update 2025-02-17 23:37:02

This commit is contained in:
actions-user 2025-02-17 23:37:02 +08:00
parent 7ab7037b1a
commit 26ecd0ecc7
50 changed files with 2037 additions and 0 deletions

47
floatip/Makefile Normal file
View File

@ -0,0 +1,47 @@
#
# Copyright (C) 2024 jjm2473 <jjm2473@gmail.com>
#
# This is free software, licensed under the MIT License.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=floatip
PKG_VERSION:=1.0.8
PKG_RELEASE:=1
PKG_MAINTAINER:=jjm2473 <jjm2473@gmail.com>
include $(INCLUDE_DIR)/package.mk
define Package/$(PKG_NAME)
SECTION:=net
CATEGORY:=Network
SUBMENU:=IP Addresses and Names
TITLE:=Float IP
DEPENDS:=+curl
PKGARCH:=all
endef
define Package/$(PKG_NAME)/description
Auto setup an IP if some host down
endef
define Package/$(PKG_NAME)/conffiles
/etc/config/floatip
endef
define Build/Configure
endef
define Build/Compile
endef
define Package/$(PKG_NAME)/install
$(INSTALL_DIR) $(1)/usr/libexec $(1)/etc/init.d $(1)/etc/config $(1)/etc/uci-defaults
$(INSTALL_BIN) ./files/floatip.sh $(1)/usr/libexec/floatip.sh
$(INSTALL_BIN) ./files/floatip.init $(1)/etc/init.d/floatip
$(INSTALL_CONF) ./files/floatip.config $(1)/etc/config/floatip
$(INSTALL_BIN) ./files/floatip.uci-default $(1)/etc/uci-defaults/floatip
endef
$(eval $(call BuildPackage,$(PKG_NAME)))

View File

@ -0,0 +1,17 @@
config floatip 'main'
# 启动时enabled != 1 ,或者原 lan 口配置网段不包括 set_ip清除自身的 set_ip然后退出进程。
option enabled '0'
# fallback 表示后备
option role 'fallback'
# option role 'main'
# 对于 fallback 节点,检查到 check_ip 都不在线超过一定时间例如30秒就设置自身的 set_ip然后检查 check_ip 中任一 IP 在线就清除自身的 set_ip重复上述流程。
# 对于 main 节点,启动后不断检查 set_ip 和 check_url直到 set_ip 不在线且 check_url 没有失败,就设置自身的 set_ip 并允许 LAN 口 ping否则清除自身的 set_ip 并禁止 LAN 口 ping重复上述流程。
# set_ip 可以不提供前缀长度,将会按 lan 口配置的网段的长度
option set_ip '192.168.100.3/24'
# option set_ip '192.168.100.3'
# check_ip 仅 fallback 有效,并且检查时只检查跟 set_ip 同一网段的
list check_ip '192.168.100.2'
# list check_ip '192.168.100.4'
# check_url 仅 main 有效
# list check_url 'https://www.google.com/generate_204'
# option check_url_timeout '5'

67
floatip/files/floatip.init Executable file
View File

@ -0,0 +1,67 @@
#!/bin/sh /etc/rc.common
START=98
USE_PROCD=1
enable_lan_ping() {
uci -q set firewall.floatip_lan_offline.enabled=0 || return 0
uci changes | grep -Fq 'firewall.floatip_lan_offline.enabled' || return 0
uci commit firewall
/etc/init.d/firewall reload
}
start_service() {
config_load floatip
config_get_bool enabled "main" enabled 0
ifdown floatip
[[ "$enabled" = 1 ]] || {
enable_lan_ping
return 0
}
[[ "`uci -q get network.lan.proto`" = "static" ]] || return 0
local set_ip set_prefix
config_get set_ip "main" set_ip
[[ -n "$set_ip" ]] || return 0
if [[ "$set_ip" = "*/*" ]]; then
eval "$(ipcalc.sh "$set_ip" )";set_prefix=$PREFIX;set_ip=$IP
else
set_prefix=32
fi
local lan_ip="`uci -q get network.lan.ipaddr`"
[[ -n "$lan_ip" ]] || return 0
local lan_net lan_prefix set_net ip
local in_range=0
local lan_netmask="`uci -q get network.lan.netmask`"
for ip in $lan_ip; do
if [[ "$ip" = "*/*" ]]; then
eval "$(ipcalc.sh $ip )";lan_net=$NETWORK;lan_prefix=$PREFIX
else
# prefix=32 if not present
[[ -n "$lan_netmask" ]] || continue
eval "$(ipcalc.sh $ip $lan_netmask )";lan_net=$NETWORK;lan_prefix=$PREFIX
fi
[[ "$set_prefix" -ge "$lan_prefix" ]] || continue
eval "$(ipcalc.sh $set_ip/$lan_prefix )";set_net=$NETWORK
[[ "$set_net" = "$lan_net" ]] && {
[[ "$set_prefix" = 32 ]] && set_prefix=$lan_prefix
in_range=1
break
}
done
[[ $in_range = 1 ]] || return 0
procd_open_instance
procd_set_param command /usr/libexec/floatip.sh "$set_prefix"
procd_set_param stderr 1
procd_set_param file /etc/config/floatip
procd_close_instance
}
stop_service() {
enable_lan_ping
ifdown floatip
}
service_triggers() {
procd_add_reload_trigger "network" "floatip"
}

223
floatip/files/floatip.sh Executable file
View File

