mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-09-10 18:34:09 +08:00
update 2026-05-13 01:55:45
This commit is contained in:
+41
-19
@@ -1,36 +1,38 @@
|
|||||||
# SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
#
|
|
||||||
# Copyright (C) 2017-2020 Yousong Zhou <yszhou4tech@gmail.com>
|
|
||||||
# Copyright (C) 2021-2023 ImmortalWrt.org
|
|
||||||
|
|
||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=aliyundrive-webdav
|
PKG_NAME:=aliyundrive-webdav
|
||||||
PKG_VERSION:=2.3.3
|
PKG_VERSION:=2.3.3
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=1
|
||||||
|
|
||||||
PKG_SOURCE_PROTO:=git
|
|
||||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
|
||||||
PKG_SOURCE_URL:=https://github.com/messense/aliyundrive-webdav.git
|
|
||||||
PKG_SOURCE_VERSION:=6e8eba62b4e50acf89681e3a67a3a693186dcd05
|
|
||||||
PKG_MIRROR_HASH:=b6b1099c90818c5eb322bdae105d3df8d7a954d6e9ee7798c3c2412eed528640
|
|
||||||
|
|
||||||
PKG_LICENSE:=MIT
|
PKG_LICENSE:=MIT
|
||||||
PKG_MAINTAINER:=messense <messense@icloud.com>
|
PKG_MAINTAINER:=messense <messense@icloud.com>
|
||||||
|
|
||||||
PKG_BUILD_DEPENDS:=rust/host
|
PKG_LIBC:=musl
|
||||||
|
ifeq ($(ARCH),arm)
|
||||||
|
PKG_LIBC:=musleabi
|
||||||
|
|
||||||
RUST_PKG_FEATURES:=rustls-tls atomic64 native-tls native-tls-vendored
|
ARM_CPU_FEATURES:=$(word 2,$(subst +,$(space),$(call qstrip,$(CONFIG_CPU_TYPE))))
|
||||||
|
ifneq ($(filter $(ARM_CPU_FEATURES),vfp vfpv2),)
|
||||||
|
PKG_LIBC:=musleabihf
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
PKG_ARCH=$(ARCH)
|
||||||
|
ifeq ($(ARCH),i386)
|
||||||
|
PKG_ARCH:=i686
|
||||||
|
endif
|
||||||
|
|
||||||
|
PKG_SOURCE:=aliyundrive-webdav-v$(PKG_VERSION).$(PKG_ARCH)-unknown-linux-$(PKG_LIBC).tar.gz
|
||||||
|
PKG_SOURCE_URL:=https://github.com/messense/aliyundrive-webdav/releases/download/v$(PKG_VERSION)/
|
||||||
|
PKG_HASH:=skip
|
||||||
|
|
||||||
include $(INCLUDE_DIR)/package.mk
|
include $(INCLUDE_DIR)/package.mk
|
||||||
include ../../lang/rust/rust-package.mk
|
|
||||||
|
|
||||||
define Package/aliyundrive-webdav
|
define Package/aliyundrive-webdav
|
||||||
SECTION:=multimedia
|
SECTION:=multimedia
|
||||||
CATEGORY:=Multimedia
|
CATEGORY:=Multimedia
|
||||||
TITLE:=WebDAV server for AliyunDrive
|
TITLE:=WebDAV server for AliyunDrive
|
||||||
URL:=https://github.com/messense/aliyundrive-webdav
|
URL:=https://github.com/messense/aliyundrive-webdav
|
||||||
DEPENDS:=$$(RUST_ARCH_DEPENDS)
|
|
||||||
endef
|
endef
|
||||||
|
|
||||||
define Package/aliyundrive-webdav/description
|
define Package/aliyundrive-webdav/description
|
||||||
@@ -41,14 +43,34 @@ define Package/aliyundrive-webdav/conffiles
|
|||||||
/etc/config/aliyundrive-webdav
|
/etc/config/aliyundrive-webdav
|
||||||
endef
|
endef
|
||||||
|
|
||||||
|
define Download/sha256sum
|
||||||
|
FILE:=$(PKG_SOURCE).sha256
|
||||||
|
URL_FILE:=$(FILE)
|
||||||
|
URL:=$(PKG_SOURCE_URL)
|
||||||
|
HASH:=skip
|
||||||
|
endef
|
||||||
|
$(eval $(call Download,sha256sum))
|
||||||
|
|
||||||
|
define Build/Prepare
|
||||||
|
mv $(DL_DIR)/$(PKG_SOURCE).sha256 .
|
||||||
|
cp $(DL_DIR)/$(PKG_SOURCE) .
|
||||||
|
shasum -a 256 -c $(PKG_SOURCE).sha256
|
||||||
|
rm $(PKG_SOURCE).sha256 $(PKG_SOURCE)
|
||||||
|
|
||||||
|
tar -C $(PKG_BUILD_DIR)/ -zxf $(DL_DIR)/$(PKG_SOURCE)
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Build/Compile
|
||||||
|
echo "aliyundrive-webdav using precompiled binary."
|
||||||
|
endef
|
||||||
|
|
||||||
define Package/aliyundrive-webdav/install
|
define Package/aliyundrive-webdav/install
|
||||||
$(INSTALL_DIR) $(1)/usr/bin
|
$(INSTALL_DIR) $(1)/usr/bin
|
||||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/target/$(RUSTC_TARGET_ARCH)/release/aliyundrive-webdav $(1)/usr/bin/
|
$(INSTALL_BIN) $(PKG_BUILD_DIR)/aliyundrive-webdav $(1)/usr/bin/aliyundrive-webdav
|
||||||
|
|
||||||
$(INSTALL_DIR) $(1)/etc/init.d
|
$(INSTALL_DIR) $(1)/etc/init.d
|
||||||
$(INSTALL_BIN) $(CURDIR)/files/aliyundrive-webdav.init $(1)/etc/init.d/aliyundrive-webdav
|
$(INSTALL_BIN) ./files/aliyundrive-webdav.init $(1)/etc/init.d/aliyundrive-webdav
|
||||||
$(INSTALL_DIR) $(1)/etc/config
|
$(INSTALL_DIR) $(1)/etc/config
|
||||||
$(INSTALL_CONF) $(CURDIR)/files/aliyundrive-webdav.config $(1)/etc/config/aliyundrive-webdav
|
$(INSTALL_CONF) ./files/aliyundrive-webdav.config $(1)/etc/config/aliyundrive-webdav
|
||||||
endef
|
endef
|
||||||
|
|
||||||
$(eval $(call BuildPackage,aliyundrive-webdav))
|
$(eval $(call BuildPackage,aliyundrive-webdav))
|
||||||
|
|||||||
+19
-16
@@ -1,57 +1,60 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-only
|
# SPDX-License-Identifier: GPL-3.0-only
|
||||||
#
|
#
|
||||||
# Copyright (C) 2021-2023 sirpdboy <herboy2008@gmail.com>
|
# Copyright (C) 2021-2026 sirpdboy <herboy2008@gmail.com>
|
||||||
#
|
|
||||||
# This is free software, licensed under the Apache License, Version 2.0 .
|
|
||||||
#
|
#
|
||||||
|
|
||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=ddns-go
|
PKG_NAME:=ddns-go
|
||||||
PKG_VERSION:=6.17.0
|
PKG_VERSION:=6.16.2
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=1
|
||||||
|
|
||||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||||
PKG_SOURCE_URL:=https://codeload.github.com/jeessy2/ddns-go/tar.gz/v$(PKG_VERSION)?
|
PKG_SOURCE_URL:=https://codeload.github.com/jeessy2/ddns-go/tar.gz/v$(PKG_VERSION)?
|
||||||
PKG_HASH:=b1693abe90941d9d6eaf893e73bd05c1e122cd2fff5f69409987b524b12f2ad0
|
PKG_HASH:=1a92f53e50c8ca81e20c15c6dbaf490544f1a7b7b8c8d1c4c60ad8f1bc890276
|
||||||
|
|
||||||
PKG_LICENSE:=MIT
|
PKG_LICENSE:=MIT
|
||||||
PKG_LICENSE_FILES:=LICENSE
|
PKG_LICENSE_FILES:=LICENSE
|
||||||
PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
|
PKG_MAINTAINER:=sirpdboy <herboy2008@gmail.com>
|
||||||
|
|
||||||
PKG_BUILD_DEPENDS:=golang/host
|
PKG_BUILD_DEPENDS:=golang/host
|
||||||
PKG_BUILD_PARALLEL:=1
|
PKG_BUILD_PARALLEL:=1
|
||||||
PKG_BUILD_FLAGS:=no-mips16
|
PKG_BUILD_FLAGS:=no-mips16
|
||||||
|
|
||||||
GO_PKG:=github.com/jeessy2/ddns-go/v6
|
GO_PKG:=github.com/jeessy2/ddns-go/v6
|
||||||
GO_PKG_LDFLAGS_X:=main.version=$(PKG_VERSION)
|
GO_PKG_LDFLAGS_X:=main.Version=$(PKG_VERSION)
|
||||||
|
GO_PKG_LDFLAGS:=$(GO_PKG_LDFLAGS_X)
|
||||||
|
|
||||||
include $(INCLUDE_DIR)/package.mk
|
include $(INCLUDE_DIR)/package.mk
|
||||||
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
|
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
|
||||||
|
|
||||||
define Package/ddns-go
|
define Package/ddns-go
|
||||||
TITLE:=A Linux web GUI client of ddns-go
|
|
||||||
SECTION:=net
|
SECTION:=net
|
||||||
CATEGORY:=Network
|
CATEGORY:=Network
|
||||||
SUBMENU:=Web Servers/Proxies
|
SUBMENU:=IP Addresses and Names
|
||||||
DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle
|
TITLE:=Support IPV4 and IPV6 simple and easy-to-use IP binding domain name tool
|
||||||
URL:=https://github.com/jeessy2/ddns-go
|
URL:=https://github.com/jeessy2/ddns-go
|
||||||
|
DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle
|
||||||
USERID:=ddns-go:ddns-go
|
USERID:=ddns-go:ddns-go
|
||||||
endef
|
endef
|
||||||
|
|
||||||
define Package/ddns-go/description
|
define Package/ddns-go/description
|
||||||
ddns-go is a automatically obtain your public IPv4 or IPv6 address and resolve it to the corresponding domain name service,
|
A simple and easy-to-use IP binding domain name tool that supports IPV4 and IPV6.
|
||||||
support Alidns Dnspod Cloudflare Hicloud Callback Baiducloud porkbun GoDaddy Google Domains.
|
|
||||||
endef
|
endef
|
||||||
|
|
||||||
|
define Package/ddns-go/conffiles
|
||||||
|
/etc/config/ddns-go
|
||||||
|
/etc/ddns-go/ddns-go-config.yaml
|
||||||
|
endef
|
||||||
|
|
||||||
|
|
||||||
define Package/ddns-go/install
|
define Package/ddns-go/install
|
||||||
$(call GoPackage/Package/Install/Bin,$(1))
|
$(call GoPackage/Package/Install/Bin,$(1))
|
||||||
|
|
||||||
$(INSTALL_DIR) $(1)/etc/init.d
|
$(INSTALL_DIR) $(1)/etc/init.d
|
||||||
$(INSTALL_BIN) $(CURDIR)/file/ddns-go.init $(1)/etc/init.d/ddns-go
|
$(INSTALL_DIR) $(1)/etc/config
|
||||||
|
$(INSTALL_BIN) $(CURDIR)/files/ddns-go.init $(1)/etc/init.d/ddns-go
|
||||||
$(INSTALL_DIR) $(1)/etc/uci-defaults
|
$(INSTALL_CONF) $(CURDIR)/files/ddns-go.conf $(1)/etc/config/ddns-go
|
||||||
$(INSTALL_BIN) $(CURDIR)/file/luci-ddns-go.uci-default $(1)/etc/uci-defaults/luci-ddns-go
|
|
||||||
endef
|
endef
|
||||||
|
|
||||||
$(eval $(call GoBinPackage,ddns-go))
|
$(eval $(call GoBinPackage,ddns-go))
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
#!/bin/sh /etc/rc.common
|
|
||||||
#
|
|
||||||
# Copyright (C) 2021-2023 sirpdboy <herboy2008@gmail.com> https://github.com/sirpdboy/luci-app-ddns-go
|
|
||||||
#
|
|
||||||
# This file is part of ddns-go .
|
|
||||||
#
|
|
||||||
# This is free software, licensed under the Apache License, Version 2.0 .
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
START=99
|
|
||||||
USE_PROCD=1
|
|
||||||
|
|
||||||
PROG=/usr/bin/ddns-go
|
|
||||||
CONFDIR=/etc/ddns-go
|
|
||||||
CONF=$CONFDIR/ddns-go-config.yaml
|
|
||||||
|
|
||||||
get_config() {
|
|
||||||
config_get_bool enabled $1 enabled 1
|
|
||||||
config_get_bool logger $1 logger 1
|
|
||||||
config_get port $1 port 9876
|
|
||||||
config_get time $1 time 300
|
|
||||||
}
|
|
||||||
|
|
||||||
init_yaml(){
|
|
||||||
[ -d $CONFDIR ] || mkdir -p $CONFDIR 2>/dev/null
|
|
||||||
cat /usr/share/ddns-go/ddns-go-default.yaml > $CONF
|
|
||||||
}
|
|
||||||
|
|
||||||
start_service() {
|
|
||||||
config_load ddns-go
|
|
||||||
config_foreach get_config basic
|
|
||||||
[ x$enabled == x1 ] || return 1
|
|
||||||
[ -s ${CONF} ] || init_yaml
|
|
||||||
logger -t ddns-go -p warn "ddns-go is start."
|
|
||||||
echo "ddns-go is start."
|
|
||||||
procd_open_instance
|
|
||||||
procd_set_param command $PROG -l :$port -f $time -c "$CONF"
|
|
||||||
[ "x$logger" == x1 ] && procd_set_param stderr 1
|
|
||||||
procd_set_param respawn
|
|
||||||
procd_close_instance
|
|
||||||
}
|
|
||||||
|
|
||||||
service_triggers() {
|
|
||||||
procd_add_reload_trigger "ddns-go"
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
[ -s "/etc/ddns-go/localtime" ] && mv -f /etc/ddns-go/localtime /etc/localtime
|
|
||||||
/etc/init.d/ddns-go enable
|
|
||||||
/etc/init.d/ddns-go start
|
|
||||||
rm -f /tmp/luci*
|
|
||||||
exit 0
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
config basic 'config'
|
||||||
|
option enabled '0'
|
||||||
|
option logger '1'
|
||||||
|
option port '9876'
|
||||||
|
option time '300'
|
||||||
|
option ctimes '5'
|
||||||
|
option skipverify '0'
|
||||||
|
option delay '0'
|
||||||
|
option dns '223.5.5.5'
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
#!/bin/sh /etc/rc.common
|
||||||
|
#
|
||||||
|
# Copyright (C) 2021-2026 sirpdboy <herboy2008@gmail.com>
|
||||||
|
#
|
||||||
|
# This file is part of ddns-go .
|
||||||
|
#
|
||||||
|
# This is free software, licensed under the Apache License, Version 2.0 .
|
||||||
|
#
|
||||||
|
|
||||||
|
START=99
|
||||||
|
USE_PROCD=1
|
||||||
|
NAME=ddns-go
|
||||||
|
PROG=/usr/bin/ddns-go
|
||||||
|
CONFDIR=/etc/ddns-go
|
||||||
|
CONF=$CONFDIR/ddns-go-config.yaml
|
||||||
|
|
||||||
|
init_yaml() {
|
||||||
|
[ -d "$CONFDIR" ] || mkdir -p "$CONFDIR"
|
||||||
|
chown -R ddns-go:ddns-go "$CONFDIR"
|
||||||
|
chmod 755 "$CONFDIR"
|
||||||
|
[ -f "$CONF" ] && chmod 644 "$CONF"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_args() {
|
||||||
|
local cfg="$1"
|
||||||
|
local args="-c $CONF"
|
||||||
|
|
||||||
|
config_get port "$cfg" port '9876'
|
||||||
|
args="$args -l :$port"
|
||||||
|
|
||||||
|
config_get time "$cfg" time '300'
|
||||||
|
[ -n "$time" ] && args="$args -f $time"
|
||||||
|
|
||||||
|
config_get ctimes "$cfg" ctimes '5'
|
||||||
|
[ -n "$ctimes" ] && args="$args -cacheTimes $ctimes"
|
||||||
|
|
||||||
|
config_get dns "$cfg" dns '223.5.5.5'
|
||||||
|
[ -n "$dns" ] && args="$args -dns $dns"
|
||||||
|
|
||||||
|
config_get_bool noweb "$cfg" noweb 0
|
||||||
|
[ "$noweb" -eq 1 ] && args="$args -noweb"
|
||||||
|
|
||||||
|
config_get_bool skipverify "$cfg" skipverify 0
|
||||||
|
[ "$skipverify" -eq 1 ] && args="$args -skipVerify"
|
||||||
|
|
||||||
|
echo "$args"
|
||||||
|
}
|
||||||
|
|
||||||
|
start_instance() {
|
||||||
|
local cfg="$1"
|
||||||
|
config_get_bool enabled "$cfg" enabled 0
|
||||||
|
[ "$enabled" -eq 0 ] && return 0
|
||||||
|
|
||||||
|
config_get delay "$cfg" delay 0
|
||||||
|
if [ "$delay" -gt 0 ]; then
|
||||||
|
local uptime=$(awk -F. '{print $1}' /proc/uptime)
|
||||||
|
[ "$uptime" -lt 120 ] && sleep "$delay"
|
||||||
|
fi
|
||||||
|
|
||||||
|
init_yaml
|
||||||
|
|
||||||
|
local args=$(build_args "$cfg")
|
||||||
|
|
||||||
|
procd_open_instance
|
||||||
|
procd_set_param command $PROG $args
|
||||||
|
|
||||||
|
procd_set_param stdout 1
|
||||||
|
procd_set_param stderr 1
|
||||||
|
procd_set_param log_level 7
|
||||||
|
|
||||||
|
procd_set_param user ddns-go
|
||||||
|
procd_set_param respawn
|
||||||
|
|
||||||
|
procd_close_instance
|
||||||
|
}
|
||||||
|
|
||||||
|
start_service() {
|
||||||
|
config_load "$NAME"
|
||||||
|
config_foreach start_instance 'basic'
|
||||||
|
}
|
||||||
|
|
||||||
|
service_triggers() {
|
||||||
|
procd_add_reload_trigger "$NAME"
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# Copyright 2019 Shun Li <riverscn@gmail.com>
|
||||||
|
# Licensed to the public under the GNU General Public License v3.
|
||||||
|
|
||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
|
PKG_NAME:=iptvhelper
|
||||||
|
PKG_VERSION:=0.1.1
|
||||||
|
PKG_RELEASE:=1
|
||||||
|
PKG_MAINTAINER:=Shun Li <riverscn@gmail.com>
|
||||||
|
PKG_LICENSE:=GPL-3.0
|
||||||
|
|
||||||
|
include $(INCLUDE_DIR)/package.mk
|
||||||
|
|
||||||
|
define Package/iptvhelper
|
||||||
|
SECTION:=net
|
||||||
|
CATEGORY:=Network
|
||||||
|
SUBMENU:=Routing and Redirection
|
||||||
|
DEPENDS:= \
|
||||||
|
+ipset \
|
||||||
|
+iptables
|
||||||
|
TITLE:=Scripts for configure IPTV easily
|
||||||
|
MAINTAINER:=Shun Li <riverscn@gmail.com>
|
||||||
|
PKGARCH:=all
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/iptvhelper/description
|
||||||
|
Scripts for configure IPTV easily
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/iptvhelper/conffiles
|
||||||
|
/etc/config/iptvhelper
|
||||||
|
/etc/firewall.iptvhelper
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Build/Compile
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/iptvhelper/postinst
|
||||||
|
#!/bin/sh
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/iptvhelper/postrm
|
||||||
|
#!/bin/sh
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/iptvhelper/install
|
||||||
|
$(CP) ./files/* $(1)
|
||||||
|
endef
|
||||||
|
|
||||||
|
$(eval $(call BuildPackage,iptvhelper))
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
config tvbox 'default'
|
||||||
|
option disabled '1'
|
||||||
|
option respawn '1'
|
||||||
|
option mac '00:00:00:00:00:00'
|
||||||
|
option ipset '1'
|
||||||
|
option dns_redir '1'
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
test -s "/etc/init.d/iptvhelper" && /etc/init.d/iptvhelper reload
|
||||||
Executable
+127
@@ -0,0 +1,127 @@
|
|||||||
|
#!/bin/sh /etc/rc.common
|
||||||
|
# Copyright 2019 Shun Li <riverscn@gmail.com>
|
||||||
|
# Licensed to the public under the GNU General Public License v3.
|
||||||
|
|
||||||
|
START=95
|
||||||
|
USE_PROCD=1
|
||||||
|
|
||||||
|
_ipt() {
|
||||||
|
cmd="$(echo "$@" | sed 's|-A|-D|g;s|-I|-D|g')"
|
||||||
|
while iptables $cmd &>/dev/null; do
|
||||||
|
:
|
||||||
|
done
|
||||||
|
iptables $@
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy_iptv_ipset() {
|
||||||
|
for ip_set in $(ipset list | awk '/iptvhelper/ {print $2}'); do ipset destroy $ip_set; done
|
||||||
|
}
|
||||||
|
|
||||||
|
clear_iptv_rules() {
|
||||||
|
iptables-save --counters | grep -v "iptvhelper-rule" | iptables-restore --counters
|
||||||
|
}
|
||||||
|
|
||||||
|
append_arg() {
|
||||||
|
local cfg="$1"
|
||||||
|
local var="$2"
|
||||||
|
local opt="$3"
|
||||||
|
local def="$4"
|
||||||
|
local val
|
||||||
|
|
||||||
|
config_get val "$cfg" "$var"
|
||||||
|
[ -n "$val" -o -n "$def" ] && procd_append_param command $opt "${val:-$def}"
|
||||||
|
}
|
||||||
|
|
||||||
|
append_bool() {
|
||||||
|
local cfg="$1"
|
||||||
|
local var="$2"
|
||||||
|
local opt="$3"
|
||||||
|
local def="$4"
|
||||||
|
local val
|
||||||
|
|
||||||
|
config_get_bool val "$cfg" "$var" "$def"
|
||||||
|
[ "$val" = 1 ] && procd_append_param command "$opt"
|
||||||
|
}
|
||||||
|
|
||||||
|
start_instance() {
|
||||||
|
local cfg="$1"
|
||||||
|
local aux
|
||||||
|
|
||||||
|
config_get_bool aux "$cfg" 'disabled' '0'
|
||||||
|
[ "$aux" = 1 ] && return 1
|
||||||
|
|
||||||
|
logger "iptvhelper.$cfg: instance starting"
|
||||||
|
|
||||||
|
local IPTV_MAC
|
||||||
|
local IPTV_DNS_REDIR
|
||||||
|
local IPTV_IPSET
|
||||||
|
|
||||||
|
config_get IPTV_MAC $cfg mac
|
||||||
|
config_get IPTV_DNS_REDIR $cfg dns_redir
|
||||||
|
config_get IPTV_IPSET $cfg ipset
|
||||||
|
logger "iptvhelper.$cfg: topbox mac=$IPTV_MAC"
|
||||||
|
|
||||||
|
if [[ $IPTV_DNS_REDIR == '1' ]]; then
|
||||||
|
logger "iptvhelper.$cfg: topbox DNS redierct enabled"
|
||||||
|
_ipt -t nat -A PREROUTING -m mac --mac-source $IPTV_MAC -m comment --comment "iptvhelper-rule" -p udp --dport 53 -j REDIRECT --to-ports 53
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $IPTV_IPSET='1' ]]; then
|
||||||
|
logger "iptvhelper.$cfg: topbox ipset enabled"
|
||||||
|
if ! ipset -q list iptvhelper_$cfg >/dev/null; then
|
||||||
|
ipset create iptvhelper_$cfg nethash
|
||||||
|
fi
|
||||||
|
_ipt -t nat -I PREROUTING -m mac --mac-source $IPTV_MAC -m comment --comment "iptvhelper-rule" -m set ! --match-set iptvhelper_$cfg dst -m conntrack --ctstate NEW -j LOG --log-prefix "iptvhelper.$cfg:"
|
||||||
|
procd_open_instance
|
||||||
|
procd_set_param command /usr/sbin/iptvhelper.sh $cfg
|
||||||
|
config_get_bool aux "$cfg" 'respawn' '0'
|
||||||
|
[ "$aux" = 1 ] && procd_set_param respawn
|
||||||
|
procd_set_param pidfile /var/run/iptvhelper_$cfg.pid
|
||||||
|
procd_close_instance
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
service_triggers() {
|
||||||
|
procd_add_reload_trigger "iptvhelper"
|
||||||
|
}
|
||||||
|
|
||||||
|
start_service() {
|
||||||
|
logger "iptvhelper: starting"
|
||||||
|
echo "iptvhelper: starting"
|
||||||
|
config_load iptvhelper
|
||||||
|
config_foreach start_instance tvbox
|
||||||
|
if uci -q show firewall >/dev/null; then
|
||||||
|
if ! uci -q get firewall.iptvhelper >/dev/null; then
|
||||||
|
uci -q batch <<-EOF >/dev/null
|
||||||
|
set firewall.iptvhelper=include
|
||||||
|
set firewall.iptvhelper.type='script'
|
||||||
|
set firewall.iptvhelper.path='/etc/firewall.iptvhelper'
|
||||||
|
set firewall.iptvhelper.family='any'
|
||||||
|
set firewall.iptvhelper.reload='1'
|
||||||
|
commit firewall
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_service() {
|
||||||
|
if uci -q show firewall >/dev/null; then
|
||||||
|
if uci -q get firewall.iptvhelper >/dev/null; then
|
||||||
|
uci delete firewall.iptvhelper
|
||||||
|
uci commit firewall
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
for pid in $(ps | grep -v awk | awk '/iptvhelper.sh/ {print $1}'); do kill -9 $pid; done
|
||||||
|
for pid in $(ps | grep -v awk | awk '/logread -e iptvhelper/ {print $1}'); do kill -9 $pid; done
|
||||||
|
clear_iptv_rules
|
||||||
|
destroy_iptv_ipset
|
||||||
|
logger "iptvhelper: stopped"
|
||||||
|
echo "iptvhelper: stopped"
|
||||||
|
}
|
||||||
|
|
||||||
|
reload_service() {
|
||||||
|
logger "iptvhelper: reloading"
|
||||||
|
echo "iptvhelper: reloading"
|
||||||
|
stop
|
||||||
|
start
|
||||||
|
}
|
||||||
Executable
+24
@@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Copyright 2019 Shun Li <riverscn@gmail.com>
|
||||||
|
# Licensed to the public under the GNU General Public License v3.
|
||||||
|
|
||||||
|
add_to_set() {
|
||||||
|
local ip=$1
|
||||||
|
local cfg=$2
|
||||||
|
local subnet=$ip/24
|
||||||
|
ping -W1 -c1 $ip &>/dev/null && return
|
||||||
|
if ! ipset -q test iptvhelper_$cfg $subnet; then
|
||||||
|
ipset add iptvhelper_$cfg $subnet
|
||||||
|
echo added $subnet to set iptvhelper_$cfg
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo $1
|
||||||
|
|
||||||
|
logread -e "iptvhelper\.$1" -f |
|
||||||
|
while read line; do
|
||||||
|
ip=$(echo "$line" | sed -r 's|.*DST=([0-9.]+).*|\1|')
|
||||||
|
echo requested $ip
|
||||||
|
add_to_set $ip $1 &
|
||||||
|
done
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
zzz_test/
|
||||||
|
*~
|
||||||
|
*.bak
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Copyright (C) 2016 Openwrt.org
|
||||||
|
#
|
||||||
|
# This is free software, licensed under the Apache License, Version 2.0 .
|
||||||
|
#
|
||||||
|
|
||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
|
LUCI_TITLE:=LuCI support for Timewol
|
||||||
|
LUCI_DEPENDS:=+etherwake
|
||||||
|
LUCI_PKGARCH:=all
|
||||||
|
PKG_VERSION:=1.1
|
||||||
|
PKG_RELEASE:=2
|
||||||
|
|
||||||
|
include $(TOPDIR)/feeds/luci/luci.mk
|
||||||
|
|
||||||
|
# call BuildPackage - OpenWrt buildroot signature
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
module("luci.controller.timewol", package.seeall)
|
||||||
|
|
||||||
|
function index()
|
||||||
|
if not nixio.fs.access("/etc/config/luci-app-control-timewol") then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
entry({"admin", "control"}, firstchild(), "Control", 44).dependent = false
|
||||||
|
entry({"admin", "control", "timewol"}, cbi("timewol"), _("Timed Wakeup"), 95).dependent = true
|
||||||
|
entry({"admin", "control", "timewol", "status"}, call("status")).leaf = true
|
||||||
|
end
|
||||||
|
|
||||||
|
function status()
|
||||||
|
local e = {}
|
||||||
|
e.status = luci.sys.call("cat /etc/crontabs/root | grep etherwake >/dev/null") == 0
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(e)
|
||||||
|
end
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
local i = require "luci.sys"
|
||||||
|
|
||||||
|
t = Map("luci-app-control-timewol", translate("Timed network wake-up"), translate("Wake up your LAN device regularly"))
|
||||||
|
|
||||||
|
e = t:section(TypedSection, "basic", translate("Basic setting"))
|
||||||
|
e.anonymous = true
|
||||||
|
|
||||||
|
o = e:option(Flag, "enable", translate("Enable"))
|
||||||
|
o.rmempty = false
|
||||||
|
|
||||||
|
e = t:section(TypedSection, "macclient", translate("Client setting"))
|
||||||
|
e.template = "cbi/tblsection"
|
||||||
|
e.anonymous = true
|
||||||
|
e.addremove = true
|
||||||
|
|
||||||
|
nolimit_mac = e:option(Value, "macaddr", translate("MAC Address"))
|
||||||
|
nolimit_mac.rmempty = false
|
||||||
|
i.net.mac_hints(function(e, t)
|
||||||
|
nolimit_mac:value(e, "%s (%s)" % {e, t})
|
||||||
|
end)
|
||||||
|
nolimit_eth = e:option(Value, "maceth", translate("Network interface"))
|
||||||
|
nolimit_eth.rmempty = false
|
||||||
|
for t, e in ipairs(i.net.devices()) do
|
||||||
|
if e ~= "lo" then
|
||||||
|
nolimit_eth:value(e)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
a = e:option(Value, "minute", translate("minutes"))
|
||||||
|
a.optional = false
|
||||||
|
|
||||||
|
a = e:option(Value, "hour", translate("hour"))
|
||||||
|
a.optional = false
|
||||||
|
|
||||||
|
a = e:option(Value, "day", translate("day"))
|
||||||
|
a.optional = false
|
||||||
|
|
||||||
|
a = e:option(Value, "month", translate("month"))
|
||||||
|
a.optional = false
|
||||||
|
|
||||||
|
a = e:option(Value, "weeks", translate("weeks"))
|
||||||
|
a.optional = false
|
||||||
|
|
||||||
|
return t
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<%#
|
||||||
|
Copyright 2016 Chen RuiWei <crwbak@gmail.com>
|
||||||
|
Licensed to the public under the Apache License 2.0.
|
||||||
|
-%>
|
||||||
|
|
||||||
|
<% include("cbi/map") %>
|
||||||
|
<script type="text/javascript">//<![CDATA[
|
||||||
|
XHR.poll(2, '<%=luci.dispatcher.build_url("admin", "control", "timewol", "status")%>', null,
|
||||||
|
function(x, result)
|
||||||
|
{
|
||||||
|
var status = document.getElementsByClassName('timewol_status')[0];
|
||||||
|
status.setAttribute("style","font-weight:bold;");
|
||||||
|
status.setAttribute("color",result.status ? "green":"red");
|
||||||
|
status.innerHTML = result.status?'<%=translate("RUNNING")%>':'<%=translate("NOT RUNNING")%>';
|
||||||
|
}
|
||||||
|
)
|
||||||
|
//]]>
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<%+cbi/valueheader%>
|
||||||
|
<font class="timewol_status"><%=pcdata(self:cfgvalue(section) or self.default or "")%></font>
|
||||||
|
<%+cbi/valuefooter%>
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
msgid "Control"
|
||||||
|
msgstr "管控"
|
||||||
|
|
||||||
|
msgid "Running Status"
|
||||||
|
msgstr "运行状态"
|
||||||
|
|
||||||
|
msgid "Collecting data..."
|
||||||
|
msgstr "正在收集数据..."
|
||||||
|
|
||||||
|
msgid "NOT RUNNING"
|
||||||
|
msgstr "未运行"
|
||||||
|
|
||||||
|
msgid "RUNNING"
|
||||||
|
msgstr "运行中"
|
||||||
|
|
||||||
|
msgid "Timed Wakeup"
|
||||||
|
msgstr "定时唤醒"
|
||||||
|
|
||||||
|
msgid "Timed network wake-up"
|
||||||
|
msgstr "定时网络唤醒"
|
||||||
|
|
||||||
|
msgid "Wake up your LAN device regularly"
|
||||||
|
msgstr "定时唤醒你的局域网设备"
|
||||||
|
|
||||||
|
msgid "Basic setting"
|
||||||
|
msgstr "基本设置"
|
||||||
|
|
||||||
|
msgid "Enable"
|
||||||
|
msgstr "开启"
|
||||||
|
|
||||||
|
msgid "Client setting"
|
||||||
|
msgstr "客户端设置"
|
||||||
|
|
||||||
|
msgid "MAC Address"
|
||||||
|
msgstr "MAC 地址"
|
||||||
|
|
||||||
|
msgid "Network interface"
|
||||||
|
msgstr "网络接口"
|
||||||
|
|
||||||
|
msgid "minutes"
|
||||||
|
msgstr "分钟"
|
||||||
|
|
||||||
|
msgid "hour"
|
||||||
|
msgstr "小时"
|
||||||
|
|
||||||
|
msgid "day"
|
||||||
|
msgstr "日"
|
||||||
|
|
||||||
|
msgid "month"
|
||||||
|
msgstr "月"
|
||||||
|
|
||||||
|
msgid "weeks"
|
||||||
|
msgstr "星期"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
zh-cn
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
config basic
|
||||||
|
option enable '0'
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
#!/bin/sh /etc/rc.common
|
||||||
|
#
|
||||||
|
# Copyright (C) 2015 OpenWrt-dist
|
||||||
|
# Copyright (C) 2016 fw867 <ffkykzs@gmail.com>
|
||||||
|
#
|
||||||
|
# This is free software, licensed under the GNU General Public License v3.
|
||||||
|
# See /LICENSE for more information.
|
||||||
|
#
|
||||||
|
|
||||||
|
START=99
|
||||||
|
|
||||||
|
CONFIG="luci-app-control-timewol"
|
||||||
|
|
||||||
|
uci_get_by_type() {
|
||||||
|
local index=0
|
||||||
|
if [ -n $4 ]; then
|
||||||
|
index=$4
|
||||||
|
fi
|
||||||
|
local ret=$(uci get $CONFIG.@$1[$index].$2 2>/dev/null)
|
||||||
|
echo ${ret:=$3}
|
||||||
|
}
|
||||||
|
|
||||||
|
is_true() {
|
||||||
|
case $1 in
|
||||||
|
1|on|true|yes|enabled) echo 0;;
|
||||||
|
*) echo 1;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
load_config() {
|
||||||
|
ENABLED=$(uci_get_by_type basic enable)
|
||||||
|
return $(is_true $ENABLED)
|
||||||
|
}
|
||||||
|
|
||||||
|
add_rule(){
|
||||||
|
local items=$(uci show ${CONFIG} | grep "=macclient" | cut -d '.' -sf 2 | cut -d '=' -sf 1)
|
||||||
|
for i in $items; do
|
||||||
|
local macaddr=$(uci -q get ${CONFIG}.${i}.macaddr)
|
||||||
|
local maceth=$(uci -q get ${CONFIG}.${i}.maceth)
|
||||||
|
local minute=$(uci -q get ${CONFIG}.${i}.minute)
|
||||||
|
local hour=$(uci -q get ${CONFIG}.${i}.hour)
|
||||||
|
local day=$(uci -q get ${CONFIG}.${i}.day)
|
||||||
|
local month=$(uci -q get ${CONFIG}.${i}.month)
|
||||||
|
local weeks=$(uci -q get ${CONFIG}.${i}.weeks)
|
||||||
|
if [ -z $macaddr ] || [ -z $maceth ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [ -z $minute ] ; then
|
||||||
|
minute="0"
|
||||||
|
fi
|
||||||
|
if [ -z $hour ] ; then
|
||||||
|
hour="*"
|
||||||
|
fi
|
||||||
|
if [ -z $day ] ; then
|
||||||
|
day="*"
|
||||||
|
fi
|
||||||
|
if [ -z $month ] ; then
|
||||||
|
month="*"
|
||||||
|
fi
|
||||||
|
if [ -z $weeks ] ; then
|
||||||
|
weeks="*"
|
||||||
|
fi
|
||||||
|
echo "$minute $hour $day $month $weeks /usr/bin/etherwake -D -i $maceth $macaddr" >> /etc/crontabs/root
|
||||||
|
unset macaddr maceth minute hour day month weeks
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
stop
|
||||||
|
! load_config && exit 0
|
||||||
|
add_rule
|
||||||
|
}
|
||||||
|
stop() {
|
||||||
|
sed -i '/etherwake/d' /etc/crontabs/root >/dev/null 2>&1
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
uci -q batch <<-EOF >/dev/null
|
||||||
|
delete ucitrack.@luci_app_control_timewol[-1]
|
||||||
|
add ucitrack luci_app_control_timewol
|
||||||
|
set ucitrack.@luci_app_control_timewol[-1].init=luci-app-control-timewol
|
||||||
|
commit ucitrack
|
||||||
|
EOF
|
||||||
|
|
||||||
|
rm -rf /tmp/luci-*cache
|
||||||
|
exit 0
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"luci-app-control-timewol": {
|
||||||
|
"description": "Grant UCI access for luci-app-control-timewol",
|
||||||
|
"read": {
|
||||||
|
"uci": [ "luci-app-control-timewol" ]
|
||||||
|
},
|
||||||
|
"write": {
|
||||||
|
"uci": [ "luci-app-control-timewol" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"config": "luci-app-control-timewol",
|
||||||
|
"init": "luci-app-control-timewol"
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Copyright (C) 2016 Openwrt.org
|
||||||
|
#
|
||||||
|
# This is free software, licensed under the Apache License, Version 2.0 .
|
||||||
|
#
|
||||||
|
|
||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
|
LUCI_TITLE:=LuCI support for Webrestriction
|
||||||
|
LUCI_PKGARCH:=all
|
||||||
|
PKG_VERSION:=1.1
|
||||||
|
PKG_RELEASE:=2
|
||||||
|
|
||||||
|
include $(TOPDIR)/feeds/luci/luci.mk
|
||||||
|
|
||||||
|
# call BuildPackage - OpenWrt buildroot signature
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
module("luci.controller.webrestriction", package.seeall)
|
||||||
|
|
||||||
|
function index()
|
||||||
|
if not nixio.fs.access("/etc/config/luci-app-control-webrestriction") then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
entry({"admin", "control"}, firstchild(), "Control", 44).dependent = false
|
||||||
|
entry({"admin", "control", "webrestriction"}, cbi("webrestriction"), _("Access Control"), 11).dependent = true
|
||||||
|
entry({"admin", "control", "webrestriction", "status"}, call("status")).leaf = true
|
||||||
|
end
|
||||||
|
|
||||||
|
function status()
|
||||||
|
local e = {}
|
||||||
|
e.status = luci.sys.call("iptables -w -L FORWARD | grep WEB_RESTRICTION >/dev/null") == 0
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(e)
|
||||||
|
end
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
local o = require "luci.sys"
|
||||||
|
|
||||||
|
a = Map("luci-app-control-webrestriction", translate("Access Control"), translate(
|
||||||
|
"Use the blacklist or whitelist mode to control whether a client in the list can connect to the Internet."))
|
||||||
|
a.template = "webrestriction/index"
|
||||||
|
|
||||||
|
e = a:section(TypedSection, "basic", translate("Running Status"))
|
||||||
|
e.anonymous = true
|
||||||
|
|
||||||
|
t = e:option(DummyValue, "webrestriction_status", translate("Running Status"))
|
||||||
|
t.template = "webrestriction/webrestriction"
|
||||||
|
t.value = translate("Collecting data...")
|
||||||
|
|
||||||
|
e = a:section(TypedSection, "basic", translate("Global setting"))
|
||||||
|
e.anonymous = true
|
||||||
|
|
||||||
|
t = e:option(Flag, "enable", translate("Enable"))
|
||||||
|
t.rmempty = false
|
||||||
|
|
||||||
|
t = e:option(ListValue, "limit_type", translate("Limit mode"))
|
||||||
|
t.default = "blacklist"
|
||||||
|
t:value("whitelist", translate("Whitelist"))
|
||||||
|
t:value("blacklist", translate("Blacklist"))
|
||||||
|
|
||||||
|
e = a:section(TypedSection, "macbind", translate("List setting"), translate(
|
||||||
|
"In blacklist mode, the client in the list is prohibited from connecting to the Internet. In whitelist mode, only the clients in the list can connect to the Internet."))
|
||||||
|
e.template = "cbi/tblsection"
|
||||||
|
e.anonymous = true
|
||||||
|
e.addremove = true
|
||||||
|
|
||||||
|
t = e:option(Flag, "enable", translate("Enable"))
|
||||||
|
t.rmempty = false
|
||||||
|
|
||||||
|
t = e:option(Value, "macaddr", translate("MAC Address"))
|
||||||
|
t.rmempty = true
|
||||||
|
|
||||||
|
o.net.mac_hints(function(e, a)
|
||||||
|
t:value(e, "%s (%s)" % {e, a})
|
||||||
|
end)
|
||||||
|
return a
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<%#
|
||||||
|
Copyright 2016 Chen RuiWei <crwbak@gmail.com>
|
||||||
|
Licensed to the public under the Apache License 2.0.
|
||||||
|
-%>
|
||||||
|
|
||||||
|
<% include("cbi/map") %>
|
||||||
|
<script type="text/javascript">//<![CDATA[
|
||||||
|
XHR.poll(2, '<%=luci.dispatcher.build_url("admin", "control", "webrestriction", "status")%>', null,
|
||||||
|
function(x, result)
|
||||||
|
{
|
||||||
|
var status = document.getElementsByClassName('webrestriction_status')[0];
|
||||||
|
status.setAttribute("style","font-weight:bold;");
|
||||||
|
status.setAttribute("color",result.status ? "green":"red");
|
||||||
|
status.innerHTML = result.status?'<%=translate("RUNNING")%>':'<%=translate("NOT RUNNING")%>';
|
||||||
|
}
|
||||||
|
)
|
||||||
|
//]]>
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<%+cbi/valueheader%>
|
||||||
|
<font class="webrestriction_status"><%=pcdata(self:cfgvalue(section) or self.default or "")%></font>
|
||||||
|
<%+cbi/valuefooter%>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
msgid "Control"
|
||||||
|
msgstr "管控"
|
||||||
|
|
||||||
|
msgid "Running Status"
|
||||||
|
msgstr "运行状态"
|
||||||
|
|
||||||
|
msgid "Collecting data..."
|
||||||
|
msgstr "正在收集数据..."
|
||||||
|
|
||||||
|
msgid "NOT RUNNING"
|
||||||
|
msgstr "未运行"
|
||||||
|
|
||||||
|
msgid "RUNNING"
|
||||||
|
msgstr "运行中"
|
||||||
|
|
||||||
|
msgid "Access Control"
|
||||||
|
msgstr "访问限制"
|
||||||
|
|
||||||
|
msgid "Use the blacklist or whitelist mode to control whether a client in the list can connect to the Internet."
|
||||||
|
msgstr "使用黑名单或者白名单模式控制列表中的客户端是否能够连接到互联网。"
|
||||||
|
|
||||||
|
msgid "Global setting"
|
||||||
|
msgstr "全局设置"
|
||||||
|
|
||||||
|
msgid "Enable"
|
||||||
|
msgstr "开启"
|
||||||
|
|
||||||
|
msgid "Limit mode"
|
||||||
|
msgstr "限制模式"
|
||||||
|
|
||||||
|
msgid "Whitelist"
|
||||||
|
msgstr "白名单"
|
||||||
|
|
||||||
|
msgid "Blacklist"
|
||||||
|
msgstr "黑名单"
|
||||||
|
|
||||||
|
msgid "List setting"
|
||||||
|
msgstr "名单设置"
|
||||||
|
|
||||||
|
msgid "In blacklist mode, the client in the list is prohibited from connecting to the Internet. In whitelist mode, only the clients in the list can connect to the Internet."
|
||||||
|
msgstr "如果是黑名单模式,列表中的客户端将被禁止连接到互联网;白名单模式表示仅有列表中的客户端可以连接到互联网。"
|
||||||
|
|
||||||
|
msgid "MAC Address"
|
||||||
|
msgstr "MAC 地址"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
zh-cn
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
config basic
|
||||||
|
option enable '0'
|
||||||
|
option limit_type 'blacklist'
|
||||||
|
|
||||||
+81
@@ -0,0 +1,81 @@
|
|||||||
|
#!/bin/sh /etc/rc.common
|
||||||
|
# Copyright (C) 2016 fw867 <ffkykzs@gmail.com>
|
||||||
|
# Copyright (C) 2024 Lienol
|
||||||
|
|
||||||
|
START=99
|
||||||
|
|
||||||
|
CONFIG="luci-app-control-webrestriction"
|
||||||
|
ipt="iptables -w"
|
||||||
|
ip6t="ip6tables -w"
|
||||||
|
|
||||||
|
iptables_w(){
|
||||||
|
$ipt 1 "$@"
|
||||||
|
$ip6t 1 "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
add_rule(){
|
||||||
|
action=$1
|
||||||
|
local items=$(uci show ${CONFIG} | grep "=macbind" | cut -d '.' -sf 2 | cut -d '=' -sf 1)
|
||||||
|
for i in $items; do
|
||||||
|
enable=$(uci -q get ${CONFIG}.${i}.enable)
|
||||||
|
macaddr=$(uci -q get ${CONFIG}.${i}.macaddr)
|
||||||
|
if [ -z $enable ] || [ -z $macaddr ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [ "$enable" == "1" ]; then
|
||||||
|
iptables_w -A WEB_RESTRICTION -m mac --mac-source $macaddr -j $action
|
||||||
|
[ "$limit_type" == "blacklist" ] && {
|
||||||
|
iptables_w -t nat -A WEB_RESTRICTION -m mac --mac-source $macaddr -j RETURN
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
unset enable macaddr
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
start(){
|
||||||
|
stop
|
||||||
|
ENABLED=$(uci -q get ${CONFIG}.@basic[0].enable || echo "0")
|
||||||
|
[ "${ENABLED}" != "1" ] && exit 0
|
||||||
|
limit_type=$(uci -q get ${CONFIG}.@basic[0].limit_type)
|
||||||
|
|
||||||
|
iptables_w -N WEB_RESTRICTION
|
||||||
|
if [ "$limit_type" == "blacklist" ]; then
|
||||||
|
iptables_w -t nat -N WEB_RESTRICTION
|
||||||
|
add_rule DROP
|
||||||
|
else
|
||||||
|
add_rule ACCEPT
|
||||||
|
iptables_w -A WEB_RESTRICTION -j DROP
|
||||||
|
fi
|
||||||
|
|
||||||
|
FW3_INDEX_4=$($ipt -L FORWARD --line-numbers | tail -n +3 | grep -E ACCEPT | grep ctstate | grep fw3 | awk '{print $1}')
|
||||||
|
[ -n "$FW3_INDEX_4" ] && let FW3_INDEX_4+=1
|
||||||
|
$ipt -I FORWARD $FW3_INDEX_4 -j WEB_RESTRICTION
|
||||||
|
|
||||||
|
FW3_INDEX_6=$($ip6t -L FORWARD --line-numbers | tail -n +3 | grep -E ACCEPT | grep ctstate | grep fw3 | awk '{print $1}')
|
||||||
|
[ -n "$FW3_INDEX_6" ] && let FW3_INDEX_6+=1
|
||||||
|
$ip6t -I FORWARD $FW3_INDEX_6 -j WEB_RESTRICTION
|
||||||
|
|
||||||
|
[ "$limit_type" == "blacklist" ] && {
|
||||||
|
iptables_w -t nat -I PREROUTING 1 -j WEB_RESTRICTION
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stop(){
|
||||||
|
ipt_del() {
|
||||||
|
for i in $(seq 1 $($1 -nL $2 | grep -c "WEB_RESTRICTION")); do
|
||||||
|
local index=$($1 --line-number -nL $2 | grep "WEB_RESTRICTION" | head -1 | awk '{print $1}')
|
||||||
|
$1 -w -D $2 $index 2>/dev/null
|
||||||
|
done
|
||||||
|
}
|
||||||
|
ipt_del "$ipt" "FORWARD"
|
||||||
|
ipt_del "$ipt" "INPUT"
|
||||||
|
ipt_del "$ipt -t nat" "PREROUTING"
|
||||||
|
ipt_del "$ip6t" "FORWARD"
|
||||||
|
ipt_del "$ip6t" "INPUT"
|
||||||
|
ipt_del "$ip6t -t nat" "PREROUTING"
|
||||||
|
|
||||||
|
iptables_w -F WEB_RESTRICTION 2>/dev/null
|
||||||
|
iptables_w -X WEB_RESTRICTION 2>/dev/null
|
||||||
|
iptables_w -t nat -F WEB_RESTRICTION 2>/dev/null
|
||||||
|
iptables_w -t nat -X WEB_RESTRICTION 2>/dev/null
|
||||||
|
}
|
||||||
Executable
+11
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
uci -q batch <<-EOF >/dev/null
|
||||||
|
delete ucitrack.@luci_app_control_webrestriction[-1]
|
||||||
|
add ucitrack luci_app_control_webrestriction
|
||||||
|
set ucitrack.@luci_app_control_webrestriction[-1].init=luci-app-control-webrestriction
|
||||||
|
commit ucitrack
|
||||||
|
EOF
|
||||||
|
|
||||||
|
rm -rf /tmp/luci-*cache
|
||||||
|
exit 0
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"luci-app-control-webrestriction": {
|
||||||
|
"description": "Grant UCI access for luci-app-control-webrestriction",
|
||||||
|
"read": {
|
||||||
|
"uci": [ "luci-app-control-webrestriction" ]
|
||||||
|
},
|
||||||
|
"write": {
|
||||||
|
"uci": [ "luci-app-control-webrestriction" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"config": "luci-app-control-webrestriction",
|
||||||
|
"init": "luci-app-control-webrestriction"
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Copyright (C) 2016 Openwrt.org
|
||||||
|
#
|
||||||
|
# This is free software, licensed under the Apache License, Version 2.0 .
|
||||||
|
#
|
||||||
|
|
||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
|
LUCI_TITLE:=LuCI support for Weburl
|
||||||
|
LUCI_DEPENDS:=+iptables-mod-filter +kmod-ipt-filter
|
||||||
|
LUCI_PKGARCH:=all
|
||||||
|
PKG_VERSION:=1.1
|
||||||
|
PKG_RELEASE:=2
|
||||||
|
|
||||||
|
include $(TOPDIR)/feeds/luci/luci.mk
|
||||||
|
|
||||||
|
# call BuildPackage - OpenWrt buildroot signature
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
module("luci.controller.weburl", package.seeall)
|
||||||
|
|
||||||
|
function index()
|
||||||
|
if not nixio.fs.access("/etc/config/luci-app-control-weburl") then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
entry({"admin", "control"}, firstchild(), "Control", 44).dependent = false
|
||||||
|
entry({"admin", "control", "weburl"}, cbi("weburl"), _("URL Filter"), 12).dependent = true
|
||||||
|
entry({"admin", "control", "weburl", "status"}, call("status")).leaf = true
|
||||||
|
end
|
||||||
|
|
||||||
|
function status()
|
||||||
|
local e = {}
|
||||||
|
e.status = luci.sys.call("iptables -w -L FORWARD | grep WEBURL >/dev/null") == 0
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(e)
|
||||||
|
end
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
local o = require "luci.sys"
|
||||||
|
|
||||||
|
a = Map("luci-app-control-weburl", translate("URL Filter"), translate(
|
||||||
|
"Set keyword filtering here, can be any character in the URL, can filter such as video sites, QQ, thunder, Taobao..."))
|
||||||
|
a.template = "weburl/index"
|
||||||
|
|
||||||
|
t = a:section(TypedSection, "basic", translate("Running Status"))
|
||||||
|
t.anonymous = true
|
||||||
|
|
||||||
|
e = t:option(DummyValue, "weburl_status", translate("Running Status"))
|
||||||
|
e.template = "weburl/weburl"
|
||||||
|
e.value = translate("Collecting data...")
|
||||||
|
|
||||||
|
t = a:section(TypedSection, "basic", translate("Basic setting"), translate(
|
||||||
|
"In general, normal filtering works fine, but forced filtering uses more complex algorithms and leads to higher CPU usage."))
|
||||||
|
t.anonymous = true
|
||||||
|
|
||||||
|
e = t:option(Flag, "enable", translate("Enable"))
|
||||||
|
e.rmempty = false
|
||||||
|
|
||||||
|
e = t:option(Flag, "algos", translate("Forced filter"))
|
||||||
|
e.rmempty = false
|
||||||
|
|
||||||
|
t = a:section(TypedSection, "macbind", translate("Keyword setting"), translate(
|
||||||
|
"MAC addresses do not filter out all clients. For example, only specified clients are filtered out. Filtering time is optional."))
|
||||||
|
t.template = "cbi/tblsection"
|
||||||
|
t.anonymous = true
|
||||||
|
t.addremove = true
|
||||||
|
|
||||||
|
e = t:option(Flag, "enable", translate("Enable"))
|
||||||
|
e.rmempty = false
|
||||||
|
|
||||||
|
e = t:option(Value, "macaddr", translate("MAC Address"))
|
||||||
|
e.rmempty = true
|
||||||
|
|
||||||
|
o.net.mac_hints(function(t, a)
|
||||||
|
e:value(t, "%s (%s)" % {t, a})
|
||||||
|
end)
|
||||||
|
|
||||||
|
e = t:option(Value, "timeon", translate("Start time"))
|
||||||
|
e.placeholder = "00:00"
|
||||||
|
e.rmempty = true
|
||||||
|
|
||||||
|
e = t:option(Value, "timeoff", translate("End time"))
|
||||||
|
e.placeholder = "23:59"
|
||||||
|
e.rmempty = true
|
||||||
|
|
||||||
|
e = t:option(Value, "keyword", translate("Keyword"))
|
||||||
|
e.rmempty = false
|
||||||
|
|
||||||
|
return a
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<%#
|
||||||
|
Copyright 2016 Chen RuiWei <crwbak@gmail.com>
|
||||||
|
Licensed to the public under the Apache License 2.0.
|
||||||
|
-%>
|
||||||
|
|
||||||
|
<% include("cbi/map") %>
|
||||||
|
<script type="text/javascript">//<![CDATA[
|
||||||
|
XHR.poll(2, '<%=luci.dispatcher.build_url("admin", "control", "weburl", "status")%>', null,
|
||||||
|
function(x, result)
|
||||||
|
{
|
||||||
|
var status = document.getElementsByClassName('weburl_status')[0];
|
||||||
|
status.setAttribute("style","font-weight:bold;");
|
||||||
|
status.setAttribute("color",result.status ? "green":"red");
|
||||||
|
status.innerHTML = result.status?'<%=translate("RUNNING")%>':'<%=translate("NOT RUNNING")%>';
|
||||||
|
}
|
||||||
|
)
|
||||||
|
//]]>
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<%+cbi/valueheader%>
|
||||||
|
<font class="weburl_status"><%=pcdata(self:cfgvalue(section) or self.default or "")%></font>
|
||||||
|
<%+cbi/valuefooter%>
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
msgid "Control"
|
||||||
|
msgstr "管控"
|
||||||
|
|
||||||
|
msgid "Running Status"
|
||||||
|
msgstr "运行状态"
|
||||||
|
|
||||||
|
msgid "Collecting data..."
|
||||||
|
msgstr "正在收集数据..."
|
||||||
|
|
||||||
|
msgid "NOT RUNNING"
|
||||||
|
msgstr "未运行"
|
||||||
|
|
||||||
|
msgid "RUNNING"
|
||||||
|
msgstr "运行中"
|
||||||
|
|
||||||
|
msgid "URL Filter"
|
||||||
|
msgstr "网址过滤"
|
||||||
|
|
||||||
|
msgid "Set keyword filtering here, can be any character in the URL, can filter such as video sites, QQ, thunder, Taobao..."
|
||||||
|
msgstr "在这里设置关键词过滤,可以是URL里任意字符,可以过滤如视频网站、QQ、迅雷、淘宝。。。"
|
||||||
|
|
||||||
|
msgid "Basic setting"
|
||||||
|
msgstr "基本设置"
|
||||||
|
|
||||||
|
msgid "In general, normal filtering works fine, but forced filtering uses more complex algorithms and leads to higher CPU usage."
|
||||||
|
msgstr "一般来说普通过滤效果就很好了,强制过滤会使用更复杂的算法导致更高的CPU占用。"
|
||||||
|
|
||||||
|
msgid "Enable"
|
||||||
|
msgstr "启用"
|
||||||
|
|
||||||
|
msgid "Forced filter"
|
||||||
|
msgstr "强效过滤"
|
||||||
|
|
||||||
|
msgid "Keyword setting"
|
||||||
|
msgstr "关键词设置"
|
||||||
|
|
||||||
|
msgid "MAC addresses do not filter out all clients. For example, only specified clients are filtered out. Filtering time is optional."
|
||||||
|
msgstr "MAC不设置为全客户端过滤,如设置只过滤指定的客户端。过滤时间可不设置。"
|
||||||
|
|
||||||
|
msgid "MAC Address"
|
||||||
|
msgstr "MAC 地址"
|
||||||
|
|
||||||
|
msgid "Start time"
|
||||||
|
msgstr "开始时间"
|
||||||
|
|
||||||
|
msgid "End time"
|
||||||
|
msgstr "结束时间"
|
||||||
|
|
||||||
|
msgid "Keyword"
|
||||||
|
msgstr "关键字"
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
zh-cn
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
config basic
|
||||||
|
option enable '0'
|
||||||
|
option algos '0'
|
||||||
|
|
||||||
|
config macbind
|
||||||
|
option keyword 'qq.com'
|
||||||
|
option enable '0'
|
||||||
|
option macaddr '00:0C:29:C8:99:9E'
|
||||||
|
|
||||||
|
config macbind
|
||||||
|
option keyword 'taobao.com'
|
||||||
|
option enable '0'
|
||||||
|
|
||||||
|
config macbind
|
||||||
|
option enable '0'
|
||||||
|
option keyword 'youku.com'
|
||||||
|
|
||||||
|
config macbind
|
||||||
|
option enable '0'
|
||||||
|
option keyword 'www'
|
||||||
|
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
#!/bin/sh /etc/rc.common
|
||||||
|
# Copyright (C) 2016 fw867 <ffkykzs@gmail.com>
|
||||||
|
# Copyright (C) 2024 Lienol
|
||||||
|
|
||||||
|
START=99
|
||||||
|
|
||||||
|
CONFIG="luci-app-control-weburl"
|
||||||
|
|
||||||
|
iptables_w(){
|
||||||
|
iptables -w 1 "$@"
|
||||||
|
ip6tables -w 1 "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
start(){
|
||||||
|
stop
|
||||||
|
ENABLED=$(uci -q get ${CONFIG}.@basic[0].enable || echo "0")
|
||||||
|
[ "${ENABLED}" != "1" ] && exit 0
|
||||||
|
ALGOS=$(uci -q get ${CONFIG}.@basic[0].algos || echo "0")
|
||||||
|
WEBURL_ALGOS="bm"
|
||||||
|
[ "${ALGOS}" = "1" ] && WEBURL_ALGOS="kmp"
|
||||||
|
|
||||||
|
# resolve interface
|
||||||
|
local interface=$(
|
||||||
|
. /lib/functions/network.sh
|
||||||
|
|
||||||
|
network_is_up "lan" && network_get_device device "lan"
|
||||||
|
echo "${device:-br-lan}"
|
||||||
|
)
|
||||||
|
|
||||||
|
iptables_w -t filter -N WEBURL_REJECT
|
||||||
|
iptables_w -t filter -I WEBURL_REJECT -j DROP
|
||||||
|
iptables_w -t filter -I WEBURL_REJECT -p tcp -j REJECT --reject-with tcp-reset
|
||||||
|
iptables_w -t filter -N WEBURL_RULES
|
||||||
|
iptables_w -t filter -N WEBURL
|
||||||
|
iptables_w -t filter -I WEBURL -i $interface -m length --length 53:768 -j WEBURL_RULES
|
||||||
|
# iptables_w -t filter -I WEBURL -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
iptables_w -t filter -I FORWARD -j WEBURL
|
||||||
|
|
||||||
|
local items=$(uci show ${CONFIG} | grep "=macbind" | cut -d '.' -sf 2 | cut -d '=' -sf 1)
|
||||||
|
for i in $items; do
|
||||||
|
enable=$(uci -q get ${CONFIG}.${i}.enable || echo "0")
|
||||||
|
macaddr=$(uci -q get ${CONFIG}.${i}.macaddr)
|
||||||
|
timeon=$(uci -q get ${CONFIG}.${i}.timeon)
|
||||||
|
timeoff=$(uci -q get ${CONFIG}.${i}.timeoff)
|
||||||
|
keyword=$(uci -q get ${CONFIG}.${i}.keyword)
|
||||||
|
if [ "$enable" == "0" ] || [ -z "$keyword" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [ -z "$timeon" ] || [ -z "$timeoff" ]; then
|
||||||
|
settime=""
|
||||||
|
else
|
||||||
|
settime="-m time --kerneltz --timestart $timeon --timestop $timeoff"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$macaddr" ]; then
|
||||||
|
iptables_w -t filter -I WEBURL_RULES $settime -m string --string "$keyword" --algo $WEBURL_ALGOS -j WEBURL_REJECT
|
||||||
|
else
|
||||||
|
iptables_w -t filter -I WEBURL_RULES $settime -m mac --mac-source $macaddr -m string --string "$keyword" --algo $WEBURL_ALGOS -j WEBURL_REJECT
|
||||||
|
fi
|
||||||
|
unset enable macaddr timeon timeoff keyword
|
||||||
|
done
|
||||||
|
|
||||||
|
logger -t weburl "weburl filter on $interface"
|
||||||
|
}
|
||||||
|
|
||||||
|
stop(){
|
||||||
|
iptables_w -t filter -D FORWARD -j WEBURL 2>/dev/null
|
||||||
|
iptables_w -t filter -F WEBURL 2>/dev/null
|
||||||
|
iptables_w -t filter -X WEBURL 2>/dev/null
|
||||||
|
iptables_w -t filter -F WEBURL_RULES 2>/dev/null
|
||||||
|
iptables_w -t filter -X WEBURL_RULES 2>/dev/null
|
||||||
|
iptables_w -t filter -F WEBURL_REJECT 2>/dev/null
|
||||||
|
iptables_w -t filter -X WEBURL_REJECT 2>/dev/null
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
uci -q batch <<-EOF >/dev/null
|
||||||
|
delete ucitrack.@luci_app_control_weburl[-1]
|
||||||
|
add ucitrack luci_app_control_weburl
|
||||||
|
set ucitrack.@luci_app_control_weburl[-1].init=luci-app-control-weburl
|
||||||
|
commit ucitrack
|
||||||
|
EOF
|
||||||
|
|
||||||
|
rm -rf /tmp/luci-*cache
|
||||||
|
exit 0
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"luci-app-control-weburl": {
|
||||||
|
"description": "Grant UCI access for luci-app-control-weburl",
|
||||||
|
"read": {
|
||||||
|
"uci": [ "luci-app-control-weburl" ]
|
||||||
|
},
|
||||||
|
"write": {
|
||||||
|
"uci": [ "luci-app-control-weburl" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"config": "luci-app-control-weburl",
|
||||||
|
"init": "luci-app-control-weburl"
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# From https://github.com/DarkDean89/luci-app-filebrowser
|
||||||
|
# From https://github.com/stuarthua/oh-my-openwrt/tree/master/stuart/luci-app-fileassistant
|
||||||
|
# This is free software, licensed under the Apache License, Version 2.0 .
|
||||||
|
|
||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
|
LUCI_TITLE:=LuCI support for Fileassistant
|
||||||
|
LUCI_PKGARCH:=all
|
||||||
|
PKG_VERSION:=1.0
|
||||||
|
PKG_RELEASE:=5
|
||||||
|
|
||||||
|
include $(TOPDIR)/feeds/luci/luci.mk
|
||||||
|
|
||||||
|
# call BuildPackage - OpenWrt buildroot signature
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
.fb-container {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
.fb-container .cbi-button {
|
||||||
|
height: 1.8rem;
|
||||||
|
}
|
||||||
|
.fb-container .cbi-input-text {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.fb-container .panel-title {
|
||||||
|
padding-bottom: 0;
|
||||||
|
width: 50%;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.fb-container .panel-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
.fb-container .upload-container {
|
||||||
|
display: none;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
.fb-container .upload-file {
|
||||||
|
margin-right: 2rem;
|
||||||
|
}
|
||||||
|
.fb-container .cbi-value-field {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.fb-container .parent-icon strong {
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
.fb-container td[class$="-icon"] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.fb-container .file-icon, .fb-container .folder-icon, .fb-container .link-icon {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.fb-container .file-icon:before, .fb-container .folder-icon:before, .fb-container .link-icon:before {
|
||||||
|
display: inline-block;
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
content: '';
|
||||||
|
background-size: contain;
|
||||||
|
margin: 0 0.5rem 0 1rem;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.fb-container .file-icon:before {
|
||||||
|
background-image: url(file-icon.png);
|
||||||
|
}
|
||||||
|
.fb-container .folder-icon:before {
|
||||||
|
background-image: url(folder-icon.png);
|
||||||
|
}
|
||||||
|
.fb-container .link-icon:before {
|
||||||
|
background-image: url(link-icon.png);
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 480px) {
|
||||||
|
.fb-container .upload-file {
|
||||||
|
width: 14.6rem;
|
||||||
|
}
|
||||||
|
.fb-container .cbi-value-owner,
|
||||||
|
.fb-container .cbi-value-perm {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,358 @@
|
|||||||
|
String.prototype.replaceAll = function(search, replacement) {
|
||||||
|
var target = this;
|
||||||
|
return target.replace(new RegExp(search, 'g'), replacement);
|
||||||
|
};
|
||||||
|
(function () {
|
||||||
|
var iwxhr = new XHR();
|
||||||
|
var listElem = document.getElementById("list-content");
|
||||||
|
listElem.onclick = handleClick;
|
||||||
|
var currentPath;
|
||||||
|
var pathElem = document.getElementById("current-path");
|
||||||
|
pathElem.onblur = function () {
|
||||||
|
update_list(this.value.trim());
|
||||||
|
};
|
||||||
|
pathElem.onkeyup = function (evt) {
|
||||||
|
if (evt.keyCode == 13) {
|
||||||
|
this.blur();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
function removePath(filename, isdir) {
|
||||||
|
var c = confirm('你确定要删除 ' + filename + ' 吗?');
|
||||||
|
if (c) {
|
||||||
|
iwxhr.get('/cgi-bin/luci/admin/nas/fileassistant/delete',
|
||||||
|
{
|
||||||
|
path: concatPath(currentPath, filename),
|
||||||
|
isdir: isdir
|
||||||
|
},
|
||||||
|
function (x, res) {
|
||||||
|
if (res.ec === 0) {
|
||||||
|
refresh_list(res.data, currentPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function installPath(filename, isdir) {
|
||||||
|
if (isdir === "1") {
|
||||||
|
alert('这是一个目录,请选择 ipk 文件进行安装!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var isipk = isIPK(filename);
|
||||||
|
if (isipk === 0) {
|
||||||
|
alert('只允许安装 ipk 格式的文件!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var c = confirm('你确定要安装 ' + filename + ' 吗?');
|
||||||
|
if (c) {
|
||||||
|
iwxhr.get('/cgi-bin/luci/admin/nas/fileassistant/install',
|
||||||
|
{
|
||||||
|
filepath: concatPath(currentPath, filename),
|
||||||
|
isdir: isdir
|
||||||
|
},
|
||||||
|
function (x, res) {
|
||||||
|
if (res.ec === 0) {
|
||||||
|
location.reload();
|
||||||
|
alert('安装成功!');
|
||||||
|
} else {
|
||||||
|
alert('安装失败,请检查文件格式!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isIPK(filename) {
|
||||||
|
var index= filename.lastIndexOf(".");
|
||||||
|
var ext = filename.substr(index+1);
|
||||||
|
if (ext === 'ipk') {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renamePath(filename) {
|
||||||
|
var newname = prompt('请输入新的文件名:', filename);
|
||||||
|
if (newname) {
|
||||||
|
newname = newname.trim();
|
||||||
|
if (newname != filename) {
|
||||||
|
var newpath = concatPath(currentPath, newname);
|
||||||
|
iwxhr.get('/cgi-bin/luci/admin/nas/fileassistant/rename',
|
||||||
|
{
|
||||||
|
filepath: concatPath(currentPath, filename),
|
||||||
|
newpath: newpath
|
||||||
|
},
|
||||||
|
function (x, res) {
|
||||||
|
if (res.ec === 0) {
|
||||||
|
refresh_list(res.data, currentPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function chmodPath(filename, isdir) {
|
||||||
|
var newmod = prompt('请输入新的权限位(支持八进制权限位或者a+x格式):', isdir === "1" ? "0755" : "0644");
|
||||||
|
if (newmod) {
|
||||||
|
iwxhr.get('/cgi-bin/luci/admin/nas/fileassistant/chmod',
|
||||||
|
{
|
||||||
|
filepath: concatPath(currentPath, filename),
|
||||||
|
newmod: newmod
|
||||||
|
},
|
||||||
|
function (x, res) {
|
||||||
|
if (res.ec === 0) {
|
||||||
|
refresh_list(res.data, currentPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function chownPath(filename) {
|
||||||
|
var newown = prompt('请输入新的用户名(支持用户名或用户名:群组格式):', "root");
|
||||||
|
if (newown) {
|
||||||
|
iwxhr.get('/cgi-bin/luci/admin/nas/fileassistant/chown',
|
||||||
|
{
|
||||||
|
filepath: concatPath(currentPath, filename),
|
||||||
|
newown: newown
|
||||||
|
},
|
||||||
|
function (x, res) {
|
||||||
|
if (res.ec === 0) {
|
||||||
|
refresh_list(res.data, currentPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openpath(filename, dirname) {
|
||||||
|
dirname = dirname || currentPath;
|
||||||
|
window.open('/cgi-bin/luci/admin/nas/fileassistant/open?path='
|
||||||
|
+ encodeURIComponent(dirname) + '&filename='
|
||||||
|
+ encodeURIComponent(filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFileElem(elem) {
|
||||||
|
if (elem.className.indexOf('-icon') > -1) {
|
||||||
|
return elem;
|
||||||
|
}
|
||||||
|
else if (elem.parentNode.className.indexOf('-icon') > -1) {
|
||||||
|
return elem.parentNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function concatPath(path, filename) {
|
||||||
|
if (path === '/') {
|
||||||
|
return path + filename;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return path.replace(/\/$/, '') + '/' + filename;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClick(evt) {
|
||||||
|
var targetElem = evt.target;
|
||||||
|
var infoElem;
|
||||||
|
if (targetElem.className.indexOf('cbi-button-remove') > -1) {
|
||||||
|
infoElem = targetElem.parentNode.parentNode;
|
||||||
|
removePath(infoElem.dataset['filename'] , infoElem.dataset['isdir'])
|
||||||
|
}
|
||||||
|
else if (targetElem.className.indexOf('cbi-button-install') > -1) {
|
||||||
|
infoElem = targetElem.parentNode.parentNode;
|
||||||
|
installPath(infoElem.dataset['filename'] , infoElem.dataset['isdir'])
|
||||||
|
}
|
||||||
|
else if (targetElem.className.indexOf('cbi-button-edit') > -1) {
|
||||||
|
renamePath(targetElem.parentNode.parentNode.dataset['filename']);
|
||||||
|
}
|
||||||
|
else if (targetElem.className.indexOf('cbi-button-chmod') > -1) {
|
||||||
|
infoElem = targetElem.parentNode.parentNode;
|
||||||
|
chmodPath(infoElem.dataset['filename'] , infoElem.dataset['isdir']);
|
||||||
|
}
|
||||||
|
else if (targetElem.className.indexOf('cbi-button-chown') > -1) {
|
||||||
|
chownPath(targetElem.parentNode.parentNode.dataset['filename']);
|
||||||
|
}
|
||||||
|
else if (targetElem = getFileElem(targetElem)) {
|
||||||
|
if (targetElem.className.indexOf('parent-icon') > -1) {
|
||||||
|
update_list(currentPath.replace(/\/[^/]+($|\/$)/, ''));
|
||||||
|
}
|
||||||
|
else if (targetElem.className.indexOf('file-icon') > -1) {
|
||||||
|
openpath(targetElem.parentNode.dataset['filename']);
|
||||||
|
}
|
||||||
|
else if (targetElem.className.indexOf('link-icon') > -1) {
|
||||||
|
infoElem = targetElem.parentNode;
|
||||||
|
var filepath = infoElem.dataset['linktarget'];
|
||||||
|
if (filepath) {
|
||||||
|
if (infoElem.dataset['isdir'] === "1") {
|
||||||
|
update_list(filepath);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var lastSlash = filepath.lastIndexOf('/');
|
||||||
|
openpath(filepath.substring(lastSlash + 1), filepath.substring(0, lastSlash));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (targetElem.className.indexOf('folder-icon') > -1) {
|
||||||
|
update_list(concatPath(currentPath, targetElem.parentNode.dataset['filename']))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function refresh_list(filenames, path) {
|
||||||
|
var listHtml = '<table class="cbi-section-table"><thead><tr class="cbi-section-table-row cbi-rowstyle-2">'
|
||||||
|
+'<td class="cbi-value-field">文件</td>'
|
||||||
|
+'<td class="cbi-value-field">所有者</td>'
|
||||||
|
+'<td class="cbi-value-field">修改时间</td>'
|
||||||
|
+'<td class="cbi-value-field">大小</td>'
|
||||||
|
+'<td class="cbi-value-field">权限</td>'
|
||||||
|
+'<td class="cbi-section-table-cell">操作</td>'
|
||||||
|
+'</tr></thead><tbody>';
|
||||||
|
if (path !== '/') {
|
||||||
|
listHtml += '<tr class="cbi-section-table-row cbi-rowstyle-2"><td class="parent-icon" colspan="6"><strong>..</strong></td></tr>';
|
||||||
|
}
|
||||||
|
if (filenames) {
|
||||||
|
for (var i = 0; i < filenames.length; i++) {
|
||||||
|
var line = filenames[i];
|
||||||
|
if (line) {
|
||||||
|
var f = line.match(/(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+([\S\s]+)/);
|
||||||
|
var isLink = f[1][0] === 'z' || f[1][0] === 'l' || f[1][0] === 'x';
|
||||||
|
var o = {
|
||||||
|
displayname: f[9],
|
||||||
|
filename: isLink ? f[9].split(' -> ')[0] : f[9],
|
||||||
|
perms: f[1],
|
||||||
|
date: f[7] + ' ' + f[6] + ' ' + f[8],
|
||||||
|
size: f[5],
|
||||||
|
owner: f[3],
|
||||||
|
icon: (f[1][0] === 'd') ? "folder-icon" : (isLink ? "link-icon" : "file-icon")
|
||||||
|
};
|
||||||
|
|
||||||
|
var install_btn = '<button class="cbi-button cbi-button-install" style="visibility: hidden;">安装</button>';
|
||||||
|
var index= o.filename.lastIndexOf(".");
|
||||||
|
var ext = o.filename.substr(index+1);
|
||||||
|
if (ext === 'ipk') {
|
||||||
|
install_btn = '<button class="cbi-button cbi-button-install">安装</button>';
|
||||||
|
}
|
||||||
|
|
||||||
|
listHtml += '<tr class="cbi-section-table-row cbi-rowstyle-' + (1 + i%2)
|
||||||
|
+ '" data-filename="' + o.filename + '" data-isdir="' + Number(f[1][0] === 'd' || f[1][0] === 'z') + '"'
|
||||||
|
+ ((f[1][0] === 'z' || f[1][0] === 'l') ? (' data-linktarget="' + f[9].split(' -> ')[1]) : '')
|
||||||
|
+ '">'
|
||||||
|
+ '<td class="cbi-value-field ' + o.icon + '">'
|
||||||
|
+ '<strong>' + o.displayname + '</strong>'
|
||||||
|
+ '</td>'
|
||||||
|
+ '<td class="cbi-value-field cbi-value-owner">'+o.owner+'</td>'
|
||||||
|
+ '<td class="cbi-value-field cbi-value-date">'+o.date+'</td>'
|
||||||
|
+ '<td class="cbi-value-field cbi-value-size">'+o.size+'</td>'
|
||||||
|
+ '<td class="cbi-value-field cbi-value-perm">'+o.perms+'</td>'
|
||||||
|
+ '<td class="cbi-section-table-cell">\
|
||||||
|
<button class="cbi-button cbi-button-edit">重命名</button>\
|
||||||
|
<button class="cbi-button cbi-button-remove">删除</button>\
|
||||||
|
<button class="cbi-button cbi-button-apply cbi-button-chmod">改权限</button>\
|
||||||
|
<button class="cbi-button cbi-button-apply cbi-button-chown">改用户</button>'
|
||||||
|
+ install_btn
|
||||||
|
+ '</td>'
|
||||||
|
+ '</tr>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
listHtml += "</table>";
|
||||||
|
listElem.innerHTML = listHtml;
|
||||||
|
}
|
||||||
|
function update_list(path, opt) {
|
||||||
|
opt = opt || {};
|
||||||
|
path = concatPath(path, '');
|
||||||
|
if (currentPath != path) {
|
||||||
|
iwxhr.get('/cgi-bin/luci/admin/nas/fileassistant/list',
|
||||||
|
{path: path},
|
||||||
|
function (x, res) {
|
||||||
|
if (res.ec === 0) {
|
||||||
|
refresh_list(res.data, path);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
refresh_list([], path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (!opt.popState) {
|
||||||
|
history.pushState({path: path}, null, '?path=' + path);
|
||||||
|
}
|
||||||
|
currentPath = path;
|
||||||
|
pathElem.value = currentPath;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var uploadToggle = document.getElementById('upload-toggle');
|
||||||
|
var uploadContainer = document.getElementById('upload-container');
|
||||||
|
var isUploadHide = true;
|
||||||
|
uploadToggle.onclick = function() {
|
||||||
|
if (isUploadHide) {
|
||||||
|
uploadContainer.style.display = 'inline-flex';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
uploadContainer.style.display = 'none';
|
||||||
|
}
|
||||||
|
isUploadHide = !isUploadHide;
|
||||||
|
};
|
||||||
|
var uploadBtn = uploadContainer.getElementsByClassName('cbi-input-apply')[0];
|
||||||
|
uploadBtn.onclick = function (evt) {
|
||||||
|
var uploadinput = document.getElementById('upload-file');
|
||||||
|
var fullPath = uploadinput.value;
|
||||||
|
if (!fullPath) {
|
||||||
|
evt.preventDefault();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var formData = new FormData();
|
||||||
|
var startIndex = (fullPath.indexOf('\\') >= 0 ? fullPath.lastIndexOf('\\') : fullPath.lastIndexOf('/'));
|
||||||
|
formData.append('upload-filename', fullPath.substring(startIndex + 1));
|
||||||
|
formData.append('upload-dir', concatPath(currentPath, ''));
|
||||||
|
formData.append('upload-file', uploadinput.files[0]);
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open("POST", "/cgi-bin/luci/admin/nas/fileassistant/upload", true);
|
||||||
|
xhr.onload = function() {
|
||||||
|
if (xhr.status == 200) {
|
||||||
|
var res = JSON.parse(xhr.responseText);
|
||||||
|
refresh_list(res.data, currentPath);
|
||||||
|
uploadinput.value = '';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alert('上传失败,请稍后再试...');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.send(formData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById('mkdir-toggle').onclick = function() {
|
||||||
|
var dirname = null;
|
||||||
|
if (dirname = prompt("请输入文件夹名称:")) {
|
||||||
|
var formData = new FormData();
|
||||||
|
formData.append('path', currentPath);
|
||||||
|
formData.append('dirname', dirname);
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open("POST", "/cgi-bin/luci/admin/nas/fileassistant/mkdir", true);
|
||||||
|
xhr.onload = function() {
|
||||||
|
if (xhr.status == 200) {
|
||||||
|
var res = JSON.parse(xhr.responseText);
|
||||||
|
refresh_list(res.data, currentPath);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alert('创建失败,请稍后再试...');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.send(formData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('DOMContentLoaded', function(evt) {
|
||||||
|
var initPath = '/';
|
||||||
|
if (/path=([/\w]+)/.test(location.search)) {
|
||||||
|
initPath = RegExp.$1;
|
||||||
|
}
|
||||||
|
update_list(initPath, {popState: true});
|
||||||
|
});
|
||||||
|
window.addEventListener('popstate', function (evt) {
|
||||||
|
var path = '/';
|
||||||
|
if (evt.state && evt.state.path) {
|
||||||
|
path = evt.state.path;
|
||||||
|
}
|
||||||
|
update_list(path, {popState: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
})();
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,262 @@
|
|||||||
|
module("luci.controller.fileassistant", package.seeall)
|
||||||
|
|
||||||
|
function index()
|
||||||
|
|
||||||
|
entry({"admin", "nas"}, firstchild(), "NAS", 44).dependent = false
|
||||||
|
|
||||||
|
local page
|
||||||
|
page = entry({"admin", "nas", "fileassistant"}, template("fileassistant"), _("文件助手"), 1)
|
||||||
|
page.i18n = "base"
|
||||||
|
page.dependent = true
|
||||||
|
|
||||||
|
page = entry({"admin", "nas", "fileassistant", "list"}, call("fileassistant_list"), nil)
|
||||||
|
page.leaf = true
|
||||||
|
|
||||||
|
page = entry({"admin", "nas", "fileassistant", "open"}, call("fileassistant_open"), nil)
|
||||||
|
page.leaf = true
|
||||||
|
|
||||||
|
page = entry({"admin", "nas", "fileassistant", "delete"}, call("fileassistant_delete"), nil)
|
||||||
|
page.leaf = true
|
||||||
|
|
||||||
|
page = entry({"admin", "nas", "fileassistant", "rename"}, call("fileassistant_rename"), nil)
|
||||||
|
page.leaf = true
|
||||||
|
|
||||||
|
page = entry({"admin", "nas", "fileassistant", "upload"}, call("fileassistant_upload"), nil)
|
||||||
|
page.leaf = true
|
||||||
|
|
||||||
|
page = entry({"admin", "nas", "fileassistant", "install"}, call("fileassistant_install"), nil)
|
||||||
|
page.leaf = true
|
||||||
|
|
||||||
|
page = entry({"admin", "nas", "fileassistant", "mkdir"}, call("fileassistant_mkdir"), nil)
|
||||||
|
page.leaf = true
|
||||||
|
|
||||||
|
page = entry({"admin", "nas", "fileassistant", "chmod"}, call("fileassistant_chmod"), nil)
|
||||||
|
page.leaf = true
|
||||||
|
|
||||||
|
page = entry({"admin", "nas", "fileassistant", "chown"}, call("fileassistant_chown"), nil)
|
||||||
|
page.leaf = true
|
||||||
|
end
|
||||||
|
|
||||||
|
function list_response(path, success)
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
local result
|
||||||
|
if success then
|
||||||
|
local rv = scandir(path)
|
||||||
|
result = {
|
||||||
|
ec = 0,
|
||||||
|
data = rv
|
||||||
|
}
|
||||||
|
else
|
||||||
|
result = {
|
||||||
|
ec = 1
|
||||||
|
}
|
||||||
|
end
|
||||||
|
luci.http.write_json(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
function fileassistant_list()
|
||||||
|
local path = luci.http.formvalue("path")
|
||||||
|
list_response(path, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
function fileassistant_open()
|
||||||
|
local path = luci.http.formvalue("path")
|
||||||
|
local filename = luci.http.formvalue("filename")
|
||||||
|
local io = require "io"
|
||||||
|
local mime = to_mime(filename)
|
||||||
|
|
||||||
|
file = path..filename
|
||||||
|
|
||||||
|
local download_fpi = io.open(file, "r")
|
||||||
|
luci.http.header('Content-Disposition', 'inline; filename="'..filename..'"' )
|
||||||
|
luci.http.prepare_content(mime)
|
||||||
|
luci.ltn12.pump.all(luci.ltn12.source.file(download_fpi), luci.http.write)
|
||||||
|
end
|
||||||
|
|
||||||
|
function fileassistant_delete()
|
||||||
|
local path = luci.http.formvalue("path")
|
||||||
|
local isdir = luci.http.formvalue("isdir")
|
||||||
|
path = path:gsub("<>", "/")
|
||||||
|
path = path:gsub(" ", "\ ")
|
||||||
|
local success
|
||||||
|
if isdir then
|
||||||
|
success = os.execute('rm -r "'..path..'"')
|
||||||
|
else
|
||||||
|
success = os.remove(path)
|
||||||
|
end
|
||||||
|
list_response(nixio.fs.dirname(path), success)
|
||||||
|
end
|
||||||
|
|
||||||
|
function fileassistant_rename()
|
||||||
|
local filepath = luci.http.formvalue("filepath")
|
||||||
|
local newpath = luci.http.formvalue("newpath")
|
||||||
|
local success = os.execute('mv "'..filepath..'" "'..newpath..'"')
|
||||||
|
list_response(nixio.fs.dirname(filepath), success)
|
||||||
|
end
|
||||||
|
|
||||||
|
function fileassistant_install()
|
||||||
|
local filepath = luci.http.formvalue("filepath")
|
||||||
|
local isdir = luci.http.formvalue("isdir")
|
||||||
|
local ext = filepath:match(".+%.(%w+)$")
|
||||||
|
filepath = filepath:gsub("<>", "/")
|
||||||
|
filepath = filepath:gsub(" ", "\ ")
|
||||||
|
local success
|
||||||
|
if isdir == "1" then
|
||||||
|
success = false
|
||||||
|
elseif ext == "ipk" then
|
||||||
|
success = installIPK(filepath)
|
||||||
|
else
|
||||||
|
success = false
|
||||||
|
end
|
||||||
|
list_response(nixio.fs.dirname(filepath), success)
|
||||||
|
end
|
||||||
|
|
||||||
|
function installIPK(filepath)
|
||||||
|
luci.sys.exec('opkg --force-depends install "'..filepath..'"')
|
||||||
|
luci.sys.exec('rm -rf /tmp/luci-*')
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
|
||||||
|
function fileassistant_upload()
|
||||||
|
local fp
|
||||||
|
-- MUST setfilehandler before formvalue,
|
||||||
|
-- beacuse formvalue will parse form and write body to /tmp if filehandler not present
|
||||||
|
luci.http.setfilehandler(
|
||||||
|
function(meta, chunk, eof)
|
||||||
|
if not fp and meta and meta.name == "upload-file" then
|
||||||
|
local filename = luci.http.formvalue("upload-filename")
|
||||||
|
local uploaddir = luci.http.formvalue("upload-dir")
|
||||||
|
if not uploaddir or not filename then
|
||||||
|
error("uploaddir or filename is nil")
|
||||||
|
end
|
||||||
|
local filepath = uploaddir..filename
|
||||||
|
fp = io.open(filepath, "w")
|
||||||
|
end
|
||||||
|
if fp and chunk then
|
||||||
|
fp:write(chunk)
|
||||||
|
end
|
||||||
|
if fp and eof then
|
||||||
|
fp:close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
list_response(luci.http.formvalue("upload-dir"), true)
|
||||||
|
end
|
||||||
|
|
||||||
|
function fileassistant_mkdir()
|
||||||
|
local path = luci.http.formvalue("path")
|
||||||
|
local dirname = luci.http.formvalue("dirname")
|
||||||
|
local success = os.execute('sh -c \'cd "'..path..'" && mkdir -p "'..dirname..'"\'')
|
||||||
|
list_response(path, success)
|
||||||
|
end
|
||||||
|
|
||||||
|
function fileassistant_chmod()
|
||||||
|
local path = luci.http.formvalue("filepath")
|
||||||
|
local newmod = luci.http.formvalue("newmod")
|
||||||
|
local success = os.execute('chmod '..newmod..' "'..path..'"')
|
||||||
|
list_response(nixio.fs.dirname(path), success)
|
||||||
|
end
|
||||||
|
|
||||||
|
function fileassistant_chown()
|
||||||
|
local path = luci.http.formvalue("filepath")
|
||||||
|
local newown = luci.http.formvalue("newown")
|
||||||
|
local success = os.execute('chown '..newown..' "'..path..'"')
|
||||||
|
list_response(nixio.fs.dirname(path), success)
|
||||||
|
end
|
||||||
|
|
||||||
|
function scandir(directory)
|
||||||
|
local i, t, popen = 0, {}, io.popen
|
||||||
|
|
||||||
|
local pfile = popen("ls -lh \""..directory.."\" | egrep '^d' ; ls -lh \""..directory.."\" | egrep -v '^d|^l'")
|
||||||
|
for fileinfo in pfile:lines() do
|
||||||
|
i = i + 1
|
||||||
|
t[i] = fileinfo
|
||||||
|
end
|
||||||
|
pfile:close()
|
||||||
|
pfile = popen("ls -lh \""..directory.."\" | egrep '^l' ;")
|
||||||
|
for fileinfo in pfile:lines() do
|
||||||
|
i = i + 1
|
||||||
|
linkindex, _, linkpath = string.find(fileinfo, "->%s+(.+)$")
|
||||||
|
local finalpath;
|
||||||
|
if string.sub(linkpath, 1, 1) == "/" then
|
||||||
|
finalpath = linkpath
|
||||||
|
else
|
||||||
|
finalpath = nixio.fs.realpath(directory..linkpath)
|
||||||
|
end
|
||||||
|
local linktype;
|
||||||
|
if not finalpath then
|
||||||
|
finalpath = linkpath;
|
||||||
|
linktype = 'x'
|
||||||
|
elseif nixio.fs.stat(finalpath, "type") == "dir" then
|
||||||
|
linktype = 'z'
|
||||||
|
else
|
||||||
|
linktype = 'l'
|
||||||
|
end
|
||||||
|
fileinfo = string.sub(fileinfo, 2, linkindex - 1)
|
||||||
|
fileinfo = linktype..fileinfo.."-> "..finalpath
|
||||||
|
t[i] = fileinfo
|
||||||
|
end
|
||||||
|
pfile:close()
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
MIME_TYPES = {
|
||||||
|
["txt"] = "text/plain";
|
||||||
|
["conf"] = "text/plain";
|
||||||
|
["ovpn"] = "text/plain";
|
||||||
|
["log"] = "text/plain";
|
||||||
|
["js"] = "text/javascript";
|
||||||
|
["json"] = "application/json";
|
||||||
|
["css"] = "text/css";
|
||||||
|
["htm"] = "text/html";
|
||||||
|
["html"] = "text/html";
|
||||||
|
["patch"] = "text/x-patch";
|
||||||
|
["c"] = "text/x-csrc";
|
||||||
|
["h"] = "text/x-chdr";
|
||||||
|
["o"] = "text/x-object";
|
||||||
|
["ko"] = "text/x-object";
|
||||||
|
|
||||||
|
["bmp"] = "image/bmp";
|
||||||
|
["gif"] = "image/gif";
|
||||||
|
["png"] = "image/png";
|
||||||
|
["jpg"] = "image/jpeg";
|
||||||
|
["jpeg"] = "image/jpeg";
|
||||||
|
["svg"] = "image/svg+xml";
|
||||||
|
|
||||||
|
["zip"] = "application/zip";
|
||||||
|
["pdf"] = "application/pdf";
|
||||||
|
["xml"] = "application/xml";
|
||||||
|
["xsl"] = "application/xml";
|
||||||
|
["doc"] = "application/msword";
|
||||||
|
["ppt"] = "application/vnd.ms-powerpoint";
|
||||||
|
["xls"] = "application/vnd.ms-excel";
|
||||||
|
["odt"] = "application/vnd.oasis.opendocument.text";
|
||||||
|
["odp"] = "application/vnd.oasis.opendocument.presentation";
|
||||||
|
["pl"] = "application/x-perl";
|
||||||
|
["sh"] = "application/x-shellscript";
|
||||||
|
["php"] = "application/x-php";
|
||||||
|
["deb"] = "application/x-deb";
|
||||||
|
["iso"] = "application/x-cd-image";
|
||||||
|
["tgz"] = "application/x-compressed-tar";
|
||||||
|
|
||||||
|
["mp3"] = "audio/mpeg";
|
||||||
|
["ogg"] = "audio/x-vorbis+ogg";
|
||||||
|
["wav"] = "audio/x-wav";
|
||||||
|
|
||||||
|
["mpg"] = "video/mpeg";
|
||||||
|
["mpeg"] = "video/mpeg";
|
||||||
|
["avi"] = "video/x-msvideo";
|
||||||
|
}
|
||||||
|
|
||||||
|
function to_mime(filename)
|
||||||
|
if type(filename) == "string" then
|
||||||
|
local ext = filename:match("[^%.]+$")
|
||||||
|
|
||||||
|
if ext and MIME_TYPES[ext:lower()] then
|
||||||
|
return MIME_TYPES[ext:lower()]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return "application/octet-stream"
|
||||||
|
end
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<%+header%>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/luci-static/resources/fileassistant/fb.css<%# ?v=PKG_VERSION %>">
|
||||||
|
<h2 name="content">文件助手</h2>
|
||||||
|
<fieldset class="cbi-section fb-container">
|
||||||
|
<input id="current-path" type="text" class="current-path cbi-input-text" value="/"/>
|
||||||
|
<div class="panel-container">
|
||||||
|
<div class="panel-title">文件列表</div>
|
||||||
|
<button id="upload-toggle" class="upload-toggle cbi-button cbi-button-edit">上传</button>
|
||||||
|
<button id="mkdir-toggle" class="upload-toggle cbi-button cbi-button-edit">新建文件夹… </button>
|
||||||
|
</div>
|
||||||
|
<div class="upload-container" id="upload-container">
|
||||||
|
<input id="upload-file" name="upload-file" class="upload-file" type="file">
|
||||||
|
<button type="button" class="cbi-button cbi-input-apply">点我上传</button>
|
||||||
|
</div>
|
||||||
|
<div id="list-content"></div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<script src="/luci-static/resources/fileassistant/fb.js<%# ?v=PKG_VERSION %>"></script>
|
||||||
|
|
||||||
|
<%+footer%>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Copyright (C) 2018-2020 Lienol <lawlienol@gmail.com>
|
||||||
|
#
|
||||||
|
# This is free software, licensed under the Apache License, Version 2.0 .
|
||||||
|
#
|
||||||
|
|
||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
|
LUCI_TITLE:=LuCI support for FileBrowser
|
||||||
|
LUCI_PKGARCH:=all
|
||||||
|
PKG_VERSION:=1.1
|
||||||
|
PKG_RELEASE:=2
|
||||||
|
|
||||||
|
include $(TOPDIR)/feeds/luci/luci.mk
|
||||||
|
|
||||||
|
# call BuildPackage - OpenWrt buildroot signature
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
-- Copyright 2018-2020 Lienol <lawlienol@gmail.com>
|
||||||
|
module("luci.controller.filebrowser", package.seeall)
|
||||||
|
|
||||||
|
local http = require "luci.http"
|
||||||
|
local api = require "luci.model.cbi.filebrowser.api"
|
||||||
|
|
||||||
|
function index()
|
||||||
|
if not nixio.fs.access("/etc/config/filebrowser") then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
entry({"admin", "nas"}, firstchild(), "NAS", 44).dependent = false
|
||||||
|
entry({"admin", "nas", "filebrowser"}, cbi("filebrowser/settings"), _("File Browser"), 2).dependent = true
|
||||||
|
|
||||||
|
entry({"admin", "nas", "filebrowser", "check"}, call("action_check")).leaf = true
|
||||||
|
entry({"admin", "nas", "filebrowser", "download"}, call("action_download")).leaf = true
|
||||||
|
entry({"admin", "nas", "filebrowser", "status"}, call("act_status")).leaf = true
|
||||||
|
entry({"admin", "nas", "filebrowser", "get_log"}, call("get_log")).leaf = true
|
||||||
|
entry({"admin", "nas", "filebrowser", "clear_log"}, call("clear_log")).leaf = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function http_write_json(content)
|
||||||
|
http.prepare_content("application/json")
|
||||||
|
http.write_json(content or {
|
||||||
|
code = 1
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function act_status()
|
||||||
|
local e = {}
|
||||||
|
e.status = luci.sys.call("busybox ps -w | grep -v grep | grep 'filebrowser -a 0.0.0.0' >/dev/null") == 0
|
||||||
|
http_write_json(e)
|
||||||
|
end
|
||||||
|
|
||||||
|
function action_check()
|
||||||
|
local json = api.to_check()
|
||||||
|
http_write_json(json)
|
||||||
|
end
|
||||||
|
|
||||||
|
function action_download()
|
||||||
|
local json = nil
|
||||||
|
local task = http.formvalue("task")
|
||||||
|
if task == "extract" then
|
||||||
|
json = api.to_extract(http.formvalue("file"))
|
||||||
|
elseif task == "move" then
|
||||||
|
json = api.to_move(http.formvalue("file"))
|
||||||
|
else
|
||||||
|
json = api.to_download(http.formvalue("url"))
|
||||||
|
end
|
||||||
|
http_write_json(json)
|
||||||
|
end
|
||||||
|
|
||||||
|
function get_log()
|
||||||
|
luci.http.write(luci.sys.exec("[ -f '/var/log/filebrowser.log' ] && cat /var/log/filebrowser.log"))
|
||||||
|
end
|
||||||
|
|
||||||
|
function clear_log()
|
||||||
|
luci.sys.call("echo '' > /var/log/filebrowser.log")
|
||||||
|
end
|
||||||
@@ -0,0 +1,364 @@
|
|||||||
|
local fs = require "nixio.fs"
|
||||||
|
local sys = require "luci.sys"
|
||||||
|
local uci = require"luci.model.uci".cursor()
|
||||||
|
local util = require "luci.util"
|
||||||
|
local i18n = require "luci.i18n"
|
||||||
|
|
||||||
|
module("luci.model.cbi.filebrowser.api", package.seeall)
|
||||||
|
|
||||||
|
local appname = "filebrowser"
|
||||||
|
local api_url = "https://api.github.com/repos/filebrowser/filebrowser/releases/latest"
|
||||||
|
|
||||||
|
local wget = "/usr/bin/wget"
|
||||||
|
local wget_args = {"--no-check-certificate", "--quiet", "--timeout=10", "--tries=2"}
|
||||||
|
local command_timeout = 300
|
||||||
|
|
||||||
|
local LEDE_BOARD = nil
|
||||||
|
local DISTRIB_TARGET = nil
|
||||||
|
|
||||||
|
function uci_get_type(type, config, default)
|
||||||
|
value = uci:get_first(appname, type, config, default) or
|
||||||
|
sys.exec("echo -n `uci -q get " .. appname .. ".@" .. type .. "[0]." .. config .. "`")
|
||||||
|
if (value == nil or value == "") and (default and default ~= "") then
|
||||||
|
value = default
|
||||||
|
end
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _unpack(t, i)
|
||||||
|
i = i or 1
|
||||||
|
if t[i] ~= nil then
|
||||||
|
return t[i], _unpack(t, i + 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function exec(cmd, args, writer, timeout)
|
||||||
|
local os = require "os"
|
||||||
|
local nixio = require "nixio"
|
||||||
|
|
||||||
|
local fdi, fdo = nixio.pipe()
|
||||||
|
local pid = nixio.fork()
|
||||||
|
|
||||||
|
if pid > 0 then
|
||||||
|
fdo:close()
|
||||||
|
|
||||||
|
if writer or timeout then
|
||||||
|
local starttime = os.time()
|
||||||
|
while true do
|
||||||
|
if timeout and os.difftime(os.time(), starttime) >= timeout then
|
||||||
|
nixio.kill(pid, nixio.const.SIGTERM)
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if writer then
|
||||||
|
local buffer = fdi:read(2048)
|
||||||
|
if buffer and #buffer > 0 then
|
||||||
|
writer(buffer)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local wpid, stat, code = nixio.waitpid(pid, "nohang")
|
||||||
|
|
||||||
|
if wpid and stat == "exited" then
|
||||||
|
return code
|
||||||
|
end
|
||||||
|
|
||||||
|
if not writer and timeout then
|
||||||
|
nixio.nanosleep(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local wpid, stat, code = nixio.waitpid(pid)
|
||||||
|
return wpid and stat == "exited" and code
|
||||||
|
end
|
||||||
|
elseif pid == 0 then
|
||||||
|
nixio.dup(fdo, nixio.stdout)
|
||||||
|
fdi:close()
|
||||||
|
fdo:close()
|
||||||
|
nixio.exece(cmd, args, nil)
|
||||||
|
nixio.stdout:close()
|
||||||
|
os.exit(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function compare_versions(ver1, comp, ver2)
|
||||||
|
local table = table
|
||||||
|
|
||||||
|
local av1 = util.split(ver1, "[%.%-]", nil, true)
|
||||||
|
local av2 = util.split(ver2, "[%.%-]", nil, true)
|
||||||
|
|
||||||
|
local max = table.getn(av1)
|
||||||
|
local n2 = table.getn(av2)
|
||||||
|
if (max < n2) then
|
||||||
|
max = n2
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, max, 1 do
|
||||||
|
local s1 = av1[i] or ""
|
||||||
|
local s2 = av2[i] or ""
|
||||||
|
|
||||||
|
if comp == "~=" and (s1 ~= s2) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if (comp == "<" or comp == "<=") and (s1 < s2) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if (comp == ">" or comp == ">=") and (s1 > s2) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if (s1 ~= s2) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return not (comp == "<" or comp == ">")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function auto_get_arch()
|
||||||
|
local arch = nixio.uname().machine or ""
|
||||||
|
if fs.access("/usr/lib/os-release") then
|
||||||
|
LEDE_BOARD = sys.exec("echo -n `grep 'LEDE_BOARD' /usr/lib/os-release | awk -F '[\\042\\047]' '{print $2}'`")
|
||||||
|
end
|
||||||
|
if fs.access("/etc/openwrt_release") then
|
||||||
|
DISTRIB_TARGET = sys.exec("echo -n `grep 'DISTRIB_TARGET' /etc/openwrt_release | awk -F '[\\042\\047]' '{print $2}'`")
|
||||||
|
end
|
||||||
|
|
||||||
|
if arch == "mips" then
|
||||||
|
if LEDE_BOARD and LEDE_BOARD ~= "" then
|
||||||
|
if string.match(LEDE_BOARD, "ramips") == "ramips" then
|
||||||
|
arch = "ramips"
|
||||||
|
else
|
||||||
|
arch = sys.exec("echo '" .. LEDE_BOARD .. "' | grep -oE 'ramips|ar71xx'")
|
||||||
|
end
|
||||||
|
elseif DISTRIB_TARGET and DISTRIB_TARGET ~= "" then
|
||||||
|
if string.match(DISTRIB_TARGET, "ramips") == "ramips" then
|
||||||
|
arch = "ramips"
|
||||||
|
else
|
||||||
|
arch = sys.exec("echo '" .. DISTRIB_TARGET .. "' | grep -oE 'ramips|ar71xx'")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return util.trim(arch)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_file_info(arch)
|
||||||
|
local file_tree = ""
|
||||||
|
local sub_version = ""
|
||||||
|
|
||||||
|
if arch == "x86_64" then
|
||||||
|
file_tree = "amd64"
|
||||||
|
elseif arch == "aarch64" then
|
||||||
|
file_tree = "arm64"
|
||||||
|
elseif arch == "ramips" then
|
||||||
|
file_tree = "mipsle"
|
||||||
|
elseif arch == "ar71xx" then
|
||||||
|
file_tree = "mips"
|
||||||
|
elseif arch:match("^i[%d]86$") then
|
||||||
|
file_tree = "386"
|
||||||
|
elseif arch:match("^armv[5-8]") then
|
||||||
|
file_tree = "armv"
|
||||||
|
sub_version = arch:match("[5-8]")
|
||||||
|
if LEDE_BOARD and string.match(LEDE_BOARD, "bcm53xx") == "bcm53xx" then
|
||||||
|
sub_version = "5"
|
||||||
|
elseif DISTRIB_TARGET and string.match(DISTRIB_TARGET, "bcm53xx") == "bcm53xx" then
|
||||||
|
sub_version = "5"
|
||||||
|
end
|
||||||
|
sub_version = "5"
|
||||||
|
end
|
||||||
|
|
||||||
|
return file_tree, sub_version
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_api_json(url)
|
||||||
|
local jsonc = require "luci.jsonc"
|
||||||
|
|
||||||
|
local output = {}
|
||||||
|
-- exec(wget, { "-O-", url, _unpack(wget_args) },
|
||||||
|
-- function(chunk) output[#output + 1] = chunk end)
|
||||||
|
-- local json_content = util.trim(table.concat(output))
|
||||||
|
|
||||||
|
local json_content = luci.sys.exec(wget .. " --no-check-certificate --timeout=10 -t 1 -O- " .. url)
|
||||||
|
|
||||||
|
if json_content == "" then
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
return jsonc.parse(json_content) or {}
|
||||||
|
end
|
||||||
|
|
||||||
|
function get_version()
|
||||||
|
return uci_get_type("global", "version", "0")
|
||||||
|
end
|
||||||
|
|
||||||
|
function to_check(arch)
|
||||||
|
if not arch or arch == "" then
|
||||||
|
arch = auto_get_arch()
|
||||||
|
end
|
||||||
|
|
||||||
|
local file_tree, sub_version = get_file_info(arch)
|
||||||
|
|
||||||
|
if file_tree == "" then
|
||||||
|
return {
|
||||||
|
code = 1,
|
||||||
|
error = i18n.translate("Can't determine ARCH, or ARCH not supported.")
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local json = get_api_json(api_url)
|
||||||
|
|
||||||
|
if json.tag_name == nil then
|
||||||
|
return {
|
||||||
|
code = 1,
|
||||||
|
error = i18n.translate("Get remote version info failed.")
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local remote_version = json.tag_name:match("[^v]+")
|
||||||
|
|
||||||
|
local needs_update = compare_versions(get_version(), "<", remote_version)
|
||||||
|
local html_url, download_url
|
||||||
|
|
||||||
|
if needs_update then
|
||||||
|
html_url = json.html_url
|
||||||
|
for _, v in ipairs(json.assets) do
|
||||||
|
if v.name and v.name:match("linux%-" .. file_tree .. sub_version) then
|
||||||
|
download_url = v.browser_download_url
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if needs_update and not download_url then
|
||||||
|
return {
|
||||||
|
code = 1,
|
||||||
|
version = remote_version,
|
||||||
|
html_url = html_url,
|
||||||
|
error = i18n.translate("New version found, but failed to get new version download url.")
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
code = 0,
|
||||||
|
update = needs_update,
|
||||||
|
version = remote_version,
|
||||||
|
url = {
|
||||||
|
html = html_url,
|
||||||
|
download = download_url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function to_download(url)
|
||||||
|
if not url or url == "" then
|
||||||
|
return {
|
||||||
|
code = 1,
|
||||||
|
error = i18n.translate("Download url is required.")
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
sys.call("/bin/rm -f /tmp/filebrowser_download.*")
|
||||||
|
|
||||||
|
local tmp_file = util.trim(util.exec("mktemp -u -t filebrowser_download.XXXXXX"))
|
||||||
|
|
||||||
|
local result = exec(wget, {"-O", tmp_file, url, _unpack(wget_args)}, nil, command_timeout) == 0
|
||||||
|
|
||||||
|
if not result then
|
||||||
|
exec("/bin/rm", {"-f", tmp_file})
|
||||||
|
return {
|
||||||
|
code = 1,
|
||||||
|
error = i18n.translatef("File download failed or timed out: %s", url)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
code = 0,
|
||||||
|
file = tmp_file
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function to_extract(file, subfix)
|
||||||
|
if not file or file == "" or not fs.access(file) then
|
||||||
|
return {
|
||||||
|
code = 1,
|
||||||
|
error = i18n.translate("File path required.")
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
sys.call("/bin/rm -rf /tmp/filebrowser_extract.*")
|
||||||
|
local tmp_dir = util.trim(util.exec("mktemp -d -t filebrowser_extract.XXXXXX"))
|
||||||
|
|
||||||
|
local output = {}
|
||||||
|
exec("/bin/tar", {"-C", tmp_dir, "-zxvf", file}, function(chunk)
|
||||||
|
output[#output + 1] = chunk
|
||||||
|
end)
|
||||||
|
|
||||||
|
local files = util.split(table.concat(output))
|
||||||
|
|
||||||
|
exec("/bin/rm", {"-f", file})
|
||||||
|
|
||||||
|
if not new_file then
|
||||||
|
for _, f in pairs(files) do
|
||||||
|
if f:match("filebrowser") then
|
||||||
|
new_file = tmp_dir .. "/" .. util.trim(f)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not new_file then
|
||||||
|
exec("/bin/rm", {"-rf", tmp_dir})
|
||||||
|
return {
|
||||||
|
code = 1,
|
||||||
|
error = i18n.translatef("Can't find client in file: %s", file)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
code = 0,
|
||||||
|
file = new_file
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function to_move(file)
|
||||||
|
if not file or file == "" or not fs.access(file) then
|
||||||
|
sys.call("/bin/rm -rf /tmp/filebrowser_extract.*")
|
||||||
|
return {
|
||||||
|
code = 1,
|
||||||
|
error = i18n.translate("Client file is required.")
|
||||||
|
}
|
||||||
|
end
|
||||||
|
local project_directory = uci_get_type("global", "project_directory", "/tmp")
|
||||||
|
luci.sys.exec("mkdir -p " .. project_directory)
|
||||||
|
local client_path = project_directory .. "/" .. appname
|
||||||
|
local client_path_bak
|
||||||
|
|
||||||
|
if fs.access(client_path) then
|
||||||
|
client_path_bak = "/tmp/" .. appname .. ".bak"
|
||||||
|
exec("/bin/mv", {"-f", client_path, client_path_bak})
|
||||||
|
end
|
||||||
|
|
||||||
|
local result = exec("/bin/mv", {"-f", file, client_path}, nil, command_timeout) == 0
|
||||||
|
|
||||||
|
if not result or not fs.access(client_path) then
|
||||||
|
if client_path_bak then
|
||||||
|
exec("/bin/mv", {"-f", client_path_bak, client_path})
|
||||||
|
end
|
||||||
|
return {
|
||||||
|
code = 1,
|
||||||
|
error = i18n.translatef("Can't move new file to path: %s", client_path)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
exec("/bin/chmod", {"755", client_path})
|
||||||
|
|
||||||
|
if client_path_bak then
|
||||||
|
exec("/bin/rm", {"-f", client_path_bak})
|
||||||
|
end
|
||||||
|
|
||||||
|
sys.call("/bin/rm -rf /tmp/filebrowser_extract.*")
|
||||||
|
|
||||||
|
return {
|
||||||
|
code = 0
|
||||||
|
}
|
||||||
|
end
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
m = Map("filebrowser", translate("FileBrowser"), translate(
|
||||||
|
"File explorer is software that creates your own cloud that you can install on a server, point it to a path, and then access your files through a beautiful web interface. You have many features available!"))
|
||||||
|
m:append(Template("filebrowser/status"))
|
||||||
|
|
||||||
|
s = m:section(TypedSection, "global", translate("Global Settings"))
|
||||||
|
s.anonymous = true
|
||||||
|
s.addremove = false
|
||||||
|
|
||||||
|
o = s:option(Flag, "enable", translate("Enable"))
|
||||||
|
o.rmempty = false
|
||||||
|
|
||||||
|
o = s:option(Value, "port", translate("Listen port"))
|
||||||
|
o.datatype = "port"
|
||||||
|
o.default = 8088
|
||||||
|
o.rmempty = false
|
||||||
|
|
||||||
|
o = s:option(Value, "root_path", translate("Root path"),
|
||||||
|
translate("Point to a path to access your files in the web interface, default is /"))
|
||||||
|
o.default = "/"
|
||||||
|
o.rmempty = false
|
||||||
|
|
||||||
|
o = s:option(Value, "project_directory", translate("Project directory"), translate(
|
||||||
|
"The file size is large, requiring at least 32M space. It is recommended to insert a usb flash drive or hard disk, or use it in the tmp directory<br />For example, /mnt/sda1<br />For example, /tmp"))
|
||||||
|
o.default = "/tmp"
|
||||||
|
o.rmempty = false
|
||||||
|
|
||||||
|
o = s:option(Button, "_download", translate("Manually download"), translate(
|
||||||
|
"Make sure you have enough space. <br /><font style='color:red'>Be sure to fill out the project storage directory the first time you run it, and then save the application. Then manually download, otherwise can not use!</font>"))
|
||||||
|
o.template = "filebrowser/download"
|
||||||
|
o.inputstyle = "apply"
|
||||||
|
o.btnclick = "downloadClick(this);"
|
||||||
|
o.id = "download_btn"
|
||||||
|
|
||||||
|
m:append(Template("filebrowser/log"))
|
||||||
|
|
||||||
|
return m
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
<%
|
||||||
|
local dsp = require "luci.dispatcher"
|
||||||
|
-%>
|
||||||
|
|
||||||
|
<script type="text/javascript">//<![CDATA[
|
||||||
|
var msgInfo;
|
||||||
|
|
||||||
|
var tokenStr = '<%=token%>';
|
||||||
|
var clickToDownloadText = '<%:Click to download%>';
|
||||||
|
var inProgressText = '<%:Downloading...%>';
|
||||||
|
var downloadInProgressNotice = '<%:Download, are you sure to close?%>';
|
||||||
|
var downloadSuccessText = '<%:Download successful%>';
|
||||||
|
var unexpectedErrorText = '<%:Unexpected error%>';
|
||||||
|
|
||||||
|
function addPageNotice() {
|
||||||
|
window.onbeforeunload = function(e) {
|
||||||
|
e.returnValue = downloadInProgressNotice;
|
||||||
|
return downloadInProgressNotice;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function removePageNotice() {
|
||||||
|
window.onbeforeunload = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onUpdateSuccess(btn) {
|
||||||
|
alert(downloadSuccessText);
|
||||||
|
|
||||||
|
if (btn) {
|
||||||
|
btn.value = downloadSuccessText;
|
||||||
|
btn.placeholder = downloadSuccessText;
|
||||||
|
btn.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.setTimeout(function () {
|
||||||
|
window.location.reload();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRequestError(btn, errorMessage) {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.value = btn.placeholder;
|
||||||
|
|
||||||
|
if (errorMessage) {
|
||||||
|
alert(errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function doAjaxGet(url, data, onResult) {
|
||||||
|
new XHR().get(url, data, function(_, json) {
|
||||||
|
var resultJson = json || {
|
||||||
|
'code': 1,
|
||||||
|
'error': unexpectedErrorText
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof onResult === 'function') {
|
||||||
|
onResult(resultJson);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadClick(btn) {
|
||||||
|
if (msgInfo === undefined) {
|
||||||
|
checkUpdate(btn);
|
||||||
|
} else {
|
||||||
|
doDownload(btn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkUpdate(btn) {
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.value = inProgressText;
|
||||||
|
|
||||||
|
addPageNotice();
|
||||||
|
|
||||||
|
var ckeckDetailElm = document.getElementById(btn.id + '-detail');
|
||||||
|
|
||||||
|
doAjaxGet('<%=dsp.build_url("admin/nas/filebrowser/check")%>/', {
|
||||||
|
token: tokenStr
|
||||||
|
}, function (json) {
|
||||||
|
removePageNotice();
|
||||||
|
if (json.code) {
|
||||||
|
eval('Info = undefined');
|
||||||
|
onRequestError(btn, json.error);
|
||||||
|
} else {
|
||||||
|
eval('Info = json');
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.value = clickToDownloadText;
|
||||||
|
btn.placeholder = clickToDownloadText;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ckeckDetailElm) {
|
||||||
|
var urlNode = '';
|
||||||
|
if (json.version) {
|
||||||
|
urlNode = '<em style="color:red;"><%:The latest version:%>' + json.version + '</em>';
|
||||||
|
if (json.url && json.url.html) {
|
||||||
|
urlNode = '<a href="' + json.url.html + '" target="_blank">' + urlNode + '</a>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ckeckDetailElm.innerHTML = urlNode;
|
||||||
|
}
|
||||||
|
msgInfo = json;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function doDownload(btn) {
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.value = '<%:Downloading...%>';
|
||||||
|
|
||||||
|
addPageNotice();
|
||||||
|
|
||||||
|
var UpdateUrl = '<%=dsp.build_url("admin/nas/filebrowser/download")%>';
|
||||||
|
// Download file
|
||||||
|
doAjaxGet(UpdateUrl, {
|
||||||
|
token: tokenStr,
|
||||||
|
url: msgInfo ? msgInfo.url.download : ''
|
||||||
|
}, function (json) {
|
||||||
|
if (json.code) {
|
||||||
|
removePageNotice();
|
||||||
|
onRequestError(btn, json.error);
|
||||||
|
} else {
|
||||||
|
btn.value = '<%:Unpacking...%>';
|
||||||
|
|
||||||
|
// Extract file
|
||||||
|
doAjaxGet(UpdateUrl, {
|
||||||
|
token: tokenStr,
|
||||||
|
task: 'extract',
|
||||||
|
file: json.file
|
||||||
|
}, function (json) {
|
||||||
|
if (json.code) {
|
||||||
|
removePageNotice();
|
||||||
|
onRequestError(btn, json.error);
|
||||||
|
} else {
|
||||||
|
btn.value = '<%:Moving...%>';
|
||||||
|
|
||||||
|
// Move file to target dir
|
||||||
|
doAjaxGet(UpdateUrl, {
|
||||||
|
token: tokenStr,
|
||||||
|
task: 'move',
|
||||||
|
file: json.file
|
||||||
|
}, function (json) {
|
||||||
|
removePageNotice();
|
||||||
|
if (json.code) {
|
||||||
|
onRequestError(btn, json.error);
|
||||||
|
} else {
|
||||||
|
onUpdateSuccess(btn);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
//]]></script>
|
||||||
|
|
||||||
|
<%+cbi/valueheader%>
|
||||||
|
<% if self:cfgvalue(section) ~= false then %>
|
||||||
|
<input class="cbi-button cbi-input-<%=self.inputstyle or "button" %>" type="button"<%=
|
||||||
|
attr("name", cbid) ..
|
||||||
|
attr("id", self.id or cbid) ..
|
||||||
|
attr("value", self.inputtitle or self.title) ..
|
||||||
|
ifattr(self.btnclick, "onclick", self.btnclick) ..
|
||||||
|
ifattr(self.placeholder, "placeholder")
|
||||||
|
%> />
|
||||||
|
<span id="<%=self.id or cbid%>-detail"></span>
|
||||||
|
<% else %>
|
||||||
|
-
|
||||||
|
<% end %>
|
||||||
|
<%+cbi/valuefooter%>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<script type="text/javascript">
|
||||||
|
//<![CDATA[
|
||||||
|
function clear_log(btn) {
|
||||||
|
XHR.get('<%=url([[admin]], [[nas]], [[filebrowser]], [[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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
XHR.poll(3, '<%=url([[admin]], [[nas]], [[filebrowser]], [[get_log]])%>', null,
|
||||||
|
function(x, data) {
|
||||||
|
if(x && x.status == 200) {
|
||||||
|
var log_textarea = document.getElementById('log_textarea');
|
||||||
|
log_textarea.innerHTML = x.responseText;
|
||||||
|
log_textarea.scrollTop = log_textarea.scrollHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
//]]>
|
||||||
|
</script>
|
||||||
|
<fieldset class="cbi-section" id="_log_fieldset">
|
||||||
|
<legend>
|
||||||
|
<%:Logs%>
|
||||||
|
</legend>
|
||||||
|
<input class="cbi-button cbi-input-remove" type="button" onclick="clear_log()" value="<%:Clear logs%>">
|
||||||
|
<textarea id="log_textarea" class="cbi-input-textarea" style="width: 100%;margin-top: 10px;" data-update="change" rows="5" wrap="off" readonly="readonly"></textarea>
|
||||||
|
</fieldset>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<%
|
||||||
|
local dsp = require "luci.dispatcher"
|
||||||
|
-%>
|
||||||
|
|
||||||
|
<fieldset class="cbi-section">
|
||||||
|
<legend><%:Running Status%></legend>
|
||||||
|
<fieldset class="cbi-section">
|
||||||
|
<div class="cbi-value">
|
||||||
|
<label class="cbi-value-title"><%:Status%></label>
|
||||||
|
<div class="cbi-value-field" id="_status"><%:Collecting data...%></div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<script type="text/javascript">//<![CDATA[
|
||||||
|
var _status = document.getElementById('_status');
|
||||||
|
XHR.poll(3,'<%=dsp.build_url("admin/nas/filebrowser/status")%>', null,
|
||||||
|
function(x, json) {
|
||||||
|
if (x && x.status == 200) {
|
||||||
|
if (_status)
|
||||||
|
_status.innerHTML = json.status ? '<font color="green"><%:RUNNING%> ✓</font><p><input type="button" class="cbi-button cbi-input-apply" value="<%:Enter interface%>" onclick="openwebui()" /></p>' : '<font color="red"><%:NOT RUNNING%> X</font>';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function openwebui(){
|
||||||
|
var url = window.location.host+":<%=luci.sys.exec("uci get filebrowser.@global[0].port"):gsub("^%s*(.-)%s*$", "%1")%>";
|
||||||
|
window.open('http://'+url,'target','');
|
||||||
|
}
|
||||||
|
//]]></script>
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
msgid "File Browser"
|
||||||
|
msgstr "文件浏览器"
|
||||||
|
|
||||||
|
msgid "File explorer is software that creates your own cloud that you can install on a server, point it to a path, and then access your files through a beautiful web interface. You have many features available!"
|
||||||
|
msgstr "文件浏览器是一种创建你自己的云的软件,你可以在服务器上安装它,将它指向一个路径,然后通过一个漂亮的web界面访问你的文件。您有许多可用的特性!"
|
||||||
|
|
||||||
|
msgid "RUNNING"
|
||||||
|
msgstr "运行中"
|
||||||
|
|
||||||
|
msgid "NOT RUNNING"
|
||||||
|
msgstr "未运行"
|
||||||
|
|
||||||
|
msgid "Enter interface"
|
||||||
|
msgstr "进入界面"
|
||||||
|
|
||||||
|
msgid "Global Settings"
|
||||||
|
msgstr "全局设置"
|
||||||
|
|
||||||
|
msgid "Enable"
|
||||||
|
msgstr "启用"
|
||||||
|
|
||||||
|
msgid "Listen port"
|
||||||
|
msgstr "监听端口"
|
||||||
|
|
||||||
|
msgid "Root path"
|
||||||
|
msgstr "指向路径"
|
||||||
|
|
||||||
|
msgid "Point to a path to access your files in the web interface, default is /"
|
||||||
|
msgstr "指向一个路径,可在web界面访问你的文件,默认为 /"
|
||||||
|
|
||||||
|
msgid "Project directory"
|
||||||
|
msgstr "项目存放目录"
|
||||||
|
|
||||||
|
msgid "The file size is large, requiring at least 32M space. It is recommended to insert a usb flash drive or hard disk, or use it in the tmp directory<br />For example, /mnt/sda1<br />For example, /tmp"
|
||||||
|
msgstr "文件较大,至少需要32M空间。建议插入U盘或硬盘,或放入tmp目录里使用<br />例如:/mnt/sda1<br />例如:/tmp"
|
||||||
|
|
||||||
|
msgid "Manually download"
|
||||||
|
msgstr "手动下载"
|
||||||
|
|
||||||
|
msgid "Make sure you have enough space. <br /><font style='color:red'>Be sure to fill out the project storage directory the first time you run it, and then save the application. Then manually download, otherwise can not use!</font>"
|
||||||
|
msgstr "请确保具有足够的空间。<br /><font style='color:red'>第一次运行务必填好项目存放目录,然后保存应用。再手动下载,否则无法使用!</font>"
|
||||||
|
|
||||||
|
msgid "Logs"
|
||||||
|
msgstr "日志"
|
||||||
|
|
||||||
|
msgid "Clear logs"
|
||||||
|
msgstr "清空日志"
|
||||||
|
|
||||||
|
msgid "It is the latest version"
|
||||||
|
msgstr "已是最新版本"
|
||||||
|
|
||||||
|
msgid "Download successful"
|
||||||
|
msgstr "下载成功"
|
||||||
|
|
||||||
|
msgid "Click to download"
|
||||||
|
msgstr "点击下载"
|
||||||
|
|
||||||
|
msgid "Updating..."
|
||||||
|
msgstr "更新中"
|
||||||
|
|
||||||
|
msgid "Unexpected error"
|
||||||
|
msgstr "意外错误"
|
||||||
|
|
||||||
|
msgid "Download, are you sure to close?"
|
||||||
|
msgstr "正在下载,你确认要关闭吗?"
|
||||||
|
|
||||||
|
msgid "Downloading..."
|
||||||
|
msgstr "下载中"
|
||||||
|
|
||||||
|
msgid "Unpacking..."
|
||||||
|
msgstr "解压中"
|
||||||
|
|
||||||
|
msgid "Moving..."
|
||||||
|
msgstr "移动中"
|
||||||
|
|
||||||
|
msgid "The latest version:"
|
||||||
|
msgstr "最新版本:"
|
||||||
|
|
||||||
|
msgid "Can't determine ARCH, or ARCH not supported."
|
||||||
|
msgstr "无法确认ARCH架构,或是不支持。"
|
||||||
|
|
||||||
|
msgid "Get remote version info failed."
|
||||||
|
msgstr "获取远程版本信息失败。"
|
||||||
|
|
||||||
|
msgid "New version found, but failed to get new version download url."
|
||||||
|
msgstr "发现新版本,但未能获得新版本的下载地址。"
|
||||||
|
|
||||||
|
msgid "Download url is required."
|
||||||
|
msgstr "请指定下载地址。"
|
||||||
|
|
||||||
|
msgid "File download failed or timed out: %s"
|
||||||
|
msgstr "文件下载失败或超时:%s"
|
||||||
|
|
||||||
|
msgid "File path required."
|
||||||
|
msgstr "请指定文件路径。"
|
||||||
|
|
||||||
|
msgid "Can't find client in file: %s"
|
||||||
|
msgstr "无法在文件中找到客户端:%s"
|
||||||
|
|
||||||
|
msgid "Client file is required."
|
||||||
|
msgstr "请指定客户端文件。"
|
||||||
|
|
||||||
|
msgid "The client file is not suitable for current device."
|
||||||
|
msgstr "客户端文件不适合当前设备。"
|
||||||
|
|
||||||
|
msgid "Can't move new file to path: %s"
|
||||||
|
msgstr "无法移动新文件到:%s"
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
zh-cn
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
config global
|
||||||
|
option port '8088'
|
||||||
|
option root_path '/'
|
||||||
|
option project_directory '/tmp'
|
||||||
|
option enable '0'
|
||||||
|
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
#!/bin/sh /etc/rc.common
|
||||||
|
# Copyright (C) 2018-2020 Lienol <lawlienol@gmail.com>
|
||||||
|
|
||||||
|
START=99
|
||||||
|
|
||||||
|
LOG_PATH="/var/log/filebrowser.log"
|
||||||
|
|
||||||
|
echolog() {
|
||||||
|
echo -e "$(date "+%Y-%m-%d %H:%M:%S"): $1" >> $LOG_PATH
|
||||||
|
}
|
||||||
|
|
||||||
|
config_t_get() {
|
||||||
|
local index=0
|
||||||
|
[ -n "$4" ] && index=$4
|
||||||
|
local ret=$(uci get filebrowser.@$1[$index].$2 2>/dev/null)
|
||||||
|
echo ${ret:=$3}
|
||||||
|
}
|
||||||
|
start() {
|
||||||
|
ENABLED=$(config_t_get global enable 0)
|
||||||
|
[ "$ENABLED" = "0" ] && return
|
||||||
|
PORT=$(config_t_get global port 8088)
|
||||||
|
ROOT_PATH=$(config_t_get global root_path /)
|
||||||
|
project_directory=$(config_t_get global project_directory /tmp)
|
||||||
|
[ ! -f "$project_directory/filebrowser" ] && echolog "在$project_directory找不到FileBrowser主程序,请下载。" && exit
|
||||||
|
export HOME="/root"
|
||||||
|
$project_directory/filebrowser -a 0.0.0.0 -p $PORT -r $ROOT_PATH -d ${ROOT_PATH}filebrowser.db -l $LOG_PATH >/dev/null 2>&1 &
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
busybox ps -w | grep -v "grep" | grep "$project_directory/filebrowser -a 0.0.0.0" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 &
|
||||||
|
rm -rf $LOG_PATH
|
||||||
|
}
|
||||||
|
|
||||||
|
restart() {
|
||||||
|
stop
|
||||||
|
sleep 1
|
||||||
|
start
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
uci -q batch <<-EOF >/dev/null
|
||||||
|
delete ucitrack.@filebrowser[-1]
|
||||||
|
add ucitrack filebrowser
|
||||||
|
set ucitrack.@filebrowser[-1].init=filebrowser
|
||||||
|
commit ucitrack
|
||||||
|
EOF
|
||||||
|
|
||||||
|
rm -rf /tmp/luci-*cache
|
||||||
|
exit 0
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"luci-app-filebrowser": {
|
||||||
|
"description": "Grant UCI access for luci-app-filebrowser",
|
||||||
|
"read": {
|
||||||
|
"uci": [ "filebrowser" ]
|
||||||
|
},
|
||||||
|
"write": {
|
||||||
|
"uci": [ "filebrowser" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"config": "filebrowser",
|
||||||
|
"init": "filebrowser"
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#
|
||||||
|
# Copyright (C) 2018 By-Ameng Openwrt.org
|
||||||
|
#
|
||||||
|
# This is free software, licensed under the Apache License, Version 2.0 .
|
||||||
|
#
|
||||||
|
|
||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
|
LUCI_TITLE:=luci-app-guest-wifi
|
||||||
|
LUCI_DEPENDS:=
|
||||||
|
LUCI_PKGARCH:=all
|
||||||
|
PKG_VERSION:=1.0
|
||||||
|
PKG_RELEASE:=3
|
||||||
|
|
||||||
|
include $(TOPDIR)/feeds/luci/luci.mk
|
||||||
|
|
||||||
|
# call BuildPackage - OpenWrt buildroot signature
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
module("luci.controller.guest-wifi", package.seeall)
|
||||||
|
|
||||||
|
function index()
|
||||||
|
if not nixio.fs.access("/etc/config/guest-wifi") then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
entry({"admin","network","guest-wifi"}, cbi("guest-wifi"), translate("Guest-wifi"), 16)
|
||||||
|
end
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
require("luci.tools.webadmin")
|
||||||
|
|
||||||
|
m = Map("guest-wifi", translate("Guest-wifi"))
|
||||||
|
|
||||||
|
s = m:section(TypedSection, "guest-wifi", translate("Config"), translate("You can set guest wifi here. The wifi will be disconnected when enabling/disabling. When modifying the password, first disable the guest wifi, and then do the modification, save and apply. Finally check both Enable and Create, save and apply."))
|
||||||
|
s.anonymous = true
|
||||||
|
s.addremove = false
|
||||||
|
|
||||||
|
enable = s:option(Flag, "enable", translate("Enable"), translate("Enable or disable guest wifi"))
|
||||||
|
enable.default = false
|
||||||
|
enable.optional = false
|
||||||
|
enable.rmempty = false
|
||||||
|
|
||||||
|
create = s:option(Flag, "create", translate("Create/Remove"), translate("Check to create guest wifi when enabled, or check to remove guest wifi when disabled."))
|
||||||
|
create.default = false
|
||||||
|
create.optional = false
|
||||||
|
create.rmempty = false
|
||||||
|
|
||||||
|
device = s:option(ListValue, "device", translate("Define device"), translate("Define device of guest wifi"))
|
||||||
|
device:value("radio0", "radio0")
|
||||||
|
device:value("radio1", "radio1")
|
||||||
|
device:value("radio2", "radio2")
|
||||||
|
device:value("ra", "ra")
|
||||||
|
device:value("rai", "rai")
|
||||||
|
device.default = "radio0"
|
||||||
|
|
||||||
|
wifi_name = s:option(Value, "wifi_name", translate("Wifi name"), translate("Define the name of guest wifi"))
|
||||||
|
wifi_name.default = "Guest-WiFi"
|
||||||
|
wifi_name.rmempty = true
|
||||||
|
|
||||||
|
interface_name = s:option(Value, "interface_name", translate("Interface name"), translate("Define the interface name of guest wifi"))
|
||||||
|
interface_name.default = "guest"
|
||||||
|
interface_name.rmempty = true
|
||||||
|
|
||||||
|
interface_ip = s:option(Value, "interface_ip", translate("Interface IP address"), translate("Define IP address for guest wifi"))
|
||||||
|
interface_ip.datatype = "ip4addr"
|
||||||
|
interface_ip.default ="192.168.4.1"
|
||||||
|
|
||||||
|
encryption = s:option(Value, "encryption", translate("Encryption"), translate("Define encryption of guest wifi"))
|
||||||
|
encryption:value("psk", "WPA-PSK")
|
||||||
|
encryption:value("psk2", "WPA2-PSK")
|
||||||
|
encryption:value("none", "No Encryption")
|
||||||
|
encryption.default = "psk2"
|
||||||
|
encryption.widget = "select"
|
||||||
|
|
||||||
|
passwd = s:option(Value, "passwd", translate("Password"), translate("Define the password of guest wifi"))
|
||||||
|
passwd.password = true
|
||||||
|
passwd.default = "guestnetwork"
|
||||||
|
|
||||||
|
isolate = s:option(ListValue, "isolate", translate("Isolation"), translate("Enalbe or disable isolation"))
|
||||||
|
isolate:value("1", translate("YES"))
|
||||||
|
isolate:value("0", translate("NO"))
|
||||||
|
|
||||||
|
start = s:option(Value, "start", translate("Start address"), translate("Lowest leased address as offset from the network address"))
|
||||||
|
start.default = "50"
|
||||||
|
start.rmempty = true
|
||||||
|
|
||||||
|
limit = s:option(Value, "limit", translate("Client Limit"), translate("Maximum number of leased addresses"))
|
||||||
|
limit.default = "200"
|
||||||
|
limit.rmempty = true
|
||||||
|
|
||||||
|
leasetime = s:option(Value, "leasetime", translate("DHCP lease time"), translate("Expiry time of leased addresses, minimum is 2 minutes (2m)"))
|
||||||
|
leasetime.default = "1h"
|
||||||
|
leasetime.rmempty = true
|
||||||
|
|
||||||
|
return m
|
||||||
|
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
msgid "guest-wifi"
|
||||||
|
msgstr "访客网络"
|
||||||
|
|
||||||
|
msgid "Guest-wifi"
|
||||||
|
msgstr "访客网络"
|
||||||
|
|
||||||
|
msgid "Config"
|
||||||
|
msgstr "设置"
|
||||||
|
|
||||||
|
msgid "You can set guest wifi here. The wifi will be disconnected when enabling/disabling. When modifying the password, first disable the guest wifi, and then do the modification, save and apply. Finally check both Enable and Create, save and apply."
|
||||||
|
msgstr "在此可以设置访客网络。启用/禁用访客网络时wifi会断开。当修改访客名称密码时,先禁用访客网络并保存应用,然后修改后保存应用,最后同时勾选启用和创建并保存应用。"
|
||||||
|
|
||||||
|
msgid "Enable"
|
||||||
|
msgstr "启用"
|
||||||
|
|
||||||
|
msgid "Enable or disable guest wifi"
|
||||||
|
msgstr "启用/禁用访客网络"
|
||||||
|
|
||||||
|
msgid "Create/Remove"
|
||||||
|
msgstr "创建/删除"
|
||||||
|
|
||||||
|
msgid "Check to create guest wifi when enabled, or check to remove guest wifi when disabled."
|
||||||
|
msgstr "与启用同时勾选,可创建和启用访客网络;启用不勾选时勾选,可删除访客网络。"
|
||||||
|
|
||||||
|
msgid "Define device"
|
||||||
|
msgstr "指定设备"
|
||||||
|
|
||||||
|
msgid "Define device of guest wifi"
|
||||||
|
msgstr "指定用于访客网络的设备"
|
||||||
|
|
||||||
|
msgid "Wifi name"
|
||||||
|
msgstr "无线名称"
|
||||||
|
|
||||||
|
msgid "Define the name of guest wifi"
|
||||||
|
msgstr "指定访客网络的无线名称"
|
||||||
|
|
||||||
|
msgid "Interface name"
|
||||||
|
msgstr "接口名称"
|
||||||
|
|
||||||
|
msgid "Define the interface name of guest wifi"
|
||||||
|
msgstr "设置访客网络的接口名称"
|
||||||
|
|
||||||
|
msgid "Interface IP address"
|
||||||
|
msgstr "接口IP地址"
|
||||||
|
|
||||||
|
msgid "Define IP address for guest wifi"
|
||||||
|
msgstr "指定访客网络的IP地址(不能与其他LAN的IP地址段相同)"
|
||||||
|
|
||||||
|
msgid "Encryption"
|
||||||
|
msgstr "加密"
|
||||||
|
|
||||||
|
msgid "Define encryption of guest wifi"
|
||||||
|
msgstr "设置访客网络的无线加密方式"
|
||||||
|
|
||||||
|
msgid "Password"
|
||||||
|
msgstr "密码"
|
||||||
|
|
||||||
|
msgid "Define the password of guest wifi"
|
||||||
|
msgstr "设备访客网络的无线密码"
|
||||||
|
|
||||||
|
msgid "Isolation"
|
||||||
|
msgstr "隔离"
|
||||||
|
|
||||||
|
msgid "Enalbe or disable isolation"
|
||||||
|
msgstr "开启或关闭与其它LAN网段的隔离"
|
||||||
|
|
||||||
|
msgid "Start address"
|
||||||
|
msgstr "起始地址"
|
||||||
|
|
||||||
|
msgid "Lowest leased address as offset from the network address"
|
||||||
|
msgstr "网络地址分配的起始地址"
|
||||||
|
|
||||||
|
msgid "Client Limit"
|
||||||
|
msgstr "客户端最大数目"
|
||||||
|
|
||||||
|
msgid "Maximum number of leased addresses"
|
||||||
|
msgstr "最大地址分配数量"
|
||||||
|
|
||||||
|
msgid "DHCP lease time"
|
||||||
|
msgstr "DHCP地址租用时间"
|
||||||
|
|
||||||
|
msgid "Expiry time of leased addresses, minimum is 2 minutes (2m)"
|
||||||
|
msgstr "租用地址的到期时间,最短2分钟(2m)"
|
||||||
|
|
||||||
|
msgid "YES"
|
||||||
|
msgstr "是"
|
||||||
|
|
||||||
|
msgid "NO"
|
||||||
|
msgstr "否"
|
||||||
|
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
zh-cn
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
config guest-wifi
|
||||||
|
option enable '0'
|
||||||
|
option wifi_name 'Guest-WiFi'
|
||||||
|
option interface_name 'guest'
|
||||||
|
option encryption 'psk2'
|
||||||
|
option passwd 'guestnetwork'
|
||||||
|
option interface_ip '192.168.4.1'
|
||||||
|
option isolate '1'
|
||||||
|
option start '50'
|
||||||
|
option limit '200'
|
||||||
|
option leasetime '1h'
|
||||||
|
option device 'radio0'
|
||||||
|
option create '0'
|
||||||
+213
@@ -0,0 +1,213 @@
|
|||||||
|
#!/bin/sh /etc/rc.common
|
||||||
|
# Copyright (C) 2015
|
||||||
|
# Must keep author's information if you use this file.
|
||||||
|
|
||||||
|
START=50
|
||||||
|
|
||||||
|
HISTORY_DIR="/etc/config/guest_wifi"
|
||||||
|
[ -e /etc/config/guest_wifi ] || mkdir -p /etc/config/guest_wifi
|
||||||
|
|
||||||
|
enabled=$(uci get guest-wifi.@guest-wifi[0].enable)
|
||||||
|
wifi_name=$(uci get guest-wifi.@guest-wifi[0].wifi_name)
|
||||||
|
interface_name=$(uci get guest-wifi.@guest-wifi[0].interface_name)
|
||||||
|
encryption=$(uci get guest-wifi.@guest-wifi[0].encryption)
|
||||||
|
passwd=$(uci get guest-wifi.@guest-wifi[0].passwd)
|
||||||
|
interface_ip=$(uci get guest-wifi.@guest-wifi[0].interface_ip)
|
||||||
|
isolate=$(uci get guest-wifi.@guest-wifi[0].isolate)
|
||||||
|
start=$(uci get guest-wifi.@guest-wifi[0].start)
|
||||||
|
limit=$(uci get guest-wifi.@guest-wifi[0].limit)
|
||||||
|
leasetime=$(uci get guest-wifi.@guest-wifi[0].leasetime)
|
||||||
|
device=$(uci get guest-wifi.@guest-wifi[0].device)
|
||||||
|
create=$(uci get guest-wifi.@guest-wifi[0].create)
|
||||||
|
|
||||||
|
|
||||||
|
start() {
|
||||||
|
[ $enabled = 1 ] && {
|
||||||
|
[ $create = 1 ] && {
|
||||||
|
[ -f /etc/config/guest_wifi/guest_del ] || echo "#! /bin/sh" > ${HISTORY_DIR}/guest_del
|
||||||
|
chmod 0755 ${HISTORY_DIR}/guest_del
|
||||||
|
add_interface
|
||||||
|
add_ssid
|
||||||
|
mod_dhcp
|
||||||
|
mod_fw
|
||||||
|
/etc/init.d/network restart
|
||||||
|
}
|
||||||
|
uci set guest-wifi.@guest-wifi[0].create='0'
|
||||||
|
uci commit guest-wifi
|
||||||
|
uci del wireless.$interface_name.disabled
|
||||||
|
uci commit wireless
|
||||||
|
wifi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
[ $enabled = 0 ] && {
|
||||||
|
[ $create = 1 ] && {
|
||||||
|
${HISTORY_DIR}/guest_del
|
||||||
|
rule_c=`uci show firewall |grep "Hide My LAN for $wifi_name"|grep -o "[0-9]*[0-9]"`
|
||||||
|
uci del firewall.@rule[$rule_c]
|
||||||
|
uci commit firewall
|
||||||
|
rule_b=`uci show firewall |grep "Allow DHCP request for $wifi_name"|grep -o "[0-9]*[0-9]"`
|
||||||
|
uci del firewall.@rule[$rule_b]
|
||||||
|
uci commit firewall
|
||||||
|
rule_a=`uci show firewall |grep "Allow DNS Queries for $wifi_name"|grep -o "[0-9]*[0-9]"`
|
||||||
|
uci del firewall.@rule[$rule_a]
|
||||||
|
uci commit firewall
|
||||||
|
/etc/config/guest_wifi/guest_del
|
||||||
|
rm -rf /etc/config/guest_wifi/guest_del
|
||||||
|
/etc/init.d/network restart
|
||||||
|
}
|
||||||
|
uci set guest-wifi.@guest-wifi[0].create='0'
|
||||||
|
uci commit guest-wifi
|
||||||
|
uci set wireless.$interface_name.disabled='1'
|
||||||
|
uci commit wireless
|
||||||
|
wifi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
restart() {
|
||||||
|
stop
|
||||||
|
sleep 2
|
||||||
|
start
|
||||||
|
}
|
||||||
|
|
||||||
|
add_interface() {
|
||||||
|
name=`uci show network |grep "$interface_ip"`
|
||||||
|
if [ $? = 1 ]; then
|
||||||
|
uci set network.$interface_name=interface
|
||||||
|
uci set network.$interface_name.proto='static'
|
||||||
|
uci set network.$interface_name.ipaddr="$interface_ip"
|
||||||
|
uci set network.$interface_name.netmask='255.255.255.0'
|
||||||
|
echo "uci del network.$interface_name" >> ${HISTORY_DIR}/guest_del
|
||||||
|
echo "uci commit network" >> ${HISTORY_DIR}/guest_del
|
||||||
|
uci commit network
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
add_ssid() {
|
||||||
|
check_name=`uci show wireless |grep "$wifi_name"`
|
||||||
|
if [ $? = 1 ]; then
|
||||||
|
uci set wireless.$interface_name=wifi-iface
|
||||||
|
uci set wireless.$interface_name.device="$device"
|
||||||
|
uci set wireless.$interface_name.mode='ap'
|
||||||
|
uci set wireless.$interface_name.network="$interface_name"
|
||||||
|
uci set wireless.$interface_name.ssid="$wifi_name"
|
||||||
|
uci set wireless.$interface_name.encryption="$encryption"
|
||||||
|
uci set wireless.$interface_name.isolate="$isolate"
|
||||||
|
if [ "$encryption" != "none" ]; then
|
||||||
|
uci set wireless.$interface_name.key="$passwd"
|
||||||
|
fi
|
||||||
|
echo "uci del wireless.$interface_name" >> ${HISTORY_DIR}/guest_del
|
||||||
|
echo "uci commit wireless" >> ${HISTORY_DIR}/guest_del
|
||||||
|
uci commit wireless
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
mod_dhcp() {
|
||||||
|
check_dhcp=`uci show dhcp |grep "$interface_name=dhcp"`
|
||||||
|
if [ $? = 1 ]; then
|
||||||
|
uci set dhcp.$interface_name=dhcp
|
||||||
|
uci set dhcp.$interface_name.interface="$interface_name"
|
||||||
|
uci set dhcp.$interface_name.start="$start"
|
||||||
|
uci set dhcp.$interface_name.limit="$limit"
|
||||||
|
uci set dhcp.$interface_name.leasetime="$leasetime"
|
||||||
|
echo "uci del dhcp.$interface_name" >> ${HISTORY_DIR}/guest_del
|
||||||
|
echo "uci commit dhcp" >> ${HISTORY_DIR}/guest_del
|
||||||
|
uci commit dhcp
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
mod_fw() {
|
||||||
|
num_a=`uci show firewall |grep '=zone' |wc -l`
|
||||||
|
num_b=`uci show firewall |grep '=forwarding' |wc -l`
|
||||||
|
|
||||||
|
check_zone=`uci show firewall |grep "name=\'$interface_name\'"`
|
||||||
|
if [ $? = 1 ]; then
|
||||||
|
uci add firewall zone
|
||||||
|
echo "uci del firewall.@zone[$num_a]" >> ${HISTORY_DIR}/guest_del
|
||||||
|
echo "uci commit firewall" >> ${HISTORY_DIR}/guest_del
|
||||||
|
uci set firewall.@zone[$num_a]=zone
|
||||||
|
uci set firewall.@zone[$num_a].name="$interface_name"
|
||||||
|
uci set firewall.@zone[$num_a].network="$interface_name"
|
||||||
|
uci set firewall.@zone[$num_a].forward='REJECT'
|
||||||
|
uci set firewall.@zone[$num_a].output='ACCEPT'
|
||||||
|
uci set firewall.@zone[$num_a].input='REJECT'
|
||||||
|
uci commit firewall
|
||||||
|
fi
|
||||||
|
|
||||||
|
check_forward=`uci show firewall |grep "forwarding\[.*\].src=\'"$interface_name\'""`
|
||||||
|
if [ $? = 1 ]; then
|
||||||
|
uci add firewall forwarding
|
||||||
|
echo "uci del firewall.@forwarding[$num_b]" >> ${HISTORY_DIR}/guest_del
|
||||||
|
echo "uci commit firewall" >> ${HISTORY_DIR}/guest_del
|
||||||
|
uci set firewall.@forwarding[$num_b]=forwarding
|
||||||
|
uci set firewall.@forwarding[$num_b].src="$interface_name"
|
||||||
|
uci set firewall.@forwarding[$num_b].dest='wan'
|
||||||
|
uci commit firewall
|
||||||
|
fi
|
||||||
|
|
||||||
|
check_DNS=`uci show firewall |grep "Allow DNS Queries for $wifi_name"`
|
||||||
|
if [ $? = 1 ]; then
|
||||||
|
num_c=`uci show firewall |grep '=rule' |wc -l`
|
||||||
|
uci add firewall rule
|
||||||
|
uci set firewall.@rule[$num_c]=rule
|
||||||
|
uci set firewall.@rule[$num_c].name="Allow DNS Queries for $wifi_name"
|
||||||
|
uci set firewall.@rule[$num_c].src="$interface_name"
|
||||||
|
uci set firewall.@rule[$num_c].dest_port='53'
|
||||||
|
uci set firewall.@rule[$num_c].proto='tcpudp'
|
||||||
|
uci set firewall.@rule[$num_c].target='ACCEPT'
|
||||||
|
uci commit firewall
|
||||||
|
unset num_c
|
||||||
|
fi
|
||||||
|
|
||||||
|
check_DHCP=`uci show firewall |grep "Allow DHCP request for $wifi_name"`
|
||||||
|
if [ $? = 1 ]; then
|
||||||
|
num_c=`uci show firewall |grep '=rule' |wc -l`
|
||||||
|
uci add firewall rule
|
||||||
|
uci set firewall.@rule[$num_c]=rule
|
||||||
|
uci set firewall.@rule[$num_c].name="Allow DHCP request for $wifi_name"
|
||||||
|
uci set firewall.@rule[$num_c].src="$interface_name"
|
||||||
|
uci set firewall.@rule[$num_c].src_port='67-68'
|
||||||
|
uci set firewall.@rule[$num_c].dest_port='67-68'
|
||||||
|
uci set firewall.@rule[$num_c].proto='udp'
|
||||||
|
uci set firewall.@rule[$num_c].target='ACCEPT'
|
||||||
|
uci commit firewall
|
||||||
|
unset num_c
|
||||||
|
fi
|
||||||
|
|
||||||
|
check_HIDE=`uci show firewall |grep "Hide My LAN for $wifi_name"`
|
||||||
|
if [ $? = 1 ]; then
|
||||||
|
num_c=`uci show firewall |grep '=rule' |wc -l`
|
||||||
|
uci add firewall rule
|
||||||
|
uci set firewall.@rule[$num_c]=rule
|
||||||
|
uci set firewall.@rule[$num_c].enabled='1'
|
||||||
|
uci set firewall.@rule[$num_c].name="Hide My LAN for $wifi_name"
|
||||||
|
uci set firewall.@rule[$num_c].proto='all'
|
||||||
|
uci set firewall.@rule[$num_c].src="$interface_name"
|
||||||
|
#convert netmask to cidr
|
||||||
|
local lan_netmask=`uci get network.lan.netmask`
|
||||||
|
local nbits=0
|
||||||
|
local IFS=.
|
||||||
|
for netmask_dec in $lan_netmask ; do
|
||||||
|
case $netmask_dec in
|
||||||
|
255) let nbits+=8 ;;
|
||||||
|
254) let nbits+=7 ;;
|
||||||
|
252) let nbits+=6 ;;
|
||||||
|
248) let nbits+=5 ;;
|
||||||
|
240) let nbits+=4 ;;
|
||||||
|
224) let nbits+=3 ;;
|
||||||
|
192) let nbits+=2 ;;
|
||||||
|
128) let nbits+=1 ;;
|
||||||
|
0) ;;
|
||||||
|
*) echo "Error: $netmask_dec can not be recognised as netmask decimal." && exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
unset netmask_dec
|
||||||
|
uci set firewall.@rule[$num_c].dest_ip="`uci get network.lan.ipaddr`/$nbits"
|
||||||
|
uci set firewall.@rule[$num_c].target='REJECT'
|
||||||
|
uci commit firewall
|
||||||
|
unset num_c
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
uci -q batch <<-EOF >/dev/null
|
||||||
|
delete ucitrack.@guest-wifi[-1]
|
||||||
|
add ucitrack guest-wifi
|
||||||
|
set ucitrack.@guest-wifi[-1].init=guest-wifi
|
||||||
|
commit ucitrack
|
||||||
|
EOF
|
||||||
|
|
||||||
|
rm -rf /tmp/luci-*cache
|
||||||
|
exit 0
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"luci-app-guest-wifi": {
|
||||||
|
"description": "Grant UCI access for luci-app-guest-wifi",
|
||||||
|
"read": {
|
||||||
|
"uci": [ "guest-wifi" ]
|
||||||
|
},
|
||||||
|
"write": {
|
||||||
|
"uci": [ "guest-wifi" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"config": "guest-wifi",
|
||||||
|
"init": "guest-wifi"
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Copyright (C) 2018-2021 Lienol <lawlienol@gmail.com>
|
||||||
|
#
|
||||||
|
# This is free software, licensed under the Apache License, Version 2.0 .
|
||||||
|
#
|
||||||
|
|
||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
|
PKG_NAME:=luci-app-ipsec-server
|
||||||
|
PKG_VERSION:=20211223
|
||||||
|
PKG_RELEASE:=5
|
||||||
|
|
||||||
|
PKG_MAINTAINER:=Lienol <lawlienol@gmail.com>
|
||||||
|
|
||||||
|
LUCI_TITLE:=LuCI support for IPSec VPN Server
|
||||||
|
LUCI_DEPENDS:=+kmod-tun +luci-lib-jsonc +xl2tpd +strongswan \
|
||||||
|
+PACKAGE_strongswan:strongswan-mod-kernel-libipsec \
|
||||||
|
+PACKAGE_strongswan:strongswan-mod-openssl \
|
||||||
|
+PACKAGE_strongswan:strongswan-mod-xauth-generic \
|
||||||
|
+(PACKAGE_strongswan-mod-kdf||PACKAGE_strongswan-mod-openssl||PACKAGE_strongswan-mod-wolfssl):strongswan-minimal
|
||||||
|
LUCI_PKGARCH:=all
|
||||||
|
|
||||||
|
define Package/$(PKG_NAME)/conffiles
|
||||||
|
/etc/config/luci-app-ipsec-server
|
||||||
|
endef
|
||||||
|
|
||||||
|
include $(TOPDIR)/feeds/luci/luci.mk
|
||||||
|
|
||||||
|
# call BuildPackage - OpenWrt buildroot signature
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
-- Copyright 2018-2020 Lienol <lawlienol@gmail.com>
|
||||||
|
module("luci.controller.ipsec-server", package.seeall)
|
||||||
|
|
||||||
|
function index()
|
||||||
|
if not nixio.fs.access("/etc/config/luci-app-ipsec-server") then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
entry({"admin", "vpn"}, firstchild(), "VPN", 45).dependent = false
|
||||||
|
entry({"admin", "vpn", "ipsec-server"}, alias("admin", "vpn", "ipsec-server", "settings"), _("IPSec VPN Server"), 49).dependent = false
|
||||||
|
entry({"admin", "vpn", "ipsec-server", "settings"}, cbi("ipsec-server/settings"), _("General Settings"), 10).leaf = true
|
||||||
|
entry({"admin", "vpn", "ipsec-server", "users"}, cbi("ipsec-server/users"), _("Users Manager"), 20).leaf = true
|
||||||
|
entry({"admin", "vpn", "ipsec-server", "l2tp_user"}, cbi("ipsec-server/l2tp_user")).leaf = true
|
||||||
|
entry({"admin", "vpn", "ipsec-server", "online"}, cbi("ipsec-server/online"), _("L2TP Online Users"), 30).leaf = true
|
||||||
|
entry({"admin", "vpn", "ipsec-server", "status"}, call("act_status")).leaf = true
|
||||||
|
end
|
||||||
|
|
||||||
|
function act_status()
|
||||||
|
local e = {}
|
||||||
|
e["ipsec_status"] = luci.sys.call("/usr/bin/pgrep ipsec >/dev/null") == 0
|
||||||
|
e["l2tp_status"] = luci.sys.call("top -bn1 | grep -v grep | grep '/var/etc/xl2tpd' >/dev/null") == 0
|
||||||
|
luci.http.prepare_content("application/json")
|
||||||
|
luci.http.write_json(e)
|
||||||
|
end
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
local d = require "luci.dispatcher"
|
||||||
|
local sys = require "luci.sys"
|
||||||
|
|
||||||
|
m = Map("luci-app-ipsec-server", "L2TP/IPSec PSK " .. translate("Users Manager"))
|
||||||
|
m.redirect = d.build_url("admin", "vpn", "ipsec-server", "users")
|
||||||
|
|
||||||
|
if sys.call("command -v xl2tpd > /dev/null") == 0 then
|
||||||
|
s = m:section(NamedSection, arg[1], "l2tp_users", "")
|
||||||
|
s.addremove = false
|
||||||
|
s.anonymous = true
|
||||||
|
|
||||||
|
o = s:option(Flag, "enabled", translate("Enabled"))
|
||||||
|
o.default = 1
|
||||||
|
o.rmempty = false
|
||||||
|
|
||||||
|
o = s:option(Value, "username", translate("Username"))
|
||||||
|
o.placeholder = translate("Username")
|
||||||
|
o.rmempty = false
|
||||||
|
|
||||||
|
o = s:option(Value, "password", translate("Password"))
|
||||||
|
o.placeholder = translate("Password")
|
||||||
|
o.rmempty = false
|
||||||
|
|
||||||
|
o = s:option(Value, "ipaddress", translate("IP address"))
|
||||||
|
o.placeholder = translate("Automatically")
|
||||||
|
o.datatype = "ip4addr"
|
||||||
|
o.rmempty = true
|
||||||
|
|
||||||
|
o = s:option(DynamicList, "routes", translate("Static Routes"))
|
||||||
|
o.placeholder = "192.168.10.0/24"
|
||||||
|
o.datatype = "ipmask4"
|
||||||
|
o.rmempty = true
|
||||||
|
end
|
||||||
|
|
||||||
|
return m
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
local o = require "luci.dispatcher"
|
||||||
|
local fs = require "nixio.fs"
|
||||||
|
local jsonc = require "luci.jsonc"
|
||||||
|
|
||||||
|
local sessions = {}
|
||||||
|
local session_path = "/var/etc/xl2tpd/session"
|
||||||
|
if fs.access(session_path) then
|
||||||
|
for filename in fs.dir(session_path) do
|
||||||
|
local session_file = session_path .. "/" .. filename
|
||||||
|
local file = io.open(session_file, "r")
|
||||||
|
local t = jsonc.parse(file:read("*a"))
|
||||||
|
if t then
|
||||||
|
t.session_file = session_file
|
||||||
|
sessions[#sessions + 1] = t
|
||||||
|
end
|
||||||
|
file:close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local blacklist = {}
|
||||||
|
local firewall_user_path = "/etc/firewall.user"
|
||||||
|
if fs.access(firewall_user_path) then
|
||||||
|
for line in io.lines(firewall_user_path) do
|
||||||
|
local m = line:match('xl2tpd%-blacklist%-([^\n]+)')
|
||||||
|
if m then
|
||||||
|
local t = {}
|
||||||
|
t.ip = m
|
||||||
|
blacklist[#blacklist + 1] = t
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
f = SimpleForm("processes")
|
||||||
|
f.reset = false
|
||||||
|
f.submit = false
|
||||||
|
|
||||||
|
t = f:section(Table, sessions, translate("L2TP Online Users"))
|
||||||
|
t:option(DummyValue, "username", translate("Username"))
|
||||||
|
t:option(DummyValue, "interface", translate("Interface"))
|
||||||
|
t:option(DummyValue, "ip", translate("Client IP"))
|
||||||
|
t:option(DummyValue, "remote_ip", translate("IP address"))
|
||||||
|
t:option(DummyValue, "login_time", translate("Login Time"))
|
||||||
|
|
||||||
|
_blacklist = t:option(Button, "_blacklist", translate("Blacklist"))
|
||||||
|
function _blacklist.render(e, t, a)
|
||||||
|
e.title = translate("Add to Blacklist")
|
||||||
|
e.inputstyle = "remove"
|
||||||
|
Button.render(e, t, a)
|
||||||
|
end
|
||||||
|
function _blacklist.write(t, s)
|
||||||
|
local e = t.map:get(s, "remote_ip")
|
||||||
|
luci.util.execi("echo 'iptables -I INPUT -s %s -p udp -m multiport --dports 500,4500,1701 -j DROP ## xl2tpd-blacklist-%s' >> /etc/firewall.user" % {e, e})
|
||||||
|
luci.util.execi("iptables -I INPUT -s %s -p udp -m multiport --dports 500,4500,1701 -j DROP" % {e})
|
||||||
|
luci.util.execi("rm -f " .. t.map:get(s, "session_file"))
|
||||||
|
null, t.tag_error[s] = luci.sys.process.signal(t.map:get(s, "pid"), 9)
|
||||||
|
luci.http.redirect(o.build_url("admin/vpn/ipsec-server/online"))
|
||||||
|
end
|
||||||
|
|
||||||
|
_kill = t:option(Button, "_kill", translate("Forced offline"))
|
||||||
|
_kill.inputstyle = "remove"
|
||||||
|
function _kill.write(t, s)
|
||||||
|
luci.util.execi("rm -f " .. t.map:get(s, "session_file"))
|
||||||
|
null, t.tag_error[t] = luci.sys.process.signal(t.map:get(s, "pid"), 9)
|
||||||
|
luci.http.redirect(o.build_url("admin/vpn/ipsec-server/online"))
|
||||||
|
end
|
||||||
|
|
||||||
|
t = f:section(Table, blacklist, translate("Blacklist"))
|
||||||
|
t:option(DummyValue, "ip", translate("IP address"))
|
||||||
|
|
||||||
|
_blacklist2 = t:option(Button, "_blacklist2", translate("Blacklist"))
|
||||||
|
function _blacklist2.render(e, t, a)
|
||||||
|
e.title = translate("Remove from Blacklist")
|
||||||
|
e.inputstyle = "apply"
|
||||||
|
Button.render(e, t, a)
|
||||||
|
end
|
||||||
|
function _blacklist2.write(t, s)
|
||||||
|
local e = t.map:get(s, "ip")
|
||||||
|
luci.util.execi("sed -i -e '/## xl2tpd-blacklist-%s/d' /etc/firewall.user" % {e})
|
||||||
|
luci.util.execi("iptables -D INPUT -s %s -p udp -m multiport --dports 500,4500,1701 -j DROP" % {e})
|
||||||
|
luci.http.redirect(o.build_url("admin/vpn/ipsec-server/online"))
|
||||||
|
end
|
||||||
|
|
||||||
|
return f
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
local sys = require "luci.sys"
|
||||||
|
|
||||||
|
m = Map("luci-app-ipsec-server", translate("IPSec VPN Server"))
|
||||||
|
m.template = "ipsec-server/ipsec-server_status"
|
||||||
|
|
||||||
|
s = m:section(TypedSection, "service")
|
||||||
|
s.anonymous = true
|
||||||
|
|
||||||
|
o = s:option(DummyValue, "ipsec-server_status", translate("Current Condition"))
|
||||||
|
o.rawhtml = true
|
||||||
|
o.cfgvalue = function(t, n)
|
||||||
|
return '<font class="ipsec-server_status"></font>'
|
||||||
|
end
|
||||||
|
|
||||||
|
enabled = s:option(Flag, "enabled", translate("Enable"))
|
||||||
|
enabled.description = translate("Use a client that supports IPSec Xauth PSK (iOS or Android) to connect to this server.")
|
||||||
|
enabled.default = 0
|
||||||
|
enabled.rmempty = false
|
||||||
|
|
||||||
|
clientip = s:option(Value, "clientip", translate("VPN Client IP"))
|
||||||
|
clientip.description = translate("VPN Client reserved started IP addresses with the same subnet mask, such as: 192.168.100.10/24")
|
||||||
|
clientip.datatype = "ip4addr"
|
||||||
|
clientip.optional = false
|
||||||
|
clientip.rmempty = false
|
||||||
|
|
||||||
|
secret = s:option(Value, "secret", translate("Secret Pre-Shared Key"))
|
||||||
|
secret.password = true
|
||||||
|
|
||||||
|
if sys.call("command -v xl2tpd > /dev/null") == 0 then
|
||||||
|
o = s:option(DummyValue, "l2tp_status", "L2TP " .. translate("Current Condition"))
|
||||||
|
o.rawhtml = true
|
||||||
|
o.cfgvalue = function(t, n)
|
||||||
|
return '<font class="l2tp_status"></font>'
|
||||||
|
end
|
||||||
|
|
||||||
|
o = s:option(Flag, "l2tp_enable", "L2TP " .. translate("Enable"))
|
||||||
|
o.description = translate("Use a client that supports L2TP over IPSec PSK to connect to this server.")
|
||||||
|
o.default = 0
|
||||||
|
o.rmempty = false
|
||||||
|
|
||||||
|
o = s:option(Value, "l2tp_localip", "L2TP " .. translate("Server IP"))
|
||||||
|
o.description = translate("VPN Server IP address, such as: 192.168.101.1")
|
||||||
|
o.datatype = "ip4addr"
|
||||||
|
o.rmempty = true
|
||||||
|
o.default = "192.168.101.1"
|
||||||
|
o.placeholder = o.default
|
||||||
|
|
||||||
|
o = s:option(Value, "l2tp_remoteip", "L2TP " .. translate("Client IP"))
|
||||||
|
o.description = translate("VPN Client IP address range, such as: 192.168.101.10-20")
|
||||||
|
o.rmempty = true
|
||||||
|
o.default = "192.168.101.10-20"
|
||||||
|
o.placeholder = o.default
|
||||||
|
|
||||||
|
if sys.call("ls -L /usr/lib/ipsec/libipsec* 2>/dev/null >/dev/null") == 0 then
|
||||||
|
o = s:option(DummyValue, "_o", " ")
|
||||||
|
o.rawhtml = true
|
||||||
|
o.cfgvalue = function(t, n)
|
||||||
|
return string.format('<a style="color: red">%s</a>', translate("L2TP/IPSec is not compatible with kernel-libipsec, which will disable this module."))
|
||||||
|
end
|
||||||
|
o:depends("l2tp_enable", true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return m
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
local d = require "luci.dispatcher"
|
||||||
|
local sys = require "luci.sys"
|
||||||
|
|
||||||
|
m = Map("luci-app-ipsec-server")
|
||||||
|
|
||||||
|
s = m:section(TypedSection, "ipsec_users", "IPSec Xauth PSK " .. translate("Users Manager"))
|
||||||
|
s.description = translate("Use a client that supports IPSec Xauth PSK (iOS or Android) to connect to this server.")
|
||||||
|
s.addremove = true
|
||||||
|
s.anonymous = true
|
||||||
|
s.template = "cbi/tblsection"
|
||||||
|
|
||||||
|
o = s:option(Flag, "enabled", translate("Enabled"))
|
||||||
|
o.default = 1
|
||||||
|
o.rmempty = false
|
||||||
|
|
||||||
|
o = s:option(Value, "username", translate("Username"))
|
||||||
|
o.placeholder = translate("Username")
|
||||||
|
o.rmempty = false
|
||||||
|
|
||||||
|
o = s:option(Value, "password", translate("Password"))
|
||||||
|
o.placeholder = translate("Password")
|
||||||
|
o.rmempty = false
|
||||||
|
|
||||||
|
if sys.call("command -v xl2tpd > /dev/null") == 0 then
|
||||||
|
s = m:section(TypedSection, "l2tp_users", "L2TP/IPSec PSK " .. translate("Users Manager"))
|
||||||
|
s.description = translate("Use a client that supports L2TP over IPSec PSK to connect to this server.")
|
||||||
|
s.addremove = true
|
||||||
|
s.anonymous = true
|
||||||
|
s.template = "cbi/tblsection"
|
||||||
|
s.extedit = d.build_url("admin", "vpn", "ipsec-server", "l2tp_user", "%s")
|
||||||
|
function s.create(e, t)
|
||||||
|
t = TypedSection.create(e, t)
|
||||||
|
luci.http.redirect(e.extedit:format(t))
|
||||||
|
end
|
||||||
|
|
||||||
|
o = s:option(Flag, "enabled", translate("Enabled"))
|
||||||
|
o.default = 1
|
||||||
|
o.rmempty = false
|
||||||
|
|
||||||
|
o = s:option(Value, "username", translate("Username"))
|
||||||
|
o.placeholder = translate("Username")
|
||||||
|
o.rmempty = false
|
||||||
|
|
||||||
|
o = s:option(Value, "password", translate("Password"))
|
||||||
|
o.placeholder = translate("Password")
|
||||||
|
o.rmempty = false
|
||||||
|
|
||||||
|
o = s:option(Value, "ipaddress", translate("IP address"))
|
||||||
|
o.placeholder = translate("Automatically")
|
||||||
|
o.datatype = "ip4addr"
|
||||||
|
o.rmempty = true
|
||||||
|
end
|
||||||
|
|
||||||
|
return m
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<% include("cbi/map") %>
|
||||||
|
<script type="text/javascript">//<![CDATA[
|
||||||
|
XHR.poll(2, '<%=luci.dispatcher.build_url("admin", "vpn", "ipsec-server", "status")%>', null,
|
||||||
|
function(x, result)
|
||||||
|
{
|
||||||
|
var ipsec_status = document.getElementsByClassName('ipsec-server_status')[0];
|
||||||
|
if (ipsec_status) {
|
||||||
|
ipsec_status.setAttribute("style","font-weight:bold;");
|
||||||
|
ipsec_status.setAttribute("color",result.ipsec_status ? "green":"red");
|
||||||
|
ipsec_status.innerHTML = result.ipsec_status?'<%=translate("RUNNING")%>':'<%=translate("NOT RUNNING")%>';
|
||||||
|
}
|
||||||
|
var l2tp_status = document.getElementsByClassName('l2tp_status')[0];
|
||||||
|
if (l2tp_status) {
|
||||||
|
l2tp_status.setAttribute("style","font-weight:bold;");
|
||||||
|
l2tp_status.setAttribute("color",result.l2tp_status ? "green":"red");
|
||||||
|
l2tp_status.innerHTML = result.l2tp_status?'<%=translate("RUNNING")%>':'<%=translate("NOT RUNNING")%>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
//]]>
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
msgid "IPSec VPN Server"
|
||||||
|
msgstr "IPSec VPN 服务器"
|
||||||
|
|
||||||
|
msgid "Use a client that supports IPSec Xauth PSK (iOS or Android) to connect to this server."
|
||||||
|
msgstr "使用支持 IPSec Xauth PSK(iOS 或 Android)的客户端连接到此服务端。"
|
||||||
|
|
||||||
|
msgid "Use a client that supports L2TP over IPSec PSK to connect to this server."
|
||||||
|
msgstr "使用支持 L2TP over IPSec PSK 的客户端连接到此服务端。"
|
||||||
|
|
||||||
|
msgid "Current Condition"
|
||||||
|
msgstr "当前状态"
|
||||||
|
|
||||||
|
msgid "General settings"
|
||||||
|
msgstr "基本设置"
|
||||||
|
|
||||||
|
msgid "Enabled"
|
||||||
|
msgstr "启用"
|
||||||
|
|
||||||
|
msgid "VPN Client IP"
|
||||||
|
msgstr "VPN客户端地址段"
|
||||||
|
|
||||||
|
msgid "VPN Client reserved started IP addresses with the same subnet mask, such as: 192.168.100.10/24"
|
||||||
|
msgstr "VPN客户端获取IP的起始地址,例如:192.168.100.10/24"
|
||||||
|
|
||||||
|
msgid "Secret Pre-Shared Key"
|
||||||
|
msgstr "PSK密钥"
|
||||||
|
|
||||||
|
msgid "VPN Server IP address, such as: 192.168.101.1"
|
||||||
|
msgstr "VPN服务端IP地址,例如:192.168.101.1"
|
||||||
|
|
||||||
|
msgid "VPN Client IP address range, such as: 192.168.101.10-20"
|
||||||
|
msgstr "VPN客户端获取IP范围,例如:192.168.101.10-20"
|
||||||
|
|
||||||
|
msgid "L2TP/IPSec is not compatible with kernel-libipsec, which will disable this module."
|
||||||
|
msgstr "L2TP/IPSec不兼容kernel-libipsec,开启将会禁用此模块。"
|
||||||
|
|
||||||
|
msgid "Users Manager"
|
||||||
|
msgstr "用户管理"
|
||||||
|
|
||||||
|
msgid "Username"
|
||||||
|
msgstr "用户名"
|
||||||
|
|
||||||
|
msgid "Password"
|
||||||
|
msgstr "密码"
|
||||||
|
|
||||||
|
msgid "IP address"
|
||||||
|
msgstr "IP 地址"
|
||||||
|
|
||||||
|
msgid "Automatically"
|
||||||
|
msgstr "自动分配"
|
||||||
|
|
||||||
|
msgid "Online Users"
|
||||||
|
msgstr "在线用户"
|
||||||
|
|
||||||
|
msgid "L2TP Online Users"
|
||||||
|
msgstr "L2TP 在线用户"
|
||||||
|
|
||||||
|
msgid "Login Time"
|
||||||
|
msgstr "登录时间"
|
||||||
|
|
||||||
|
msgid "Blacklist"
|
||||||
|
msgstr "黑名单"
|
||||||
|
|
||||||
|
msgid "Add to Blacklist"
|
||||||
|
msgstr "加入黑名单"
|
||||||
|
|
||||||
|
msgid "Remove from Blacklist"
|
||||||
|
msgstr "移出黑名单"
|
||||||
|
|
||||||
|
msgid "Forced offline"
|
||||||
|
msgstr "强制下线"
|
||||||
|
|
||||||
|
msgid "NOT RUNNING"
|
||||||
|
msgstr "未运行"
|
||||||
|
|
||||||
|
msgid "RUNNING"
|
||||||
|
msgstr "运行中"
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
zh-cn
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
config service 'ipsec'
|
||||||
|
option enabled '0'
|
||||||
|
option secret 'ipsec'
|
||||||
|
option clientip '192.168.100.10/24'
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,274 @@
|
|||||||
|
#!/bin/sh /etc/rc.common
|
||||||
|
|
||||||
|
START=99
|
||||||
|
|
||||||
|
CONFIG="luci-app-ipsec-server"
|
||||||
|
IPSEC_SECRETS_FILE=/etc/ipsec.secrets
|
||||||
|
IPSEC_CONN_FILE=/etc/ipsec.conf
|
||||||
|
CHAP_SECRETS=/etc/ppp/chap-secrets
|
||||||
|
L2TP_PATH=/var/etc/xl2tpd
|
||||||
|
L2TP_CONTROL_FILE=${L2TP_PATH}/control
|
||||||
|
L2TP_CONFIG_FILE=${L2TP_PATH}/xl2tpd.conf
|
||||||
|
L2TP_OPTIONS_FILE=${L2TP_PATH}/options.xl2tpd
|
||||||
|
L2TP_LOG_FILE=${L2TP_PATH}/xl2tpd.log
|
||||||
|
|
||||||
|
vt_clientip=$(uci -q get ${CONFIG}.@service[0].clientip)
|
||||||
|
l2tp_enabled=$(uci -q get ${CONFIG}.@service[0].l2tp_enable)
|
||||||
|
l2tp_localip=$(uci -q get ${CONFIG}.@service[0].l2tp_localip)
|
||||||
|
|
||||||
|
ipt_flag="IPSec VPN Server"
|
||||||
|
|
||||||
|
get_enabled_anonymous_secs() {
|
||||||
|
uci -q show "${CONFIG}" | grep "${1}\[.*\.enabled='1'" | cut -d '.' -sf2
|
||||||
|
}
|
||||||
|
|
||||||
|
ipt_rule() {
|
||||||
|
if [ "$1" = "add" ]; then
|
||||||
|
iptables -t nat -I postrouting_rule -s ${vt_clientip} -m comment --comment "${ipt_flag}" -j MASQUERADE 2>/dev/null
|
||||||
|
iptables -I forwarding_rule -s ${vt_clientip} -m comment --comment "${ipt_flag}" -j ACCEPT 2>/dev/null
|
||||||
|
iptables -I forwarding_rule -m policy --dir in --pol ipsec --proto esp -m comment --comment "${ipt_flag}" -j ACCEPT 2>/dev/null
|
||||||
|
iptables -I forwarding_rule -m policy --dir out --pol ipsec --proto esp -m comment --comment "${ipt_flag}" -j ACCEPT 2>/dev/null
|
||||||
|
iptables -I input_rule -p udp -m multiport --dports 500,4500 -m comment --comment "${ipt_flag}" -j ACCEPT 2>/dev/null
|
||||||
|
iptables -t mangle -I OUTPUT -p udp -m multiport --sports 500,4500 -m comment --comment "${ipt_flag}" -j RETURN 2>/dev/null
|
||||||
|
[ "${l2tp_enabled}" = 1 ] && {
|
||||||
|
iptables -t nat -I postrouting_rule -s ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_flag}" -j MASQUERADE 2>/dev/null
|
||||||
|
iptables -I forwarding_rule -s ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_flag}" -j ACCEPT 2>/dev/null
|
||||||
|
iptables -I input_rule -p udp --dport 1701 -m comment --comment "${ipt_flag}" -j ACCEPT 2>/dev/null
|
||||||
|
iptables -t mangle -I OUTPUT -p udp --sport 1701 -m comment --comment "${ipt_flag}" -j RETURN 2>/dev/null
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ipt_del() {
|
||||||
|
for i in $(seq 1 $($1 -nL $2 | grep -c "${ipt_flag}")); do
|
||||||
|
local index=$($1 --line-number -nL $2 | grep "${ipt_flag}" | head -1 | awk '{print $1}')
|
||||||
|
$1 -w -D $2 $index 2>/dev/null
|
||||||
|
done
|
||||||
|
}
|
||||||
|
ipt_del "iptables" "forwarding_rule"
|
||||||
|
ipt_del "iptables" "input_rule"
|
||||||
|
ipt_del "iptables -t nat" "postrouting_rule"
|
||||||
|
ipt_del "iptables -t mangle" "OUTPUT"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
gen_include() {
|
||||||
|
echo '#!/bin/sh' > /var/etc/ipsecvpn.include
|
||||||
|
extract_rules() {
|
||||||
|
echo "*$1"
|
||||||
|
iptables-save -t $1 | grep "${ipt_flag}" | \
|
||||||
|
sed -e "s/^-A \(INPUT\)/-I \1 1/"
|
||||||
|
echo 'COMMIT'
|
||||||
|
}
|
||||||
|
cat <<-EOF >> /var/etc/ipsecvpn.include
|
||||||
|
iptables-save -c | grep -v "${ipt_flag}" | iptables-restore -c
|
||||||
|
iptables-restore -n <<-EOT
|
||||||
|
$(extract_rules filter)
|
||||||
|
$(extract_rules nat)
|
||||||
|
EOT
|
||||||
|
EOF
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
local vt_enabled=$(uci -q get ${CONFIG}.@service[0].enabled)
|
||||||
|
[ "$vt_enabled" = 0 ] && return 1
|
||||||
|
|
||||||
|
local vt_gateway="${vt_clientip%.*}.1"
|
||||||
|
local vt_secret=$(uci -q get ${CONFIG}.@service[0].secret)
|
||||||
|
|
||||||
|
local l2tp_enabled=$(uci -q get ${CONFIG}.@service[0].l2tp_enable)
|
||||||
|
[ "${l2tp_enabled}" = 1 ] && {
|
||||||
|
touch ${CHAP_SECRETS}
|
||||||
|
local vt_remoteip=$(uci -q get ${CONFIG}.@service[0].l2tp_remoteip)
|
||||||
|
local ipsec_l2tp_config=$(cat <<-EOF
|
||||||
|
#######################################
|
||||||
|
# L2TP Connections
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
conn L2TP-IKEv1-PSK
|
||||||
|
type=transport
|
||||||
|
keyexchange=ikev1
|
||||||
|
authby=secret
|
||||||
|
leftprotoport=udp/l2tp
|
||||||
|
left=%any
|
||||||
|
right=%any
|
||||||
|
rekey=no
|
||||||
|
forceencaps=yes
|
||||||
|
ike=aes128-sha1-modp2048,aes128-sha1-modp1024,3des-sha1-modp1024,3des-sha1-modp1536
|
||||||
|
esp=aes128-sha1,3des-sha1
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
mkdir -p ${L2TP_PATH}
|
||||||
|
cat > ${L2TP_OPTIONS_FILE} <<-EOF
|
||||||
|
name "l2tp-server"
|
||||||
|
ipcp-accept-local
|
||||||
|
ipcp-accept-remote
|
||||||
|
ms-dns ${l2tp_localip}
|
||||||
|
noccp
|
||||||
|
auth
|
||||||
|
idle 1800
|
||||||
|
mtu 1400
|
||||||
|
mru 1400
|
||||||
|
lcp-echo-failure 10
|
||||||
|
lcp-echo-interval 60
|
||||||
|
connect-delay 5000
|
||||||
|
EOF
|
||||||
|
cat > ${L2TP_CONFIG_FILE} <<-EOF
|
||||||
|
[global]
|
||||||
|
port = 1701
|
||||||
|
;debug avp = yes
|
||||||
|
;debug network = yes
|
||||||
|
;debug state = yes
|
||||||
|
;debug tunnel = yes
|
||||||
|
[lns default]
|
||||||
|
ip range = ${vt_remoteip}
|
||||||
|
local ip = ${l2tp_localip}
|
||||||
|
require chap = yes
|
||||||
|
refuse pap = yes
|
||||||
|
require authentication = no
|
||||||
|
name = l2tp-server
|
||||||
|
;ppp debug = yes
|
||||||
|
pppoptfile = ${L2TP_OPTIONS_FILE}
|
||||||
|
length bit = yes
|
||||||
|
EOF
|
||||||
|
|
||||||
|
local l2tp_users=$(get_enabled_anonymous_secs "@l2tp_users")
|
||||||
|
[ -n "${l2tp_users}" ] && {
|
||||||
|
for _user in ${l2tp_users}; do
|
||||||
|
local u_enabled=$(uci -q get ${CONFIG}.${_user}.enabled)
|
||||||
|
[ "${u_enabled}" -eq 1 ] || continue
|
||||||
|
|
||||||
|
local u_username=$(uci -q get ${CONFIG}.${_user}.username)
|
||||||
|
[ -n "${u_username}" ] || continue
|
||||||
|
|
||||||
|
local u_password=$(uci -q get ${CONFIG}.${_user}.password)
|
||||||
|
[ -n "${u_password}" ] || continue
|
||||||
|
|
||||||
|
local u_ipaddress=$(uci -q get ${CONFIG}.${_user}.ipaddress)
|
||||||
|
[ -n "${u_ipaddress}" ] || u_ipaddress="*"
|
||||||
|
|
||||||
|
echo "${u_username} l2tp-server ${u_password} ${u_ipaddress}" >> ${CHAP_SECRETS}
|
||||||
|
done
|
||||||
|
}
|
||||||
|
unset user
|
||||||
|
|
||||||
|
echo "ip-up-script /usr/share/xl2tpd/ip-up" >> ${L2TP_OPTIONS_FILE}
|
||||||
|
echo "ip-down-script /usr/share/xl2tpd/ip-down" >> ${L2TP_OPTIONS_FILE}
|
||||||
|
|
||||||
|
xl2tpd -c ${L2TP_CONFIG_FILE} -C ${L2TP_CONTROL_FILE} -D >${L2TP_LOG_FILE} 2>&1 &
|
||||||
|
rm -f "/usr/lib/ipsec/libipsec.so.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
cat > ${IPSEC_CONN_FILE} <<-EOF
|
||||||
|
# ipsec.conf - strongSwan IPsec configuration file
|
||||||
|
|
||||||
|
config setup
|
||||||
|
uniqueids=no
|
||||||
|
charondebug="cfg 2, dmn 2, ike 2, net 0"
|
||||||
|
|
||||||
|
conn %default
|
||||||
|
dpdaction=clear
|
||||||
|
dpddelay=300s
|
||||||
|
rekey=no
|
||||||
|
left=%defaultroute
|
||||||
|
leftfirewall=yes
|
||||||
|
right=%any
|
||||||
|
ikelifetime=60m
|
||||||
|
keylife=20m
|
||||||
|
rekeymargin=3m
|
||||||
|
keyingtries=1
|
||||||
|
auto=add
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Default non L2TP Connections
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
conn Non-L2TP
|
||||||
|
leftsubnet=0.0.0.0/0
|
||||||
|
rightsubnet=${vt_clientip}
|
||||||
|
rightsourceip=${vt_clientip}
|
||||||
|
rightdns=${vt_gateway}
|
||||||
|
ike=aes128-sha1-modp2048,aes128-sha1-modp1024,3des-sha1-modp1024,3des-sha1-modp1536
|
||||||
|
esp=aes128-sha1,3des-sha1
|
||||||
|
|
||||||
|
# Cisco IPSec
|
||||||
|
conn IKEv1-PSK-XAuth
|
||||||
|
also=Non-L2TP
|
||||||
|
keyexchange=ikev1
|
||||||
|
leftauth=psk
|
||||||
|
rightauth=psk
|
||||||
|
rightauth2=xauth
|
||||||
|
|
||||||
|
$ipsec_l2tp_config
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > /etc/ipsec.secrets <<-EOF
|
||||||
|
# /etc/ipsec.secrets - strongSwan IPsec secrets file
|
||||||
|
: PSK "$vt_secret"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
local ipsec_users=$(get_enabled_anonymous_secs "@ipsec_users")
|
||||||
|
[ -n "${ipsec_users}" ] && {
|
||||||
|
for _user in ${ipsec_users}; do
|
||||||
|
local u_enabled=$(uci -q get ${CONFIG}.${_user}.enabled)
|
||||||
|
[ "${u_enabled}" -eq 1 ] || continue
|
||||||
|
|
||||||
|
local u_username=$(uci -q get ${CONFIG}.${_user}.username)
|
||||||
|
[ -n "${u_username}" ] || continue
|
||||||
|
|
||||||
|
local u_password=$(uci -q get ${CONFIG}.${_user}.password)
|
||||||
|
[ -n "${u_password}" ] || continue
|
||||||
|
|
||||||
|
echo "${u_username} : XAUTH '${u_password}'" >> ${IPSEC_SECRETS_FILE}
|
||||||
|
done
|
||||||
|
}
|
||||||
|
unset user
|
||||||
|
|
||||||
|
ipt_rule add
|
||||||
|
|
||||||
|
/usr/lib/ipsec/starter --daemon charon --nofork > /dev/null 2>&1 &
|
||||||
|
gen_include
|
||||||
|
|
||||||
|
uci -q batch <<-EOF >/dev/null
|
||||||
|
set network.ipsec_server.ipaddr="${vt_clientip%.*}.1"
|
||||||
|
commit network
|
||||||
|
EOF
|
||||||
|
ifup ipsec_server > /dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
ifdown ipsec_server > /dev/null 2>&1
|
||||||
|
sed -i '/l2tp-server/d' ${CHAP_SECRETS} 2>/dev/null
|
||||||
|
top -bn1 | grep "${L2TP_PATH}" | grep -v "grep" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1
|
||||||
|
rm -rf ${L2TP_PATH}
|
||||||
|
busybox ps -w | grep "/usr/lib/ipsec" | grep -v "grep" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1
|
||||||
|
ipt_rule del
|
||||||
|
rm -rf /var/etc/ipsecvpn.include
|
||||||
|
ln -s "libipsec.so.0.0.0" "/usr/lib/ipsec/libipsec.so.0" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
gen_iface_and_firewall() {
|
||||||
|
uci -q batch <<-EOF >/dev/null
|
||||||
|
delete network.ipsec_server
|
||||||
|
set network.ipsec_server=interface
|
||||||
|
set network.ipsec_server.ifname="ipsec0"
|
||||||
|
set network.ipsec_server.device="ipsec0"
|
||||||
|
set network.ipsec_server.proto="static"
|
||||||
|
set network.ipsec_server.ipaddr="${vt_clientip%.*}.1"
|
||||||
|
set network.ipsec_server.netmask="255.255.255.0"
|
||||||
|
commit network
|
||||||
|
|
||||||
|
delete firewall.ipsecserver
|
||||||
|
set firewall.ipsecserver=zone
|
||||||
|
set firewall.ipsecserver.name="ipsecserver"
|
||||||
|
set firewall.ipsecserver.input="ACCEPT"
|
||||||
|
set firewall.ipsecserver.forward="ACCEPT"
|
||||||
|
set firewall.ipsecserver.output="ACCEPT"
|
||||||
|
set firewall.ipsecserver.network="ipsec_server"
|
||||||
|
commit firewall
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -z "$(uci -q get network.ipsec_server)" ] || [ -z "$(uci -q get firewall.ipsecserver)" ]; then
|
||||||
|
gen_iface_and_firewall
|
||||||
|
fi
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
uci -q batch <<-EOF >/dev/null
|
||||||
|
delete firewall.luci_app_ipsec_server
|
||||||
|
set firewall.luci_app_ipsec_server=include
|
||||||
|
set firewall.luci_app_ipsec_server.type=script
|
||||||
|
set firewall.luci_app_ipsec_server.path=/var/etc/ipsecvpn.include
|
||||||
|
set firewall.luci_app_ipsec_server.reload=1
|
||||||
|
EOF
|
||||||
|
|
||||||
|
uci -q batch <<-EOF >/dev/null
|
||||||
|
delete ucitrack.@luci-app-ipsec-server[-1]
|
||||||
|
add ucitrack luci-app-ipsec-server
|
||||||
|
set ucitrack.@luci-app-ipsec-server[-1].init=luci-app-ipsec-server
|
||||||
|
commit ucitrack
|
||||||
|
EOF
|
||||||
|
|
||||||
|
/etc/init.d/ipsec disable 2>/dev/null
|
||||||
|
/etc/init.d/ipsec stop 2>/dev/null
|
||||||
|
/etc/init.d/xl2tpd disable 2>/dev/null
|
||||||
|
/etc/init.d/xl2tpd stop 2>/dev/null
|
||||||
|
rm -rf /tmp/luci-*cache
|
||||||
|
exit 0
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"luci-app-ipsec-server": {
|
||||||
|
"description": "Grant UCI access for luci-app-ipsec-server",
|
||||||
|
"read": {
|
||||||
|
"uci": [ "luci-app-ipsec-server" ]
|
||||||
|
},
|
||||||
|
"write": {
|
||||||
|
"uci": [ "luci-app-ipsec-server" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"config": "luci-app-ipsec-server",
|
||||||
|
"init": "luci-app-ipsec-server"
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
_LOGOUT_TIME="$(date "+%Y-%m-%d %H:%M:%S")"
|
||||||
|
CONFIG="luci-app-ipsec-server"
|
||||||
|
L2TP_PATH=/var/etc/xl2tpd
|
||||||
|
L2TP_SESSION_PATH=${L2TP_PATH}/session
|
||||||
|
|
||||||
|
_USERNAME=${PEERNAME}
|
||||||
|
_IFACE=${1}
|
||||||
|
_TTY=${2}
|
||||||
|
_SPEED=${3}
|
||||||
|
_LOCALIP=${4}
|
||||||
|
_PEERIP=${5}
|
||||||
|
_REMOTEIP=${6}
|
||||||
|
_BYTES_SENT=${BYTES_SENT}
|
||||||
|
_BYTES_RCVD=${BYTES_RCVD}
|
||||||
|
_CONNECT_TIME=${CONNECT_TIME}
|
||||||
|
|
||||||
|
rm -f ${L2TP_SESSION_PATH}/${_USERNAME}.${_IFACE}
|
||||||
|
rm -f /var/run/${_IFACE}.pid
|
||||||
|
|
||||||
|
#可根据退出的账号自定义脚本,如静态路由表,组网等。
|
||||||
|
SCRIPT="/usr/share/xl2tpd/ip-down.d/${_USERNAME}"
|
||||||
|
[ -s "$SCRIPT" ] && {
|
||||||
|
[ ! -x "$SCRIPT" ] && chmod 0755 "$SCRIPT"
|
||||||
|
"$SCRIPT" "$@"
|
||||||
|
}
|
||||||
+58
@@ -0,0 +1,58 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
_LOGIN_TIME="$(date "+%Y-%m-%d %H:%M:%S")"
|
||||||
|
CONFIG="luci-app-ipsec-server"
|
||||||
|
L2TP_PATH=/var/etc/xl2tpd
|
||||||
|
L2TP_SESSION_PATH=${L2TP_PATH}/session
|
||||||
|
|
||||||
|
_USERNAME=${PEERNAME}
|
||||||
|
_IFACE=${1}
|
||||||
|
_TTY=${2}
|
||||||
|
_SPEED=${3}
|
||||||
|
_LOCALIP=${4}
|
||||||
|
_PEERIP=${5}
|
||||||
|
|
||||||
|
_PID=$(cat /var/run/${_IFACE}.pid 2>/dev/null)
|
||||||
|
_REMOTEIP=$(cat /var/etc/xl2tpd/xl2tpd.log 2>/dev/null | grep "PID: ${_PID}" | grep -o -E '([0-9]{1,3}[\.]){3}[0-9]{1,3}')
|
||||||
|
|
||||||
|
mkdir -p ${L2TP_SESSION_PATH}
|
||||||
|
|
||||||
|
cat <<-EOF > ${L2TP_SESSION_PATH}/${_USERNAME}.${_IFACE}
|
||||||
|
{
|
||||||
|
"username": "${_USERNAME}",
|
||||||
|
"interface": "${_IFACE}",
|
||||||
|
"tty": "${_TTY}",
|
||||||
|
"speed": "${_SPEED}",
|
||||||
|
"ip": "${_PEERIP}",
|
||||||
|
"remote_ip": "${_REMOTEIP}",
|
||||||
|
"pid": "${_PID}",
|
||||||
|
"login_time": "${_LOGIN_TIME}"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
#只能单用户使用
|
||||||
|
cfgid=$(uci show ${CONFIG} | grep "@l2tp_users" | grep "\.username='${_USERNAME}'" | cut -d '.' -sf 2)
|
||||||
|
[ -n "$cfgid" ] && {
|
||||||
|
HAS_LOGIN=$(ls ${L2TP_SESSION_PATH} | grep "^${_USERNAME}\.ppp" | grep -v "${_IFACE}")
|
||||||
|
[ -n "$HAS_LOGIN" ] && {
|
||||||
|
#踢出之前的用户
|
||||||
|
KO_IFACE=$(echo $HAS_LOGIN | awk -F '.' '{print $2}')
|
||||||
|
KO_PID=$(cat /var/run/${KO_IFACE}.pid 2>/dev/null)
|
||||||
|
[ -n "$KO_PID" ] && kill -9 ${KO_PID} >/dev/null 2>&1
|
||||||
|
rm -f ${L2TP_SESSION_PATH}/${HAS_LOGIN}
|
||||||
|
rm -f /var/run/${KO_IFACE}.pid
|
||||||
|
}
|
||||||
|
routes=$(uci -q get ${CONFIG}.${cfgid}.routes)
|
||||||
|
[ -n "$routes" ] && {
|
||||||
|
for router in ${routes}; do
|
||||||
|
route add -net ${router} dev ${_IFACE} >/dev/null 2>&1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#可根据登录的账号自定义脚本,如组网、日志、限速、权限等特殊待遇。
|
||||||
|
SCRIPT="/usr/share/xl2tpd/ip-up.d/${_USERNAME}"
|
||||||
|
[ -s "$SCRIPT" ] && {
|
||||||
|
[ ! -x "$SCRIPT" ] && chmod 0755 "$SCRIPT"
|
||||||
|
"$SCRIPT" "$@"
|
||||||
|
}
|
||||||
@@ -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>.
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Copyright (C) 2018-2022 Lienol <lawlienol@gmail.com>
|
||||||
|
#
|
||||||
|
# This is free software, licensed under the GNU General Public License v3.
|
||||||
|
#
|
||||||
|
|
||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
|
LUCI_TITLE:=LuCI support for KodExplorer
|
||||||
|
LUCI_DEPENDS:=+nginx-ssl +unzip +zoneinfo-asia +php8 +php8-fastcgi +php8-fpm +php8-mod-curl +php8-mod-dom +php8-mod-gd +php8-mod-iconv +php8-mod-mbstring +php8-mod-opcache +php8-mod-session +php8-mod-zip +php8-mod-sqlite3 +php8-mod-pdo +php8-mod-pdo-sqlite +php8-mod-pdo-mysql +php8-mod-xml +php8-mod-xmlreader +php8-mod-xmlwriter
|
||||||
|
LUCI_PKGARCH:=all
|
||||||
|
PKG_VERSION:=20220109
|
||||||
|
PKG_RELEASE:=2
|
||||||
|
|
||||||
|
include $(TOPDIR)/feeds/luci/luci.mk
|
||||||
|
|
||||||
|
# call BuildPackage - OpenWrt buildroot signature
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
-- Copyright 2018-2022 Lienol <lawlienol@gmail.com>
|
||||||
|
module("luci.controller.kodexplorer", package.seeall)
|
||||||
|
|
||||||
|
local http = require "luci.http"
|
||||||
|
local api = require "luci.model.cbi.kodexplorer.api"
|
||||||
|
|
||||||
|
function index()
|
||||||
|
if not nixio.fs.access("/etc/config/kodexplorer") then return end
|
||||||
|
|
||||||
|
entry({"admin", "nas"}, firstchild(), "NAS", 44).dependent = false
|
||||||
|
|
||||||
|
e = entry({"admin", "nas", "kodexplorer"}, cbi("kodexplorer/settings"), _("KodExplorer"), 3)
|
||||||
|
e.dependent = true
|
||||||
|
e.acl_depends = { "luci-app-kodexplorer" }
|
||||||
|
|
||||||
|
entry({"admin", "nas", "kodexplorer", "check"}, call("action_check")).leaf = true
|
||||||
|
entry({"admin", "nas", "kodexplorer", "download"}, call("action_download")).leaf = true
|
||||||
|
entry({"admin", "nas", "kodexplorer", "status"}, call("act_status")).leaf = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function http_write_json(content)
|
||||||
|
http.prepare_content("application/json")
|
||||||
|
http.write_json(content or {code = 1})
|
||||||
|
end
|
||||||
|
|
||||||
|
function act_status()
|
||||||
|
local e = {}
|
||||||
|
e.nginx_status = luci.sys.call("busybox ps -w | grep nginx | grep kodexplorer | grep -v grep > /dev/null") == 0
|
||||||
|
e.php_status = luci.sys.call("busybox ps -w | grep php | grep kodexplorer | grep -v grep > /dev/null") == 0
|
||||||
|
http_write_json(e)
|
||||||
|
end
|
||||||
|
|
||||||
|
function action_check()
|
||||||
|
local json = api.to_check()
|
||||||
|
http_write_json(json)
|
||||||
|
end
|
||||||
|
|
||||||
|
function action_download()
|
||||||
|
local json = nil
|
||||||
|
local task = http.formvalue("task")
|
||||||
|
if task == "extract" then
|
||||||
|
json = api.to_extract(http.formvalue("file"))
|
||||||
|
elseif task == "move" then
|
||||||
|
json = api.to_move(http.formvalue("file"))
|
||||||
|
else
|
||||||
|
json = api.to_download(http.formvalue("url"))
|
||||||
|
end
|
||||||
|
http_write_json(json)
|
||||||
|
end
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
local fs = require "nixio.fs"
|
||||||
|
local sys = require "luci.sys"
|
||||||
|
local uci = require"luci.model.uci".cursor()
|
||||||
|
local util = require "luci.util"
|
||||||
|
local i18n = require "luci.i18n"
|
||||||
|
|
||||||
|
module("luci.model.cbi.kodexplorer.api", package.seeall)
|
||||||
|
|
||||||
|
local appname = "kodexplorer"
|
||||||
|
local api_url = "https://api.kodcloud.com/?app/version"
|
||||||
|
|
||||||
|
local wget = "/usr/bin/wget"
|
||||||
|
local wget_args = { "--no-check-certificate", "--quiet", "--timeout=10", "--tries=2" }
|
||||||
|
local command_timeout = 300
|
||||||
|
|
||||||
|
local function _unpack(t, i)
|
||||||
|
i = i or 1
|
||||||
|
if t[i] ~= nil then return t[i], _unpack(t, i + 1) end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function exec(cmd, args, writer, timeout)
|
||||||
|
local os = require "os"
|
||||||
|
local nixio = require "nixio"
|
||||||
|
|
||||||
|
local fdi, fdo = nixio.pipe()
|
||||||
|
local pid = nixio.fork()
|
||||||
|
|
||||||
|
if pid > 0 then
|
||||||
|
fdo:close()
|
||||||
|
|
||||||
|
if writer or timeout then
|
||||||
|
local starttime = os.time()
|
||||||
|
while true do
|
||||||
|
if timeout and os.difftime(os.time(), starttime) >= timeout then
|
||||||
|
nixio.kill(pid, nixio.const.SIGTERM)
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if writer then
|
||||||
|
local buffer = fdi:read(2048)
|
||||||
|
if buffer and #buffer > 0 then
|
||||||
|
writer(buffer)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local wpid, stat, code = nixio.waitpid(pid, "nohang")
|
||||||
|
|
||||||
|
if wpid and stat == "exited" then return code end
|
||||||
|
|
||||||
|
if not writer and timeout then nixio.nanosleep(1) end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local wpid, stat, code = nixio.waitpid(pid)
|
||||||
|
return wpid and stat == "exited" and code
|
||||||
|
end
|
||||||
|
elseif pid == 0 then
|
||||||
|
nixio.dup(fdo, nixio.stdout)
|
||||||
|
fdi:close()
|
||||||
|
fdo:close()
|
||||||
|
nixio.exece(cmd, args, nil)
|
||||||
|
nixio.stdout:close()
|
||||||
|
os.exit(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function get_project_directory()
|
||||||
|
return uci:get(appname, "@global[0]", "project_directory") or "/tmp/kodcloud"
|
||||||
|
end
|
||||||
|
|
||||||
|
function get_version()
|
||||||
|
local version = get_project_directory() .. "/config/version.php"
|
||||||
|
return sys.exec(string.format("echo -n $(cat %s 2>/dev/null | grep \"'KOD_VERSION'\" | cut -d \"'\" -f 4)", version))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function compare_versions(ver1, comp, ver2)
|
||||||
|
local table = table
|
||||||
|
|
||||||
|
local av1 = util.split(ver1, "[%.%-]", nil, true)
|
||||||
|
local av2 = util.split(ver2, "[%.%-]", nil, true)
|
||||||
|
|
||||||
|
local max = table.getn(av1)
|
||||||
|
local n2 = table.getn(av2)
|
||||||
|
if (max < n2) then max = n2 end
|
||||||
|
|
||||||
|
for i = 1, max, 1 do
|
||||||
|
local s1 = av1[i] or ""
|
||||||
|
local s2 = av2[i] or ""
|
||||||
|
|
||||||
|
if comp == "~=" and (s1 ~= s2) then return true end
|
||||||
|
if (comp == "<" or comp == "<=") and (s1 < s2) then return true end
|
||||||
|
if (comp == ">" or comp == ">=") and (s1 > s2) then return true end
|
||||||
|
if (s1 ~= s2) then return false end
|
||||||
|
end
|
||||||
|
|
||||||
|
return not (comp == "<" or comp == ">")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_api_json(url)
|
||||||
|
local jsonc = require "luci.jsonc"
|
||||||
|
|
||||||
|
local output = {}
|
||||||
|
-- exec(wget, { "-O-", url, _unpack(wget_args) },
|
||||||
|
-- function(chunk) output[#output + 1] = chunk end)
|
||||||
|
-- local json_content = util.trim(table.concat(output))
|
||||||
|
|
||||||
|
local json_content = sys.exec(wget .. " --no-check-certificate --timeout=10 -t 1 -O- " .. url)
|
||||||
|
|
||||||
|
if json_content == "" then return {} end
|
||||||
|
|
||||||
|
return jsonc.parse(json_content) or {}
|
||||||
|
end
|
||||||
|
|
||||||
|
function to_check()
|
||||||
|
local json = get_api_json(api_url)
|
||||||
|
return json
|
||||||
|
end
|
||||||
|
|
||||||
|
function to_download(url)
|
||||||
|
if not url or url == "" then
|
||||||
|
return {code = 1, error = i18n.translate("Download url is required.")}
|
||||||
|
end
|
||||||
|
|
||||||
|
sys.call("/bin/rm -f /tmp/kodcloud_download.*")
|
||||||
|
|
||||||
|
local tmp_file = util.trim(util.exec("mktemp -u -t kodcloud_download.XXXXXX"))
|
||||||
|
|
||||||
|
local result = exec(wget, {"-O", tmp_file, url, _unpack(wget_args)}, nil, command_timeout) == 0
|
||||||
|
|
||||||
|
if not result then
|
||||||
|
exec("/bin/rm", {"-f", tmp_file})
|
||||||
|
return {
|
||||||
|
code = 1,
|
||||||
|
error = i18n.translatef("File download failed or timed out: %s", url)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
return {code = 0, file = tmp_file}
|
||||||
|
end
|
||||||
|
|
||||||
|
function to_extract(file)
|
||||||
|
if not file or file == "" or not fs.access(file) then
|
||||||
|
return {code = 1, error = i18n.translate("File path required.")}
|
||||||
|
end
|
||||||
|
|
||||||
|
sys.call("/bin/rm -rf /tmp/kodcloud_extract.*")
|
||||||
|
local tmp_dir = util.trim(util.exec("mktemp -d -t kodcloud_extract.XXXXXX"))
|
||||||
|
|
||||||
|
local output = {}
|
||||||
|
exec("/usr/bin/unzip", {"-o", file, "-d", tmp_dir}, function(chunk) output[#output + 1] = chunk end)
|
||||||
|
|
||||||
|
local files = util.split(table.concat(output))
|
||||||
|
|
||||||
|
exec("/bin/rm", {"-f", file})
|
||||||
|
|
||||||
|
return {code = 0, file = tmp_dir}
|
||||||
|
end
|
||||||
|
|
||||||
|
function to_move(file)
|
||||||
|
if not file or file == "" or not fs.access(file) then
|
||||||
|
sys.call("/bin/rm -rf /tmp/kodcloud_extract.*")
|
||||||
|
return {code = 1, error = i18n.translate("Client file is required.")}
|
||||||
|
end
|
||||||
|
|
||||||
|
local client_file = get_project_directory()
|
||||||
|
sys.call("mkdir -p " .. client_file)
|
||||||
|
sys.call("cp -R " .. file .. "/* " .. client_file)
|
||||||
|
sys.call("/bin/rm -rf /tmp/kodcloud_extract.*")
|
||||||
|
|
||||||
|
return {code = 0}
|
||||||
|
end
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user