@ -0,0 +1,223 @@
#!/bin/sh
DEFAULT_PREFIX=24
# random number 0-255
random() {
local num=$(dd if=/dev/urandom bs=1 count=1 2>/dev/null | hexdump -ve '1/1 "%u"')
if [[ -z "$num" ]]; then
num=$(($(grep -om1 '[0-9][0-9]$' /proc/uptime) * 255 / 100))
fi
echo ${num:-1}
}
# check host alive, timeout in 2 seconds
host_alive() {
ping -4 -c 2 -A -t 1 -W 1 -q "$1" >/dev/null
# arping -f -q -b -c 2 -w 2 -i 1 -I br-lan "$1"
}
set_up() {
local ipaddr="$1"
echo "set my floatip to $ipaddr" >&2
if ! uci -q get network.floatip.ipaddr | grep -Fwq $ipaddr; then
if [[ "x$(uci -q get network.floatip)" = xinterface ]]; then
uci -q batch <<-EOF >/dev/null
delete network.floatip.ipaddr
add_list network.floatip.ipaddr=$ipaddr
EOF
else
uci -q batch <<-EOF >/dev/null
set network.floatip=interface
set network.floatip.proto=static
add_list network.floatip.ipaddr=$ipaddr
set network.floatip.device=br-lan
set network.floatip.auto=0
EOF
fi
uci commit network
fi
ifup floatip
}
set_lan_ping() {
if [[ "$1" = 0 ]]; then
if [[ "x$(uci -q get firewall.floatip_lan_offline)" = xrule ]]; then
uci -q delete firewall.floatip_lan_offline.enabled
uci changes | grep -Fq 'firewall.floatip_lan_offline.enabled' || return 0
else
uci -q batch <<-EOF >/dev/null
set firewall.floatip_lan_offline=rule
set firewall.floatip_lan_offline.name=FloatIP-LAN-Offline
set firewall.floatip_lan_offline.src=lan
set firewall.floatip_lan_offline.proto=icmp
set firewall.floatip_lan_offline.icmp_type=echo-request
set firewall.floatip_lan_offline.family=ipv4
set firewall.floatip_lan_offline.target=DROP
EOF
fi
else
uci -q set firewall.floatip_lan_offline.enabled=0 || return 0
uci changes | grep -Fq 'firewall.floatip_lan_offline.enabled' || return 0
fi
uci commit firewall
/etc/init.d/firewall reload 2>&1
}
safe_sleep() {
local sec="$1"
[[ "$sec" -lt 1 ]] && sec=1
sleep $sec
}
. /lib/functions.sh
fallback_loop() {
local set_ip check_ip set_net set_prefix
config_get set_ip "main" set_ip
[[ -n "$set_ip" ]] || return 1
[[ "$set_ip" = "*/*" ]] || set_ip="$set_ip/$DEFAULT_PREFIX"
eval "$(ipcalc.sh "$set_ip" )";set_net=$NETWORK;set_prefix=$PREFIX;set_ip=$IP
local ipaddr="$set_ip/$set_prefix"
echo "ipaddr=$ipaddr"
local valid_check_ip cip
config_get check_ip "main" check_ip
for cip in $check_ip; do
eval "$(ipcalc.sh $cip/$set_prefix )"
[[ "$NETWORK" = "$set_net" ]] && valid_check_ip="$valid_check_ip $cip"
done
valid_check_ip="$valid_check_ip "
local order_check_ip="$valid_check_ip"
local found_alive consume_time
local dead_counter=0 floatip_up=0
while :; do
found_alive=0
consume_time=0
echo "checking host(s) $order_check_ip alive"
for cip in $order_check_ip; do
if host_alive $cip; then
echo "host $cip alive"
found_alive=1
# reorder to reduce check time
order_check_ip=" ${cip}${valid_check_ip// $cip / }"
break
fi
consume_time=$(($consume_time + 2))
done
if [[ $found_alive = 1 ]]; then
if [[ $floatip_up = 1 ]]; then
echo "set down floatip" >&2
ifdown floatip
floatip_up=0
else
dead_counter=0
fi
safe_sleep $((10 - $consume_time))
continue
fi
if [[ $floatip_up = 1 ]]; then
safe_sleep $((5 - $consume_time))
continue
fi
dead_counter=$(($dead_counter + 1))
if [[ $dead_counter -lt 3 ]]; then
safe_sleep $((10 - $consume_time))
continue
fi
echo "no host alive, set up floatip $ipaddr" >&2
set_up "$ipaddr"
floatip_up=1
sleep 5
done
}
main_loop() {
local set_ip set_prefix
config_get set_ip "main" set_ip
[[ -n "$set_ip" ]] || return 1
[[ "$set_ip" = "*/*" ]] || set_ip="$set_ip/$DEFAULT_PREFIX"
eval "$(ipcalc.sh "$set_ip" )";set_prefix=$PREFIX;set_ip=$IP
local ipaddr="$set_ip/$set_prefix"
echo "ipaddr=$ipaddr"
local check_urls check_url_timeout
config_get check_urls "main" check_url
config_get check_url_timeout "main" check_url_timeout '5'
local dead_counter=0 floatip_up=0 url_pass check_url curl_code consume_time found_alive
# sleep 2-6s
sleep $(( $(random) / 60 + 2))
while :; do
consume_time=0
if [[ $floatip_up = 0 ]]; then
found_alive=0
echo "checking host $set_ip alive"
if host_alive $set_ip; then
echo "host $set_ip alive"
found_alive=1
else
consume_time=$(($consume_time + 2))
fi
fi
url_pass=1
for check_url in $check_urls ; do
curl -L --fail --show-error --no-progress-meter -o /dev/null \
--connect-timeout "$check_url_timeout" --max-time "$check_url_timeout" \
-I "$check_url" 2>&1
curl_code=$?
[[ $curl_code = 0 ]] && continue
[[ $curl_code = 6 || $curl_code = 7 || $curl_code = 28 ]] && \
consume_time=$(($consume_time + $check_url_timeout))
echo "check_url $check_url fail, code $curl_code"
url_pass=0
break
done
if [[ $floatip_up = 0 ]]; then
if [[ $url_pass = 1 ]]; then
# notify fallback node to offline
set_lan_ping
if [[ $found_alive = 0 ]]; then
echo "no host alive, and url passed, set up floatip $ipaddr" >&2
set_up "$ipaddr"
floatip_up=1
fi
else
set_lan_ping 0
fi
safe_sleep $((5 - $consume_time))
continue
else
if [[ $url_pass = 0 ]]; then
dead_counter=$(($dead_counter + 1))
if [[ $dead_counter -lt 3 ]]; then
safe_sleep $((5 - $consume_time))
continue
fi
echo "set down floatip, and disable ping" >&2
ifdown floatip
set_lan_ping 0
floatip_up=0
fi
dead_counter=0
fi
sleep 20
done
}
main() {
local role
config_load floatip
config_get role "main" role
if [[ "$role" = "main" ]]; then
main_loop
elif [[ "$role" = "fallback" ]]; then
fallback_loop
fi
}
if [[ -n "$1" ]]; then
[[ "$1" -ge 0 && "$1" -lt 32 ]] && DEFAULT_PREFIX=$1
fi
main

View File

@ -0,0 +1,14 @@
uci -q batch <<-EOF >/dev/null
delete ucitrack.@floatip[-1]
add ucitrack floatip
set ucitrack.@floatip[-1].init=floatip
commit ucitrack
EOF
[[ "`uci -q get network.lan.proto`" = "static" && -n "`uci -q get network.lan.gateway`" ]] || exit 0
uci -q batch <<-EOF >/dev/null
set floatip.main.role=main
commit floatip
EOF

69
linkease/Makefile Normal file
View File

@ -0,0 +1,69 @@
#
# Copyright (C) 2015-2016 OpenWrt.org
# Copyright (C) 2020 jjm2473@gmail.com
#
# This is free software, licensed under the GNU General Public License v3.
#
include $(TOPDIR)/rules.mk
PKG_ARCH_LINKEASE:=$(ARCH)
PKG_NAME:=linkease
PKG_VERSION:=1.6.9
PKG_RELEASE:=$(PKG_ARCH_LINKEASE)-2
PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=http://fw0.koolcenter.com/binary/LinkEase/LinuxStorage/
PKG_HASH:=09d17d4c41e27c4f3e4b3a722e589c78e09b2124ea377c44a3d7af06005e8012
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-binary-$(PKG_VERSION)
PKG_BUILD_PARALLEL:=1
PKG_USE_MIPS16:=0
include $(INCLUDE_DIR)/package.mk
define Package/$(PKG_NAME)
SECTION:=net
CATEGORY:=Network
SUBMENU:=Web Servers/Proxies
TITLE:=LinkEase - the file cloud
DEPENDS:=@(arm||x86_64||aarch64) +linkmount
PKGARCH:=all
URL:=https://www.linkease.com/
endef
define Package/$(PKG_NAME)/description
LinkEase is a file cloud
endef
define Package/$(PKG_NAME)/conffiles
/etc/config/linkease
endef
define Package/$(PKG_NAME)/postinst
#!/bin/sh
if [ -z "$${IPKG_INSTROOT}" ]; then
[ -f /etc/uci-defaults/linkease ] && /etc/uci-defaults/linkease && rm -f /etc/uci-defaults/linkease
exit 0
fi
endef
define Build/Configure
endef
define Build/Compile
endef
define Package/$(PKG_NAME)/install
$(INSTALL_DIR) $(1)/usr/sbin/linkease-plugins $(1)/etc/config $(1)/etc/init.d $(1)/etc/uci-defaults
$(INSTALL_BIN) $(PKG_BUILD_DIR)/linkease.$(PKG_ARCH_LINKEASE) $(1)/usr/sbin/linkease
$(INSTALL_BIN) $(PKG_BUILD_DIR)/heif-converter.$(PKG_ARCH_LINKEASE) $(1)/usr/sbin/heif-converter
$(INSTALL_BIN) $(PKG_BUILD_DIR)/linkease-media.$(PKG_ARCH_LINKEASE) $(1)/usr/sbin/linkease-media
$(INSTALL_BIN) ./files/linkease-config.sh $(1)/usr/sbin/linkease-config.sh
$(INSTALL_CONF) ./files/linkease.config $(1)/etc/config/linkease
$(INSTALL_BIN) ./files/linkease.init $(1)/etc/init.d/linkease
$(INSTALL_BIN) ./files/linkease.uci-default $(1)/etc/uci-defaults/linkease
endef
$(eval $(call BuildPackage,$(PKG_NAME)))

134
linkease/files/aria2.sh Executable file
View File

@ -0,0 +1,134 @@
#!/bin/bash
sh_ver="1.0.0"
export PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/sbin:/bin
aria2_conf_dir=/var/etc/aria2/
#替换成你设备aria2.conf路径
aria2_conf=${aria2_conf_dir}/aria2.conf.main
#替换成你设备的aria2c路径
aria2c=/usr/bin/aria2c
Green_font_prefix="\033[32m"
Red_font_prefix="\033[31m"
Green_background_prefix="\033[42;37m"
Red_background_prefix="\033[41;37m"
Font_color_suffix="\033[0m"
Info="[${Green_font_prefix}信息${Font_color_suffix}]"
Error="[${Red_font_prefix}错误${Font_color_suffix}]"
Tip="[${Green_font_prefix}注意${Font_color_suffix}]"
error_code=11
success_code=0
return_error(){
echo 'Content-Type:application/json;charset=utf-8'
echo
echo "{
"\"success\"":$error_code,
"\"error\"":"\"$1\"",
"\"result"\":null
}"
exit 1
}
return_ok(){
echo 'Content-Type:application/json;charset=utf-8'
echo
echo "{
"\"success\"":$success_code,
"\"error\"":"\"$1\"",
"\"result"\":null
}"
exit 0
}
return_result(){
echo 'Content-Type:application/json;charset=utf-8'
echo
echo "{
"\"success\"":$success_code,
"\"error\"":"\"\"",
"\"result"\":$1
}"
exit 0
}
#进程中是否运行aria2
check_pid() {
PID=$(ps -ef | grep "aria2c" | grep -v grep | grep -v "aria2.sh" | grep -v "init.d" | grep -v "service" | awk '{print $2}')
}
#aria2是否正在运行
aria2_work_status(){
check_pid
# [[ ! -z ${PID} ]] && echo -e "${Error} Aria2 正在运行,请检查 !" && exit 1
[[ ! -z ${PID} ]] && return_ok "Aria2正在运行"
return_error "Aria2未运行"
}
#检测设备是否安装aria2
check_installed_status() {
[[ ! -e ${aria2c} ]] && return_error "Aria2 没有安装,请检查 !"
[[ ! -e ${aria2_conf} ]] && return_error "Aria2 配置文件不存在,请检查 !"
# return_ok "Aria2已安装"
}
#读取aria2配置信息
read_config() {
check_installed_status
if [[ ! -e ${aria2_conf} ]]; then
return_error "Aria2 配置文件不存在,请检查 !"
else
conf_text=$(cat ${aria2_conf} | grep -v '#')
aria2_dir=$(echo -e "${conf_text}" | grep "^dir=" | awk -F "=" '{print $NF}')
aria2_port=$(echo -e "${conf_text}" | grep "^rpc-listen-port=" | awk -F "=" '{print $NF}')
aria2_passwd=$(echo -e "${conf_text}" | grep "^rpc-secret=" | awk -F "=" '{print $NF}')
aria2_bt_port=$(echo -e "${conf_text}" | grep "^listen-port=" | awk -F "=" '{print $NF}')
aria2_dht_port=$(echo -e "${conf_text}" | grep "^dht-listen-port=" | awk -F "=" '{print $NF}')
return_result "{
"\"dir"\":"\"$aria2_dir"\",
"\"rpc-listen-port"\":"\"$aria2_port"\",
"\"rpc-secret"\":"\"$aria2_passwd"\",
"\"listen-port"\":"\"$aria2_bt_port"\",
"\"dht-listen-port"\":"\"$aria2_dht_port"\"}"
fi
}
#"Content-Type:text/html;charset=utf-8"
#echo
#SERVER_SOFTWARE = $SERVER_SOFTWARE #服务器软件
#SERVER_NAME = $SERVER_NAME #服务器主机名
#GATEWAY_INTERFACE = $GATEWAY_INTERFACE #CGI版本
#SERVER_PROTOCOL = $SERVER_PROTOCOL #通信使用的协议
#SERVER_PORT = $SERVER_PORT #服务器的端口号
#REQUEST_METHOD = $REQUEST_METHOD #请求方法(GET/POST/PUT/DELETE..)
#HTTP_ACCEPT = $HTTP_ACCEPT #HTTP定义的浏览器能够接受的数据类型
#SCRIPT_NAME = $SCRIPT_NAME #当前运行的脚本名称(包含路径)
#QUERY_STRING = $QUERY_STRING #地址栏中传的数据get方式
#REMOTE_ADDR = $REMOTE_ADDR #客户端的ip
#根据url QUERY调不同方法
query(){
aria2Query=${QUERY_STRING}
parse(){
echo $1 | sed 's/.*'$2'=\([[:alnum:]]*\).*/\1/'
}
value=$(parse $aria2Query "action")
if [ ! -z = "$value" ]
then
if [ "$value" = "status" ]
then
check_installed_status
elif [ "$value" = "readConfig" ]
then
read_config
elif [ "$value" = "workStatus" ]
then
aria2_work_status
else
echo
fi
else
return_error "action不能为空"
fi
}
query

View File

@ -0,0 +1,89 @@
#!/bin/sh
source /lib/functions.sh
case "$1" in
save)
if [ ! -z "$2" ]; then
uci set "linkease.@linkease[0].preconfig=$2"
uci commit
fi
;;
load)
if [ -f "/usr/sbin/preconfig.data" ]; then
data="`cat /usr/sbin/preconfig.data`"
uci set "linkease.@linkease[0].preconfig=${data}"
uci commit
rm /usr/sbin/preconfig.data
else
data="`uci -q get linkease.@linkease[0].preconfig`"
fi
if [ -z "${data}" ]; then
echo "nil"
else
echo "${data}"
fi
;;
local_save)
if [ ! -z "$2" ]; then
uci set "linkease.@linkease[0].local_home=$2"
uci commit
ROOT_DIR="$2"
if [ -f "/etc/config/quickstart" ]; then
config_load quickstart
config_get MAIN_DIR main main_dir ""
config_get CONF_DIR main conf_dir ""
config_get PUB_DIR main pub_dir ""
config_get DL_DIR main dl_dir ""
config_get TMP_DIR main tmp_dir ""
# echo "$MAIN_DIR $CONF_DIR $PUB_DIR $DL_DIR $TMP_DIR"
if [ "$ROOT_DIR" = "$MAIN_DIR" ]; then
exit 0
fi
uci set "quickstart.main.main_dir=$ROOT_DIR"
if [ -z "$CONF_DIR" -o "$CONF_DIR" = "$MAIN_DIR/Configs" ]; then
uci set "quickstart.main.conf_dir=$ROOT_DIR/Configs"
fi
if [ -z "$PUB_DIR" -o "$PUB_DIR" = "$MAIN_DIR/Public" ]; then
uci set "quickstart.main.pub_dir=$ROOT_DIR/Public"
fi
if [ -z "$DL_DIR" -o "$DL_DIR" = "$MAIN_DIR/Public/Downloads" ]; then
uci set "quickstart.main.dl_dir=$ROOT_DIR/Public/Downloads"
fi
if [ -z "$TMP_DIR" -o "$TMP_DIR" = "$MAIN_DIR/Caches" ]; then
uci set "quickstart.main.tmp_dir=$ROOT_DIR/Caches"
fi
uci commit
fi
fi
;;
local_load)
if [ -f "/etc/config/quickstart" ]; then
data="`uci -q get quickstart.main.main_dir`"
fi
if [ -z "$data" ]; then
data="`uci -q get linkease.@linkease[0].local_home`"
fi
if [ -z "${data}" ]; then
echo "nil"
else
echo "${data}"
fi
;;
status)
echo "TODO"
;;
*)
echo "Usage: $0 {save|load|status}"
exit 1
esac

View File

@ -0,0 +1,3 @@
config linkease
option port '8897'
option enabled '1'

28
linkease/files/linkease.init Executable file
View File

@ -0,0 +1,28 @@
#!/bin/sh /etc/rc.common
START=99
USE_PROCD=1
get_config() {
config_get_bool enabled $1 enabled 1
config_get_bool logger $1 logger
config_get port $1 port 8897
}
start_service() {
config_load linkease
config_foreach get_config linkease
[ $enabled != 1 ] && return 1
procd_open_instance
procd_set_param limits nofile="65535 65535"
procd_set_param command /usr/sbin/linkease
[ -n "$port" ] && procd_append_param command --deviceAddr ":$port" --localApi /var/run/linkease.sock
[ "$logger" == 1 ] && procd_set_param stderr 1
procd_set_param respawn
procd_close_instance
}
service_triggers() {
procd_add_reload_trigger "linkease"
}

View File

@ -0,0 +1,22 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
delete ucitrack.@linkease[-1]
add ucitrack linkease
set ucitrack.@linkease[-1].init=linkease
commit ucitrack
delete firewall.linkease
set firewall.linkease=rule
set firewall.linkease.name="linkease"
set firewall.linkease.target="ACCEPT"
set firewall.linkease.src="wan"
set firewall.linkease.proto="tcp"
set firewall.linkease.dest_port="8897"
commit firewall
EOF
/etc/init.d/linkease enable
/etc/init.d/linkease start
exit 0

18
luci-app-floatip/Makefile Normal file
View File

@ -0,0 +1,18 @@
# Copyright (C) 2016 Openwrt.org
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI support for floatip
LUCI_DEPENDS:=+floatip
LUCI_PKGARCH:=all
PKG_VERSION:=0.1.2-1
# PKG_RELEASE MUST be empty for luci.mk
PKG_RELEASE:=
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -0,0 +1,24 @@
module("luci.controller.floatip", package.seeall)
function index()
if not nixio.fs.access("/etc/config/floatip") then
return
end
entry({"admin", "services", "floatip"}, cbi("floatip"), _("FloatingGateway"), 20).dependent = true
entry({"admin", "services", "floatip_status"}, call("floatip_status"))
end
function floatip_status()
local sys = require "luci.sys"
local uci = require "luci.model.uci".cursor()
local status = {
running = (sys.call("pidof floatip.sh >/dev/null") == 0),
}
luci.http.prepare_content("application/json")
luci.http.write_json(status)
end

View File

@ -0,0 +1,34 @@
local m, s, o
m = Map("floatip", translate("FloatingGateway"), translate("FloatingGateway allows two gateway within one lan which can switch between each other in case of a failure."))
m:section(SimpleSection).template = "floatip_status"
s=m:section(NamedSection, "main", translate("Global settings"))
s.anonymous=true
o = s:option(Flag, "enabled", translate("Enable"))
o.rmempty = false
o = s:option(ListValue, "role", translate("Node Role"))
o.rmempty = false
o.widget = "select"
o:value("main", translate("Preempt Node"))
o:value("fallback", translate("Fallback Node"))
o = s:option(Value, "set_ip", translate("Floating Gateway IP"))
o.rmempty = false
o.datatype = "or(ip4addr,cidr4)"
o = s:option(Value, "check_ip", translate("Preempt Node IP"))
o.datatype = "ip4addr"
o:depends("role", "fallback")
o = s:option(Value, "check_url", translate("Check URL"), translate("If status code of the URL is not 2xx, then release the floating IP and disable LAN port pinging"))
o:depends("role", "main")
o = s:option(Value, "check_url_timeout", translate("Check URL Timeout (s)"), translate("Default is 5 seconds if not set"))
o.datatype = "uinteger"
o:depends("role", "main")
return m

View File

@ -0,0 +1,26 @@
<script type="text/javascript">//<![CDATA[
XHR.poll(5, '<%=url("admin/services/floatip_status")%>', null,
function(x, st)
{
var tb = document.getElementById('floatip_status');
if (st && tb)
{
if (st.running)
{
tb.innerHTML = '<br/><em style=\"color:green\"><%:The FloatingGateway service is running.%></em>';
}
else
{
tb.innerHTML = '<br/><em style=\"color:red\"><%:The FloatingGateway service is not running.%></em>';
}
}
}
);
//]]></script>
<fieldset class="cbi-section">
<legend><%:FloatingGateway Status%></legend>
<p id="floatip_status">
<em><%:Collecting data...%></em>
</p>
</fieldset>

View File

@ -0,0 +1,47 @@
msgid "FloatingGateway"
msgstr "浮动网关"
msgid "Running state"
msgstr "运行状态"
msgid "FloatingGateway allows two gateway within one lan which can switch between each other in case of a failure."
msgstr "浮动网关可以让你在内网有两个相互备份的网关,出现问题会相互切换。"
msgid "The FloatingGateway service is running."
msgstr "服务已启动"
msgid "The FloatingGateway service is not running."
msgstr "服务未启动"
msgid "FloatingGateway Status"
msgstr "服务状态"
msgid "Collecting data..."
msgstr "收集数据..."
msgid "Node Role"
msgstr "节点角色"
msgid "Preempt Node"
msgstr "旁路由"
msgid "Fallback Node"
msgstr "主路由"
msgid "Floating Gateway IP"
msgstr "浮动网关 IP"
msgid "Preempt Node IP"
msgstr "旁路由 IP"
msgid "Check URL"
msgstr "检查 URL"
msgid "If status code of the URL is not 2xx, then release the floating IP and disable LAN port pinging"
msgstr "如果检查URL状态码不是2xx则释放浮动IP并禁止LAN口ping"
msgid "Check URL Timeout (s)"
msgstr "检查 URL 超时(秒)"
msgid "Default is 5 seconds if not set"
msgstr "默认5秒"

1
luci-app-floatip/po/zh_Hans Symbolic link
View File

@ -0,0 +1 @@
zh-cn

View File

@ -0,0 +1,20 @@
# 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 linkease
LUCI_DEPENDS:=+linkease
LUCI_PKGARCH:=all
PKG_VERSION:=2.1.12-2
# PKG_RELEASE MUST be empty for luci.mk
PKG_RELEASE:=
LUCI_MINIFY_CSS:=0
LUCI_MINIFY_JS:=0
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -0,0 +1 @@
import{ab as L,i as v,ac as M}from"./index.js";var N=/\s/;function $(n){for(var r=n.length;r--&&N.test(n.charAt(r)););return r}var B=/^\s+/;function R(n){return n&&n.slice(0,$(n)+1).replace(B,"")}var k=0/0,F=/^[-+]0x[0-9a-f]+$/i,_=/^0b[01]+$/i,j=/^0o[0-7]+$/i,D=parseInt;function S(n){if(typeof n=="number")return n;if(L(n))return k;if(v(n)){var r=typeof n.valueOf=="function"?n.valueOf():n;n=v(r)?r+"":r}if(typeof n!="string")return n===0?n:+n;n=R(n);var t=_.test(n);return t||j.test(n)?D(n.slice(2),t?2:8):F.test(n)?k:+n}var H=function(){return M.Date.now()};const h=H;var P="Expected a function",U=Math.max,X=Math.min;function z(n,r,t){var u,c,l,s,i,f,o=0,b=!1,d=!1,T=!0;if(typeof n!="function")throw new TypeError(P);r=S(r)||0,v(t)&&(b=!!t.leading,d="maxWait"in t,l=d?U(S(t.maxWait)||0,r):l,T="trailing"in t?!!t.trailing:T);function x(e){var a=u,m=c;return u=c=void 0,o=e,s=n.apply(m,a),s}function W(e){return o=e,i=setTimeout(g,r),b?x(e):s}function O(e){var a=e-f,m=e-o,E=r-a;return d?X(E,l-m):E}function p(e){var a=e-f,m=e-o;return f===void 0||a>=r||a<0||d&&m>=l}function g(){var e=h();if(p(e))return y(e);i=setTimeout(g,O(e))}function y(e){return i=void 0,T&&u?x(e):(u=c=void 0,s)}function A(){i!==void 0&&clearTimeout(i),o=0,u=f=c=i=void 0}function C(){return i===void 0?s:y(h())}function I(){var e=h(),a=p(e);if(u=arguments,c=this,f=e,a){if(i===void 0)return W(f);if(d)return clearTimeout(i),i=setTimeout(g,r),x(f)}return i===void 0&&(i=setTimeout(g,r)),s}return I.cancel=A,I.flush=C,I}export{z as d};

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
import{e as t,r as a,F as _,y as s,S as o,q as n,a7 as c}from"./index.js";const r={class:"layout-conatiner"},d={class:"layout-f"},l={class:"layout-r"},i=t({__name:"index",setup(u){return a(!0),(e,p)=>(n(),_("div",r,[s("div",d,[o(e.$slots,"f",{},void 0,!0)]),s("div",l,[o(e.$slots,"r",{},void 0,!0)])]))}});const v=c(i,[["__scopeId","data-v-cbf72d2b"]]);export{v as l};

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
var g=(e,t,s)=>new Promise((a,l)=>{var c=i=>{try{n(s.next(i))}catch(d){l(d)}},u=i=>{try{n(s.throw(i))}catch(d){l(d)}},n=i=>i.done?a(i.value):Promise.resolve(i.value).then(c,u);n((s=s.apply(e,t)).next())});import{a8 as h,a9 as y,aa as f}from"./index.js";const o=e=>["linkease-file_web",e].join("/"),r={model:{get(){return localStorage.getItem(o("model"))=="true"},set(e){localStorage.setItem(o("model"),e)}},order:{get(){const e=localStorage.getItem(o("order"));return e||"date"},set(e){localStorage.setItem(o("order"),e)}},orderBy:{get(){return localStorage.getItem(o("orderBy"))||"asc"},set(e){localStorage.setItem(o("orderBy"),e)}},filter:{get(){return localStorage.getItem(o("filter"))||"default"},set(e){localStorage.setItem(o("filter"),e)}},copy:{get(){let e={type:"copy",values:[]};try{const t=localStorage.getItem(o("copy"));t&&(e=JSON.parse(t))}catch(t){}return e},set(e){const t=JSON.stringify(e);localStorage.setItem(o("copy"),t)}}},m=h("app",{state:()=>({keys:0,config:{model:r.model.get(),order:r.order.get(),orderBy:r.orderBy.get(),filter:r.filter.get()},menus:[],entries:[],checknoxEntries:[],previewImage:{enable:!1,values:[]},copy:r.copy.get(),task:0}),getters:{isCopy(){var e,t;return((t=(e=this.copy)==null?void 0:e.values)==null?void 0:t.length)>0}},actions:{reload(){this.keys++},taskIncr(e){e===void 0?this.task=0:this.task+=e},getEntries(){return g(this,null,function*(){try{const e=yield y.File.Basic.List.POST({path:"/local"});if(e.data){const{success:t,result:s,error:a}=e.data;t==0&&(this.menus=s.entries||[]),a&&f.Warning(a)}}catch(e){console.log(e),f.Error(`${e}`)}})},setConfigModel(){this.config.model=!this.config.model,r.model.set(`${this.config.model}`)},setConfigOrder(e){this.config.order=e,r.order.set(e)},setConfigOrderBy(e){this.config.orderBy=e,r.orderBy.set(e)},setConfigFilter(e){this.config.filter=e,r.filter.set(e)},setCopy(e){this.copy.type=e.type,this.copy.values=e.values||[],r.copy.set(e)}}}),S=h("editor",{state:()=>({entries:[],editableTabs:[],editableTabsValue:""}),actions:{addTab(e){for(let t=0;t<this.editableTabs.length;t++)if(this.editableTabs[t].rootPath===e.rootPath){this.editableTabsValue=e.rootPath||"";return}this.editableTabs.push(e),this.editableTabsValue=e.rootPath||""},removeTab(e){const t=this.editableTabs;let s=this.editableTabsValue;s===e&&t.forEach((a,l)=>{if(a.rootPath===e){const c=t[l+1]||t[l-1];c&&(s=c.rootPath||"")}}),this.editableTabsValue=s,this.editableTabs=t.filter(a=>a.rootPath!==e)}}});export{S as a,r as s,m as u};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
var u=(c,d,n)=>new Promise((p,r)=>{var s=a=>{try{i(n.next(a))}catch(e){r(e)}},m=a=>{try{i(n.throw(a))}catch(e){r(e)}},i=a=>a.done?p(a.value):Promise.resolve(a.value).then(s,m);i((n=n.apply(c,d)).next())});import{e as S,r as x,aA as D,az as g,t as B,x as y,A as l,a2 as w,q as _,y as o,P as F,bH as I,F as f,O as C,I as b,v as V,a3 as k,bI as q,aM as A,B as N,a9 as P,a4 as T,C as L,a5 as M,a6 as O,aB as h,a7 as z}from"./index.js";import{u as G}from"./chunk.6424c2fc.js";const v=c=>(M("data-v-61900a83"),c=c(),O(),c),H=["onSubmit"],U={class:"form-item"},W=v(()=>o("div",{class:"form-item_label form-required"}," \u6570\u636E\u76EE\u5F55 ",-1)),$={class:"form-item_content"},j=v(()=>o("option",{value:"",selected:""},"\u9009\u62E9\u6570\u636E\u76EE\u5F55",-1)),J=["value"],K={class:"form-btns"},Q=v(()=>o("div",{class:"auto"},null,-1)),R=S({__name:"index",setup(c){return u(this,null,function*(){let d,n;const p=G(),r=x(!1),s=D({dataPath:""}),m=()=>u(this,null,function*(){try{const a=yield P.LocalDevice.Data.GET();if(a.data){const{success:e,result:t}=a.data;t!=null&&t.dataPath&&(s.dataPath=t.dataPath)}}catch(a){}});[d,n]=g(()=>m()),yield d,n();const i=()=>u(this,null,function*(){if(!(s.dataPath==""||s.dataPath==null)){r.value=!0;try{const a={dataPath:s.dataPath},e=yield P.LocalDevice.Data.POST(a);if(e.data){const{success:t,error:E}=e.data;E&&h.Warning(E),t===0&&h.Success("\u4FDD\u5B58\u6210\u529F")}}catch(a){h.Error(`${a}`)}finally{r.value=!1}}});return(a,e)=>(_(),B(l(w),{id:"page"},{default:y(()=>[o("form",{onSubmit:N(i,["prevent"])},[o("div",U,[W,o("div",$,[F(o("select",{placeholder:"\u9009\u62E9\u6570\u636E\u76EE\u5F55","onUpdate:modelValue":e[0]||(e[0]=t=>s.dataPath=t),required:""},[j,(_(!0),f(b,null,C(l(p).menus,t=>(_(),f(b,null,[t.name!="root"?(_(),f("option",{key:0,value:"/local/"+t.name},T(t.name),9,J)):L("",!0)],64))),256))],512),[[I,s.dataPath,void 0,{trim:!0}]])])]),o("div",K,[Q,V(l(A),{type:"primary","native-type":"submit",icon:l(q),loading:r.value,disabled:r.value},{default:y(()=>[k(" \u4FDD\u5B58 ")]),_:1},8,["icon","loading","disabled"])])],40,H)]),_:1}))})}});const aa=z(R,[["__scopeId","data-v-61900a83"]]);export{aa as default};

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,39 @@
module("luci.controller.linkease", package.seeall)
function index()
if not nixio.fs.access("/etc/config/linkease") then
return
end
entry({"admin", "services", "linkease"}, cbi("linkease"), _("LinkEase"), 20).dependent = true
entry({"admin", "services", "linkease_status"}, call("linkease_status"))
entry({"admin", "services", "linkease", "file"}, call("linkease_file_template")).leaf = true
end
function linkease_status()
local sys = require "luci.sys"
local uci = require "luci.model.uci".cursor()
local port = tonumber(uci:get_first("linkease", "linkease", "port"))
local status = {
running = (sys.call("pidof linkease >/dev/null") == 0),
port = (port or 8897)
}
luci.http.prepare_content("application/json")
luci.http.write_json(status)
end
function get_params(name)
local data = {
prefix=luci.dispatcher.build_url(unpack({"admin", "services", "linkease", name})),
}
return data
end
function linkease_file_template()
luci.template.render("linkease/file", get_params("file"))
end

View File

@ -0,0 +1,191 @@
-- Copyright 2022 xiaobao <xiaobao@linkease.com>
-- Licensed to the public under the MIT License
local http = require "luci.http"
local nixio = require "nixio"
local ltn12 = require "luci.ltn12"
local table = require "table"
local util = require "luci.util"
module("luci.controller.linkease_backend", package.seeall)
local BLOCKSIZE = 2048
local LINKEASE_UNIX = "/var/run/linkease.sock"
function index()
entry({"linkease"}, call("linkease_backend")).leaf=true
end
local function sink_socket(sock, io_err)
if sock then
return function(chunk, err)
if not chunk then
return 1
else
return sock:send(chunk)
end
end
else
return ltn12.sink.error(io_err or "unable to send socket")
end
end
local function session_retrieve(sid, allowed_users)
local sdat = util.ubus("session", "get", { ubus_rpc_session = sid })
if type(sdat) == "table" and
type(sdat.values) == "table" and
type(sdat.values.token) == "string" and
(not allowed_users or
util.contains(allowed_users, sdat.values.username))
then
return sid, sdat.values
end
return nil, nil
end
local function get_session()
local sid
local key
local sdat
for _, key in ipairs({"sysauth_https", "sysauth_http", "sysauth"}) do
sid = http.getcookie(key)
if sid then
sid, sdat = session_retrieve(sid, nil)
if sid and sdat then
return sid, sdat
end
end
end
return nil, nil
end
local function chunksource(sock, buffer)
buffer = buffer or ""
return function()
local output
local _, endp, count = buffer:find("^([0-9a-fA-F]+);?.-\r\n")
while not count and #buffer <= 1024 do
local newblock, code = sock:recv(1024 - #buffer)
if not newblock then
return nil, code
end
buffer = buffer .. newblock
_, endp, count = buffer:find("^([0-9a-fA-F]+);?.-\r\n")
end
count = tonumber(count, 16)
if not count then
return nil, -1, "invalid encoding"
elseif count == 0 then
return nil
elseif count + 2 <= #buffer - endp then
output = buffer:sub(endp+1, endp+count)
buffer = buffer:sub(endp+count+3)
return output
else
output = buffer:sub(endp+1, endp+count)
buffer = ""
if count - #output > 0 then
local remain, code = sock:recvall(count-#output)
if not remain then
return nil, code
end
output = output .. remain
count, code = sock:recvall(2)
else
count, code = sock:recvall(count+2-#buffer+endp)
end
if not count then
return nil, code
end
return output
end
end
end
function linkease_backend()
local sock = nixio.socket("unix", "stream")
if sock:connect(LINKEASE_UNIX) ~= true then
http.status(500, "connect failed")
return
end
local input = {}
input[#input+1] = http.getenv("REQUEST_METHOD") .. " " .. http.getenv("REQUEST_URI") .. " HTTP/1.1"
local req = http.context.request
local start = "HTTP_"
local start_len = string.len(start)
local ctype = http.getenv("CONTENT_TYPE")
if ctype then
input[#input+1] = "Content-Type: " .. ctype
end
for k, v in pairs(req.message.env) do
if string.sub(k, 1, start_len) == start and not string.find(k, "FORWARDED") then
input[#input+1] = string.sub(k, start_len+1, string.len(k)) .. ": " .. v
end
end
local sid, sdat = get_session()
if sdat ~= nil then
input[#input+1] = "X-Forwarded-Sid: " .. sid
input[#input+1] = "X-Forwarded-Token: " .. sdat.token
end
-- input[#input+1] = "X-Forwarded-For: " .. http.getenv("REMOTE_HOST") ..":".. http.getenv("REMOTE_PORT")
local num = tonumber(http.getenv("CONTENT_LENGTH")) or 0
input[#input+1] = "Content-Length: " .. tostring(num)
input[#input+1] = "\r\n"
local source = ltn12.source.cat(ltn12.source.string(table.concat(input, "\r\n")), http.source())
local ret = ltn12.pump.all(source, sink_socket(sock, "write sock error"))
if ret ~= 1 then
sock:close()
http.status(500, "proxy error")
return
end
local linesrc = sock:linesource()
local line, code, error = linesrc()
if not line then
sock:close()
http.status(500, "response parse failed")
return
end
local protocol, status, msg = line:match("^([%w./]+) ([0-9]+) (.*)")
if not protocol then
sock:close()
http.status(500, "response protocol error")
return
end
num = tonumber(status) or 0
http.status(num, msg)
local allow_ranges = http.getenv("SERVER_SOFTWARE") ~= "uhttpd"
local chunked = 0
line = linesrc()
while line and line ~= "" do
local key, val = line:match("^([%w-]+)%s?:%s?(.*)")
if key and key ~= "Status" then
if key == "Transfer-Encoding" and val == "chunked" then
chunked = 1
end
if key ~= "Connection" and key ~= "Transfer-Encoding" and ( allow_ranges or (key ~= "Content-Length" and key ~= "Accept-Ranges") ) then
http.header(key, val)
end
end
line = linesrc()
end
if not line then
sock:close()
http.status(500, "parse header failed")
return
end
local body_buffer = linesrc(true)
if chunked == 1 then
ltn12.pump.all(chunksource(sock, body_buffer), http.write)
else
local body_source = ltn12.source.cat(ltn12.source.string(body_buffer), sock:blocksource())
ltn12.pump.all(body_source, http.write)
end
sock:close()
end

View File

@ -0,0 +1,20 @@
--wulishui <wulishui@gmail.com> ,20200911
--jjm2473 <jjm2473@gmail.com> ,20210127
local m, s
m = Map("linkease", translate("LinkEase"), translate("LinkEase is an efficient data transfer tool."))
m:section(SimpleSection).template = "linkease_status"
s=m:section(TypedSection, "linkease", translate("Global settings"))
s.addremove=false
s.anonymous=true
s:option(Flag, "enabled", translate("Enable")).rmempty=false
s:option(Value, "port", translate("Port")).rmempty=false
return m

View File

@ -0,0 +1 @@
<%+linkease_status%>

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0" /> -->
<meta name="viewport" content="width=1380" />
<title>易有云文件管理</title>
<script>
(function () {
var pathe_prefix = "<%=prefix%>"
window.path_base = pathe_prefix
window.ver = "<%# PKG_VERSION %>"
})();
</script>
<script type="module" crossorigin src="/luci-static/linkeasefile/index.js<%# ?v=PKG_VERSION %>"></script>
<link rel="stylesheet" href="/luci-static/linkeasefile/style.css<%# ?v=PKG_VERSION %>">
</head>
<body>
<div id="app"></div>
</body>
</html>

View File

@ -0,0 +1,28 @@
<script type="text/javascript">//<![CDATA[
XHR.poll(5, '<%=url("admin/services/linkease_status")%>', null,
function(x, st)
{
var tb = document.getElementById('linkease_status');
if (st && tb)
{
if (st.running)
{
tb.innerHTML = '<br/><em style=\"color:green\"><%:The LinkEase service is running.%></em>'
+ "<br/><br/><input class=\"btn cbi-button cbi-button-apply\" type=\"button\" value=\" <%:Click to open LinkEase%> \" onclick=\"window.open('http://" + window.location.hostname + ":" + st.port + "/')\"/>"
+ "<br/><input class=\"btn cbi-button cbi-button-apply\" type=\"button\" value=\" <%:Click to open Files%> \" onclick=\"window.open('/cgi-bin/luci/admin/services/linkease/file/')\"/>";
}
else
{
tb.innerHTML = '<br/><em style=\"color:red\"><%:The LinkEase service is not running.%></em>';
}
}
}
);
//]]></script>
<fieldset class="cbi-section">
<legend><%:LinkEase Status%></legend>
<p id="linkease_status">
<em><%:Collecting data...%></em>
</p>
</fieldset>

View File

@ -0,0 +1,30 @@
msgid "LinkEase"
msgstr "易有云文件管理器"
msgid "Running state"
msgstr "运行状态"
msgid "Click to open LinkEase"
msgstr "点击打开易有云"
msgid "Click to open Files"
msgstr "本地文件管理"
msgid "LinkEase is an efficient data transfer tool."
msgstr "易有云是一个微型家庭数据服务中心,主要用于文件的集中存放、读取、备份及日常管理。释放用户终端设备空间,实现个人、家庭文件长久留存;支持家庭相册、视频文件随时随地多终端查看、播放,满足家庭文件的日常管理。"
msgid "Port"
msgstr "端口"
msgid "The LinkEase service is running."
msgstr "易有云服务已启动"
msgid "The LinkEase service is not running."
msgstr "易有云服务未启动"
msgid "LinkEase Status"
msgstr "易有云服务状态"
msgid "Collecting data..."
msgstr "收集数据..."

View File

@ -0,0 +1 @@
zh-cn

View File

@ -0,0 +1,4 @@
#!/bin/sh
rm -f /tmp/luci-indexcache
exit 0