diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cabfd9645..f8f99a856 100755 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,8 +8,8 @@ jobs: build: strategy: matrix: - OMR_TARGET: [bpi-r1, bpi-r2, bpi-r64, rpi2, rpi4, wrt32x, espressobin, r2s, rpi3, wrt3200acm, cm520-79f, x86_64, ubnt-erx, r4s, r7800, rutx, l1000, zbt4019] - OMR_KERNEL: [5.4, 5.15] + OMR_TARGET: [bpi-r1, bpi-r2, bpi-r64, rpi2, rpi4, wrt32x, espressobin, r2s, rpi3, wrt3200acm, x86, x86_64, ubnt-erx, r4s, r7800, rutx, r5s, l1000, zbt4019] + OMR_KERNEL: [5.4, 5.15, 6.1] runs-on: ubuntu-latest continue-on-error: true @@ -17,14 +17,14 @@ jobs: - name: Branch name id: branch_name run: | - echo ::set-output name=SOURCE_NAME::${GITHUB_REF#refs/*/} - echo ::set-output name=SOURCE_BRANCH::${GITHUB_REF#refs/heads/} - echo ::set-output name=SOURCE_TAG::${GITHUB_REF#refs/tags/} - echo ::set-output name=WORKSPACE::${GITHUB_WORKSPACE} + echo "SOURCE_NAME=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT + echo "SOURCE_BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_OUTPUT + echo "SOURCE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT + echo "WORKSPACE=${GITHUB_WORKSPACE}" >> $GITHUB_OUTPUT - name: Prepare run: | sudo apt-get update - sudo apt-get install build-essential asciidoc binutils bzip2 gawk gettext git libncurses5-dev libz-dev patch unzip zlib1g-dev lib32gcc1 libc6-dev-i386 subversion flex uglifyjs git-core gcc-multilib p7zip p7zip-full msmtp libssl-dev texinfo libglib2.0-dev xmlto qemu-utils upx libelf-dev autoconf automake libtool autopoint device-tree-compiler + sudo apt-get install build-essential asciidoc binutils bzip2 gawk gettext git libncurses5-dev libz-dev patch unzip zlib1g-dev lib32gcc-s1 libc6-dev-i386 subversion flex uglifyjs git-core gcc-multilib p7zip p7zip-full msmtp libssl-dev texinfo libglib2.0-dev xmlto qemu-utils upx libelf-dev autoconf automake libtool autopoint device-tree-compiler - name: Free disk space run: | df -h @@ -83,7 +83,7 @@ jobs: run: | make IGNORE_ERRORS=m -C $OMR_TARGET/$OMR_KERNEL/source target/install -j$(nproc) || make IGNORE_ERRORS=m -C $OMR_TARGET/$OMR_KERNEL/source target/install -j1 V=s - name: Upload artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: ${{ matrix.OMR_TARGET }} path: /home/runner/work/omr/${{ matrix.OMR_TARGET }}/${{ matrix.OMR_KERNEL }}/source/bin diff --git a/atinout/Makefile b/atinout/Makefile new file mode 100644 index 000000000..5457697e0 --- /dev/null +++ b/atinout/Makefile @@ -0,0 +1,57 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=atinout +PKG_VERSION=0.9.1 + +PKG_MAINTAINER:=Konstantine Shevlakov +PKG_LICENSE:=GPLv2 +PKG_LICENSE_FILES:=LICENSE + +PKG_SOURCE_PROTO:=git +PKG_SOURCE_URL:=https://github.com/beralt/atinout.git +PKG_SOURCE_VERSION:=4013e8db4cd140c1df24bb90f929efeb9b61b238 + +PKG_SOURCE_SUBDIR:=$(PKG_NAME) +PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz +PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR) + +include $(INCLUDE_DIR)/package.mk + +define Package/$(PKG_NAME) + SECTION:=net + CATEGORY:=Network + SUBMENU:=Telephony + TITLE:=Send AT commands to a modem + URL:=http://atinout.sourceforge.net/ + MAINTAINER:=Adrian Guenter +endef + +define Package/$(PKG_NAME)/description + Atinout is a program that will execute AT commands in sequence and + capture the response from the modem. +endef + + +CONFIGURE_VARS += \ + CC="$(TARGET_CC)" \ + CXX="$(TARGET_CC) +.c++" \ + CFLAGS="$(TARGET_CFLAGS) -Wall -DVERSION=\"\\\"$(PKG_VERSION)\\\"\"" \ + LDFLAGS="$(TARGET_LDFLAGS)" + +define Build/Configure + $(call Build/Configure/Default,--with-linux-headers=$(LINUX_DIR)) +endef + +define Build/Compile + @echo -e "\n=== Build/Compile ===" + $(CONFIGURE_VARS) $(MAKE) -C $(PKG_BUILD_DIR) \ + all \ + +endef + +define Package/$(PKG_NAME)/install + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) $(PKG_BUILD_DIR)/$(PKG_NAME) $(1)/usr/bin/$(PKG_NAME) +endef + +$(eval $(call BuildPackage,$(PKG_NAME))) diff --git a/fullconenat/Makefile b/fullconenat/Makefile new file mode 100644 index 000000000..d49155c7e --- /dev/null +++ b/fullconenat/Makefile @@ -0,0 +1,62 @@ +# +# Copyright (C) 2022 Chion Tang +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=fullconenat +PKG_RELEASE:=9 + +PKG_SOURCE_DATE:=2022-02-13 +PKG_SOURCE_PROTO:=git +PKG_SOURCE_URL:=https://github.com/llccd/netfilter-full-cone-nat.git +PKG_SOURCE_VERSION:=108a36cbdca17e68c9e6e7fd5e26156a88f738e8 +PKG_MIRROR_HASH:=00d749235271dee194dcd23c22e6e85207ea90192a62a110b2af0b4e4de1971f + +PKG_LICENSE:=GPL-2.0 +PKG_LICENSE_FILES:=LICENSE +PKG_MAINTAINER:=Chion Tang + +include $(INCLUDE_DIR)/kernel.mk +include $(INCLUDE_DIR)/package.mk + +define Package/iptables-mod-fullconenat + SUBMENU:=Firewall + SECTION:=net + CATEGORY:=Network + TITLE:=FULLCONENAT iptables extension + DEPENDS:=+iptables +kmod-ipt-fullconenat +endef + +define Package/iptables-mod-fullconenat/install + $(INSTALL_DIR) $(1)/usr/lib/iptables + $(INSTALL_BIN) $(PKG_BUILD_DIR)/libipt_FULLCONENAT.so $(1)/usr/lib/iptables +endef + +define KernelPackage/ipt-fullconenat + SUBMENU:=Netfilter Extensions + TITLE:=FULLCONENAT netfilter module + DEPENDS:=+kmod-nf-ipt +kmod-nf-nat + KCONFIG:= \ + CONFIG_NF_CONNTRACK_EVENTS=y \ + CONFIG_NF_CONNTRACK_CHAIN_EVENTS=y + FILES:=$(PKG_BUILD_DIR)/xt_FULLCONENAT.ko +endef + +include $(INCLUDE_DIR)/kernel-defaults.mk + +define Build/Compile + +$(MAKE) $(PKG_JOBS) -C "$(LINUX_DIR)" \ + CROSS_COMPILE="$(TARGET_CROSS)" \ + ARCH="$(LINUX_KARCH)" \ + M="$(PKG_BUILD_DIR)" \ + EXTRA_CFLAGS="$(BUILDFLAGS)" \ + modules + $(call Build/Compile/Default) +endef + +$(eval $(call KernelPackage,ipt-fullconenat)) +$(eval $(call BuildPackage,iptables-mod-fullconenat)) diff --git a/fullconenat/patches/001-fix-init-Repeat-definition.patch b/fullconenat/patches/001-fix-init-Repeat-definition.patch new file mode 100644 index 000000000..67c9332c8 --- /dev/null +++ b/fullconenat/patches/001-fix-init-Repeat-definition.patch @@ -0,0 +1,20 @@ +--- a/libip6t_FULLCONENAT.c ++++ b/libip6t_FULLCONENAT.c +@@ -214,6 +214,7 @@ static struct xtables_target fullconenat_tg_reg = { + .x6_options = FULLCONENAT_opts, + }; + ++#define _init __attribute__((constructor)) _INIT + void _init(void) + { + xtables_register_target(&fullconenat_tg_reg); +--- a/libipt_FULLCONENAT.c ++++ b/libipt_FULLCONENAT.c +@@ -235,6 +235,7 @@ static struct xtables_target fullconenat_tg_reg = { + .x6_options = FULLCONENAT_opts, + }; + ++#define _init __attribute__((constructor)) _INIT + void _init(void) + { + xtables_register_target(&fullconenat_tg_reg); diff --git a/fullconenat/patches/001-linux-6.1-support.patch b/fullconenat/patches/001-linux-6.1-support.patch new file mode 100644 index 000000000..41721fb5c --- /dev/null +++ b/fullconenat/patches/001-linux-6.1-support.patch @@ -0,0 +1,26 @@ +--- a/xt_FULLCONENAT.c ++++ b/xt_FULLCONENAT.c +@@ -325,7 +325,11 @@ + /* for now we do the same thing for both --random and --random-fully */ + + /* select a random starting point */ ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) ++ start = (uint16_t)(get_random_u32() % (u32)range_size); ++#else + start = (uint16_t)(prandom_u32() % (u32)range_size); ++#endif + } else { + + if ((original_port >= min && original_port <= min + range_size - 1) +@@ -995,7 +999,11 @@ + /* for now we do the same thing for both --random and --random-fully */ + + /* select a random starting point */ ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) ++ start = (uint16_t)(get_random_u32() % (u32)range_size); ++#else + start = (uint16_t)(prandom_u32() % (u32)range_size); ++#endif + } else { + + if ((original_port >= min && original_port <= min + range_size - 1) diff --git a/fullconenat/src/Makefile b/fullconenat/src/Makefile new file mode 100644 index 000000000..b2f88db33 --- /dev/null +++ b/fullconenat/src/Makefile @@ -0,0 +1,6 @@ +libipt_FULLCONENAT.so: libipt_FULLCONENAT.o + $(CC) -shared -lxtables -o $@ $^; +libipt_FULLCONENAT.o: libipt_FULLCONENAT.c + $(CC) ${CFLAGS} -fPIC -D_INIT=$*_init -c -o $@ $<; + +obj-m += xt_FULLCONENAT.o diff --git a/https-dns-proxy/Makefile b/https-dns-proxy/Makefile index 73d0a07cf..abfb4be7a 100755 --- a/https-dns-proxy/Makefile +++ b/https-dns-proxy/Makefile @@ -1,15 +1,15 @@ include $(TOPDIR)/rules.mk PKG_NAME:=https-dns-proxy -PKG_VERSION:=2021-06-03 -PKG_RELEASE:=1 +PKG_VERSION:=2021-11-22 +PKG_RELEASE:=3 PKG_SOURCE_PROTO:=git -PKG_SOURCE_URL:=https://github.com/aarond10/https_dns_proxy -PKG_SOURCE_DATE:=2021-06-03 -PKG_SOURCE_VERSION:=5651b984f770a8bcecb14aeffc224703f8f82586 -PKG_MIRROR_HASH:=b65161936269aa3117debad0fcfce157024726b78d7e7da77c226f7aa8da5b4d -PKG_MAINTAINER:=Stan Grishin +PKG_SOURCE_URL:=https://github.com/aarond10/https_dns_proxy/ +PKG_SOURCE_DATE:=2021-11-22 +PKG_SOURCE_VERSION:=9336fd6272d67e8bb6e304fa54f3139a3d26f08f +PKG_MIRROR_HASH:=60b1ddabaf1db3a9ee19f3294a1df714364d580cef5e3c2161363c371a557456 +PKG_MAINTAINER:=Stan Grishin PKG_LICENSE:=MIT PKG_LICENSE_FILES:=LICENSE @@ -38,7 +38,10 @@ define Package/https-dns-proxy/conffiles endef define Package/https-dns-proxy/install - $(INSTALL_DIR) $(1)/usr/sbin $(1)/etc/init.d ${1}/etc/config + $(INSTALL_DIR) $(1)/usr/sbin + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_DIR) ${1}/etc/config + $(INSTALL_DIR) $(1)/etc/hotplug.d/iface $(INSTALL_BIN) $(PKG_BUILD_DIR)/https_dns_proxy $(1)/usr/sbin/https-dns-proxy $(INSTALL_BIN) ./files/https-dns-proxy.init $(1)/etc/init.d/https-dns-proxy $(SED) "s|^\(PKG_VERSION\).*|\1='$(PKG_VERSION)-$(PKG_RELEASE)'|" $(1)/etc/init.d/https-dns-proxy diff --git a/https-dns-proxy/files/https-dns-proxy.config b/https-dns-proxy/files/https-dns-proxy.config index 3c5eecf4d..f08e03ca9 100755 --- a/https-dns-proxy/files/https-dns-proxy.config +++ b/https-dns-proxy/files/https-dns-proxy.config @@ -1,13 +1,16 @@ config main 'config' option update_dnsmasq_config '*' - -config https-dns-proxy - option bootstrap_dns '8.8.8.8,8.8.4.4' - option resolver_url 'https://dns.google/dns-query' - option listen_addr '127.0.0.1' - option listen_port '5053' - option user 'nobody' - option group 'nogroup' + option force_dns '1' + list force_dns_port '53' + list force_dns_port '853' +# ports listed below are used by some +# of the dnscrypt-proxy v1 resolvers +# list force_dns_port '553' +# list force_dns_port '1443' +# list force_dns_port '4343' +# list force_dns_port '4434' +# list force_dns_port '5443' +# list force_dns_port '8443' config https-dns-proxy option bootstrap_dns '1.1.1.1,1.0.0.1' @@ -16,3 +19,11 @@ config https-dns-proxy option listen_port '5054' option user 'nobody' option group 'nogroup' + +config https-dns-proxy + option bootstrap_dns '8.8.8.8,8.8.4.4' + option resolver_url 'https://dns.google/dns-query' + option listen_addr '127.0.0.1' + option listen_port '5053' + option user 'nobody' + option group 'nogroup' diff --git a/https-dns-proxy/files/https-dns-proxy.hotplug.iface b/https-dns-proxy/files/https-dns-proxy.hotplug.iface new file mode 100644 index 000000000..c25b9e26d --- /dev/null +++ b/https-dns-proxy/files/https-dns-proxy.hotplug.iface @@ -0,0 +1,6 @@ +#!/bin/sh + +if [ "$ACTION" = 'ifup' ] && [ "$INTERFACE" = 'wan' ] && /etc/init.d/https-dns-proxy enabled; then + logger -t "https-dns-proxy" "Restarting https-dns-proxy due to $ACTION of $INTERFACE" + /etc/init.d/https-dns-proxy restart +fi diff --git a/https-dns-proxy/patches/010-fix-cmakelists.patch b/https-dns-proxy/patches/010-fix-cmakelists.patch new file mode 100644 index 000000000..106142579 --- /dev/null +++ b/https-dns-proxy/patches/010-fix-cmakelists.patch @@ -0,0 +1,15 @@ +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -21,9 +21,9 @@ if(NOT CMAKE_BUILD_TYPE) + message(STATUS "Setting build type to '${CMAKE_BUILD_TYPE}' as none was specified.") + endif() + +-set(CMAKE_C_FLAGS "-Wall -Wextra --pedantic -Wno-strict-aliasing -Wno-variadic-macros") +-set(CMAKE_C_FLAGS_DEBUG "-g -DDEBUG") +-set(CMAKE_C_FLAGS_RELEASE "-O2") ++#set(CMAKE_C_FLAGS "-Wall -Wextra --pedantic -Wno-strict-aliasing -Wno-variadic-macros") ++#set(CMAKE_C_FLAGS_DEBUG "-g -DDEBUG") ++#set(CMAKE_C_FLAGS_RELEASE "-O2") + + if ((CMAKE_C_COMPILER_ID MATCHES GNU AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 9) OR + (CMAKE_C_COMPILER_ID MATCHES Clang AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 10)) diff --git a/libmbim/Makefile b/libmbim/Makefile index 6c9766480..cfcb5ba96 100755 --- a/libmbim/Makefile +++ b/libmbim/Makefile @@ -8,28 +8,27 @@ include $(TOPDIR)/rules.mk PKG_NAME:=libmbim -PKG_VERSION:=1.26.4 -PKG_RELEASE:=$(AUTORELEASE) +PKG_SOURCE_VERSION:=1.29.2 +PKG_RELEASE:=1 -PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.xz -PKG_SOURCE_URL:=https://www.freedesktop.org/software/libmbim -PKG_HASH:=f688cec4c4586a17575f5e327448ce62f2000ef6a07c9e4589873d4a68568ad9 +PKG_SOURCE_PROTO:=git +PKG_SOURCE_URL:=https://gitlab.freedesktop.org/mobile-broadband/libmbim.git +#PKG_MIRROR_HASH:=0b0b46016738fc22355d5a58c8a2d1b2f04906c49c51a50b57a09640d13b00b7 PKG_MAINTAINER:=Nicholas Smith -PKG_INSTALL:=1 -PKG_BUILD_PARALLEL:=1 - include $(INCLUDE_DIR)/package.mk include $(INCLUDE_DIR)/nls.mk +include $(INCLUDE_DIR)/meson.mk -CONFIGURE_ARGS += \ - --disable-static \ - --disable-gtk-doc \ - --disable-gtk-doc-html \ - --disable-gtk-doc-pdf \ - --disable-silent-rules \ - --enable-more-warnings=yes +TARGET_CFLAGS += -ffunction-sections -fdata-sections -fno-merge-all-constants -fmerge-constants +TARGET_LDFLAGS += -Wl,--gc-sections + +MESON_ARGS += \ + -Dintrospection=false \ + -Dman=false \ + -Dbash_completion=false \ + -Db_lto=true define Package/libmbim SECTION:=libs @@ -56,10 +55,6 @@ define Package/mbim-utils LICENSE_FILES:=COPYING endef -CONFIGURE_ARGS += \ - --without-udev \ - --without-udev-base-dir - define Build/InstallDev $(INSTALL_DIR) $(1)/usr/include $(CP) \ @@ -78,11 +73,15 @@ define Build/InstallDev endef define Package/libmbim/install - $(INSTALL_DIR) $(1)/usr/lib + $(INSTALL_DIR) \ + $(1)/usr/lib \ + $(1)/usr/libexec + $(CP) \ $(PKG_INSTALL_DIR)/usr/lib/libmbim*.so.* \ $(1)/usr/lib/ - $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/lib/mbim-proxy $(1)/usr/lib/ + + $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/libexec/mbim-proxy $(1)/usr/libexec/ endef define Package/mbim-utils/install diff --git a/libqmi/Config.in b/libqmi/Config.in index 6f35b7453..7dfa7ca52 100755 --- a/libqmi/Config.in +++ b/libqmi/Config.in @@ -13,4 +13,19 @@ config LIBQMI_WITH_QRTR_GLIB help Compile libqmi with QRTR support +choice + prompt "Select QMI message collection to build" + default LIBQMI_COLLECTION_BASIC + + config LIBQMI_COLLECTION_MINIMAL + depends on !MODEMMANAGER_WITH_QMI + bool "minimal" + + config LIBQMI_COLLECTION_BASIC + bool "basic (default)" + + config LIBQMI_COLLECTION_FULL + bool "full" +endchoice + endmenu diff --git a/libqmi/Makefile b/libqmi/Makefile index 7cb640ea8..cecee6cb6 100755 --- a/libqmi/Makefile +++ b/libqmi/Makefile @@ -8,20 +8,21 @@ include $(TOPDIR)/rules.mk PKG_NAME:=libqmi -PKG_VERSION:=1.30.8 -PKG_RELEASE:=$(AUTORELEASE) +PKG_SOURCE_VERSION:=1.33.3 +PKG_RELEASE:=1 -PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.xz -PKG_SOURCE_URL:=https://www.freedesktop.org/software/libqmi -PKG_HASH:=862482ce9e3ad0bd65d264334ee311cdb94b9df2863b5b7136309b41b8ac1990 +PKG_SOURCE_PROTO:=git +PKG_SOURCE_URL:=https://gitlab.freedesktop.org/mobile-broadband/libqmi.git +#PKG_MIRROR_HASH:=711d16d75a6a9afaefcf2be1bc845a4a6181dff786dfbd079e41e91279a0be91 PKG_MAINTAINER:=Nicholas Smith -PKG_INSTALL:=1 -PKG_BUILD_PARALLEL:=1 - include $(INCLUDE_DIR)/package.mk include $(INCLUDE_DIR)/nls.mk +include $(INCLUDE_DIR)/meson.mk + +TARGET_CFLAGS += -ffunction-sections -fdata-sections -fno-merge-all-constants -fmerge-constants +TARGET_LDFLAGS += -Wl,--gc-sections define Package/libqmi/config source "$(SOURCE)/Config.in" @@ -59,28 +60,16 @@ define Package/libqmi-utils/description Utils to talk to QMI enabled modems endef -CONFIGURE_ARGS += \ - --disable-static \ - --disable-gtk-doc \ - --disable-gtk-doc-html \ - --disable-gtk-doc-pdf \ - --disable-silent-rules \ - --enable-firmware-update \ - --enable-more-warnings=yes \ - --without-udev \ - --without-udev-base-dir - -ifeq ($(CONFIG_LIBQMI_WITH_MBIM_QMUX),y) - CONFIGURE_ARGS += --enable-mbim-qmux -else - CONFIGURE_ARGS += --disable-mbim-qmux -endif - -ifeq ($(CONFIG_LIBQMI_WITH_QRTR_GLIB),y) - CONFIGURE_ARGS += --enable-qrtr -else - CONFIGURE_ARGS += --disable-qrtr -endif +MESON_ARGS += \ + -Dudev=false \ + -Dintrospection=false \ + -Dman=false \ + -Dbash_completion=false \ + -Db_lto=true \ + -Dmbim_qmux=$(if $(CONFIG_LIBQMI_WITH_MBIM_QMUX),true,false) \ + -Dqrtr=$(if $(CONFIG_LIBQMI_WITH_QRTR_GLIB),true,false) \ + -Dcollection=$(if $(CONFIG_LIBQMI_COLLECTION_MINIMAL),minimal\ + ,$(if $(CONFIG_LIBQMI_COLLECTION_BASIC),basic,full)) define Build/InstallDev $(INSTALL_DIR) $(1)/usr/include @@ -100,12 +89,15 @@ define Build/InstallDev endef define Package/libqmi/install - $(INSTALL_DIR) $(1)/usr/lib + $(INSTALL_DIR) \ + $(1)/usr/lib \ + $(1)/usr/libexec + $(CP) \ $(PKG_INSTALL_DIR)/usr/lib/libqmi*.so.* \ $(1)/usr/lib/ - $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/lib/qmi-proxy $(1)/usr/lib/ + $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/libexec/qmi-proxy $(1)/usr/libexec/ endef define Package/qmi-utils/install diff --git a/luci-app-adguardhome/Makefile b/luci-app-adguardhome/Makefile new file mode 100644 index 000000000..db03e8acb --- /dev/null +++ b/luci-app-adguardhome/Makefile @@ -0,0 +1,57 @@ +# Copyright (C) 2018-2019 Lienol +# +# This is free software, licensed under the Apache License, Version 2.0 . +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=luci-app-adguardhome +PKG_MAINTAINER:= + +LUCI_TITLE:=LuCI app for AdGuardHome +LUCI_PKGARCH:=all +LUCI_DEPENDS:=+ca-certs +curl +wget-ssl +PACKAGE_$(PKG_NAME)_INCLUDE_binary:adguardhome +LUCI_DESCRIPTION:=LuCI support for AdGuardHome + +define Package/$(PKG_NAME)/config +config PACKAGE_$(PKG_NAME)_INCLUDE_binary + bool "Include Binary File" + default y +endef + +PKG_CONFIG_DEPENDS:= CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_binary + +define Package/luci-app-adguardhome/conffiles +/usr/share/AdGuardHome/links.txt +/etc/config/AdGuardHome +/etc/AdGuardHome.yaml +endef + +define Package/luci-app-adguardhome/postinst +#!/bin/sh + /etc/init.d/AdGuardHome enable >/dev/null 2>&1 + enable=$(uci get AdGuardHome.AdGuardHome.enabled 2>/dev/null) + if [ "$enable" == "1" ]; then + /etc/init.d/AdGuardHome reload + fi + rm -f /tmp/luci-indexcache + rm -f /tmp/luci-modulecache/* +exit 0 +endef + +define Package/luci-app-adguardhome/prerm +#!/bin/sh +if [ -z "$${IPKG_INSTROOT}" ]; then + /etc/init.d/AdGuardHome disable + /etc/init.d/AdGuardHome stop +uci -q batch <<-EOF >/dev/null 2>&1 + delete ucitrack.@AdGuardHome[-1] + commit ucitrack +EOF +fi +exit 0 +endef + +include $(TOPDIR)/feeds/luci/luci.mk + +# call BuildPackage - OpenWrt buildroot signature diff --git a/luci-app-adguardhome/luasrc/controller/AdGuardHome.lua b/luci-app-adguardhome/luasrc/controller/AdGuardHome.lua new file mode 100644 index 000000000..e9d37a766 --- /dev/null +++ b/luci-app-adguardhome/luasrc/controller/AdGuardHome.lua @@ -0,0 +1,130 @@ +module("luci.controller.AdGuardHome",package.seeall) +local fs=require"nixio.fs" +local http=require"luci.http" +local uci=require"luci.model.uci".cursor() +function index() +local page = entry({"admin", "services", "AdGuardHome"},alias("admin", "services", "AdGuardHome", "base"),_("AdGuard Home")) +page.order = 10 +page.dependent = true +page.acl_depends = { "luci-app-adguardhome" } +entry({"admin","services","AdGuardHome","base"},cbi("AdGuardHome/base"),_("Base Setting"),1).leaf = true +entry({"admin","services","AdGuardHome","log"},form("AdGuardHome/log"),_("Log"),2).leaf = true +entry({"admin","services","AdGuardHome","manual"},cbi("AdGuardHome/manual"),_("Manual Config"),3).leaf = true +entry({"admin","services","AdGuardHome","status"},call("act_status")).leaf=true +entry({"admin", "services", "AdGuardHome", "check"}, call("check_update")) +entry({"admin", "services", "AdGuardHome", "doupdate"}, call("do_update")) +entry({"admin", "services", "AdGuardHome", "getlog"}, call("get_log")) +entry({"admin", "services", "AdGuardHome", "dodellog"}, call("do_dellog")) +entry({"admin", "services", "AdGuardHome", "reloadconfig"}, call("reload_config")) +entry({"admin", "services", "AdGuardHome", "gettemplateconfig"}, call("get_template_config")) +end +function get_template_config() + local b + local d="" + for cnt in io.lines("/tmp/resolv.conf.d/resolv.conf.auto") do + b=string.match (cnt,"^[^#]*nameserver%s+([^%s]+)$") + if (b~=nil) then + d=d.." - "..b.."\n" + end + end + local f=io.open("/usr/share/AdGuardHome/AdGuardHome_template.yaml", "r+") + local tbl = {} + local a="" + while (1) do + a=f:read("*l") + if (a=="#bootstrap_dns") then + a=d + elseif (a=="#upstream_dns") then + a=d + elseif (a==nil) then + break + end + table.insert(tbl, a) + end + f:close() + http.prepare_content("text/plain; charset=utf-8") + http.write(table.concat(tbl, "\n")) +end +function reload_config() + fs.remove("/tmp/AdGuardHometmpconfig.yaml") + http.prepare_content("application/json") + http.write('') +end +function act_status() + local e={} + local binpath=uci:get("AdGuardHome","AdGuardHome","binpath") + e.running=luci.sys.call("pgrep "..binpath.." >/dev/null")==0 + e.redirect=(fs.readfile("/var/run/AdGredir")=="1") + http.prepare_content("application/json") + http.write_json(e) +end +function do_update() + fs.writefile("/var/run/lucilogpos","0") + http.prepare_content("application/json") + http.write('') + local arg + if luci.http.formvalue("force") == "1" then + arg="force" + else + arg="" + end + if fs.access("/var/run/update_core") then + if arg=="force" then + luci.sys.exec("kill $(pgrep /usr/share/AdGuardHome/update_core.sh) ; sh /usr/share/AdGuardHome/update_core.sh "..arg.." >/tmp/AdGuardHome_update.log 2>&1 &") + end + else + luci.sys.exec("sh /usr/share/AdGuardHome/update_core.sh "..arg.." >/tmp/AdGuardHome_update.log 2>&1 &") + end +end +function get_log() + local logfile=uci:get("AdGuardHome","AdGuardHome","logfile") + if (logfile==nil) then + http.write("no log available\n") + return + elseif (logfile=="syslog") then + if not fs.access("/var/run/AdGuardHomesyslog") then + luci.sys.exec("(/usr/share/AdGuardHome/getsyslog.sh &); sleep 1;") + end + logfile="/tmp/AdGuardHometmp.log" + fs.writefile("/var/run/AdGuardHomesyslog","1") + elseif not fs.access(logfile) then + http.write("") + return + end + http.prepare_content("text/plain; charset=utf-8") + local fdp + if fs.access("/var/run/lucilogreload") then + fdp=0 + fs.remove("/var/run/lucilogreload") + else + fdp=tonumber(fs.readfile("/var/run/lucilogpos")) or 0 + end + local f=io.open(logfile, "r+") + f:seek("set",fdp) + local a=f:read(2048000) or "" + fdp=f:seek() + fs.writefile("/var/run/lucilogpos",tostring(fdp)) + f:close() + http.write(a) +end +function do_dellog() + local logfile=uci:get("AdGuardHome","AdGuardHome","logfile") + fs.writefile(logfile,"") + http.prepare_content("application/json") + http.write('') +end +function check_update() + http.prepare_content("text/plain; charset=utf-8") + local fdp=tonumber(fs.readfile("/var/run/lucilogpos")) or 0 + local f=io.open("/tmp/AdGuardHome_update.log", "r+") + f:seek("set",fdp) + local a=f:read(2048000) or "" + fdp=f:seek() + fs.writefile("/var/run/lucilogpos",tostring(fdp)) + f:close() +if fs.access("/var/run/update_core") then + http.write(a) +else + http.write(a.."\0") +end +end diff --git a/luci-app-adguardhome/luasrc/model/cbi/AdGuardHome/base.lua b/luci-app-adguardhome/luasrc/model/cbi/AdGuardHome/base.lua new file mode 100644 index 000000000..6896b61ef --- /dev/null +++ b/luci-app-adguardhome/luasrc/model/cbi/AdGuardHome/base.lua @@ -0,0 +1,304 @@ +require("luci.sys") +require("luci.util") +require("io") +local m,s,o,o1 +local fs=require"nixio.fs" +local uci=require"luci.model.uci".cursor() +local configpath=uci:get("AdGuardHome","AdGuardHome","configpath") or "/etc/AdGuardHome.yaml" +local binpath=uci:get("AdGuardHome","AdGuardHome","binpath") or "/usr/bin/AdGuardHome" +httpport=uci:get("AdGuardHome","AdGuardHome","httpport") or "3000" +m = Map("AdGuardHome", "AdGuard Home") +m.description = translate("Free and open source, powerful network-wide ads & trackers blocking DNS server.") +m:section(SimpleSection).template = "AdGuardHome/AdGuardHome_status" + +s = m:section(TypedSection, "AdGuardHome") +s.anonymous=true +s.addremove=false +---- enable +o = s:option(Flag, "enabled", translate("Enable")) +o.default = 0 +o.optional = false +---- httpport +o =s:option(Value,"httpport",translate("Browser management port")) +o.placeholder=3000 +o.default=3000 +o.datatype="port" +o.optional = false +o.description = translate("") +---- update warning not safe +local binmtime=uci:get("AdGuardHome","AdGuardHome","binmtime") or "0" +local e="" +if not fs.access(configpath) then + e=e.." "..translate("no config") +end +if not fs.access(binpath) then + e=e.." "..translate("no core") +else + local version=uci:get("AdGuardHome","AdGuardHome","version") + local testtime=fs.stat(binpath,"mtime") + if testtime~=tonumber(binmtime) or version==nil then + local tmp=luci.sys.exec(binpath.." --version | grep -m 1 -E 'v[0-9.]+' -o ") + version=string.sub(tmp, 1) + if version=="" then version="core error" end + uci:set("AdGuardHome","AdGuardHome","version",version) + uci:set("AdGuardHome","AdGuardHome","binmtime",testtime) + uci:save("AdGuardHome") + end + e=version..e +end +o=s:option(Button,"restart",translate("Update")) +o.inputtitle=translate("Update core version") +o.template = "AdGuardHome/AdGuardHome_check" +o.showfastconfig=(not fs.access(configpath)) +o.description=string.format(translate("core version:").."%s ",e) +---- port warning not safe +local port=luci.sys.exec("awk '/ port:/{printf($2);exit;}' "..configpath.." 2>nul") +if (port=="") then port="?" end +---- Redirect +o = s:option(ListValue, "redirect", port..translate("Redirect"), translate("AdGuardHome redirect mode")) +o.placeholder = "none" +o:value("none", translate("none")) +o:value("dnsmasq-upstream", translate("Run as dnsmasq upstream server")) +o:value("redirect", translate("Redirect 53 port to AdGuardHome")) +o:value("exchange", translate("Use port 53 replace dnsmasq")) +o.default = "none" +o.optional = true +---- bin path +o = s:option(Value, "binpath", translate("Bin Path"), translate("AdGuardHome Bin path if no bin will auto download")) +o.default = "/usr/bin/AdGuardHome" +o.datatype = "string" +o.optional = false +o.rmempty=false +o.validate=function(self, value) +if value=="" then return nil end +if fs.stat(value,"type")=="dir" then + fs.rmdir(value) +end +if fs.stat(value,"type")=="dir" then + if (m.message) then + m.message =m.message.."\nerror!bin path is a dir" + else + m.message ="error!bin path is a dir" + end + return nil +end +return value +end +--- upx +o = s:option(ListValue, "upxflag", translate("use upx to compress bin after download")) +o:value("", translate("none")) +o:value("-1", translate("compress faster")) +o:value("-9", translate("compress better")) +o:value("--best", translate("compress best(can be slow for big files)")) +o:value("--brute", translate("try all available compression methods & filters [slow]")) +o:value("--ultra-brute", translate("try even more compression variants [very slow]")) +o.default = "" +o.description=translate("bin use less space,but may have compatibility issues") +o.rmempty = true +---- config path +o = s:option(Value, "configpath", translate("Config Path"), translate("AdGuardHome config path")) +o.default = "/etc/AdGuardHome.yaml" +o.datatype = "string" +o.optional = false +o.rmempty=false +o.validate=function(self, value) +if value==nil then return nil end +if fs.stat(value,"type")=="dir" then + fs.rmdir(value) +end +if fs.stat(value,"type")=="dir" then + if m.message then + m.message =m.message.."\nerror!config path is a dir" + else + m.message ="error!config path is a dir" + end + return nil +end +return value +end +---- work dir +o = s:option(Value, "workdir", translate("Work dir"), translate("AdGuardHome work dir include rules,audit log and database")) +o.default = "/etc/AdGuardHome" +o.datatype = "string" +o.optional = false +o.rmempty=false +o.validate=function(self, value) +if value=="" then return nil end +if fs.stat(value,"type")=="reg" then + if m.message then + m.message =m.message.."\nerror!work dir is a file" + else + m.message ="error!work dir is a file" + end + return nil +end +if string.sub(value, -1)=="/" then + return string.sub(value, 1, -2) +else + return value +end +end +---- log file +o = s:option(Value, "logfile", translate("Runtime log file"), translate("AdGuardHome runtime Log file if 'syslog': write to system log;if empty no log")) +o.datatype = "string" +o.rmempty = true +o.validate=function(self, value) +if fs.stat(value,"type")=="dir" then + fs.rmdir(value) +end +if fs.stat(value,"type")=="dir" then + if m.message then + m.message =m.message.."\nerror!log file is a dir" + else + m.message ="error!log file is a dir" + end + return nil +end +return value +end +---- debug +o = s:option(Flag, "verbose", translate("Verbose log")) +o.default = 0 +o.optional = true +---- gfwlist +local a=luci.sys.call("grep -m 1 -q programadd "..configpath) +if (a==0) then +a="Added" +else +a="Not added" +end +o=s:option(Button,"gfwdel",translate("Del gfwlist"),translate(a)) +o.optional = true +o.inputtitle=translate("Del") +o.write=function() + luci.sys.exec("sh /usr/share/AdGuardHome/gfw2adg.sh del 2>&1") + luci.http.redirect(luci.dispatcher.build_url("admin","services","AdGuardHome")) +end +o=s:option(Button,"gfwadd",translate("Add gfwlist"),translate(a)) +o.optional = true +o.inputtitle=translate("Add") +o.write=function() + luci.sys.exec("sh /usr/share/AdGuardHome/gfw2adg.sh 2>&1") + luci.http.redirect(luci.dispatcher.build_url("admin","services","AdGuardHome")) +end +o = s:option(Value, "gfwupstream", translate("Gfwlist upstream dns server"), translate("Gfwlist domain upstream dns service")..translate(a)) +o.default = "tcp://208.67.220.220:5353" +o.datatype = "string" +o.optional = true +---- chpass +o = s:option(Value, "hashpass", translate("Change browser management password"), translate("Press load culculate model and culculate finally save/apply")) +o.default = "" +o.datatype = "string" +o.template = "AdGuardHome/AdGuardHome_chpass" +o.optional = true +---- upgrade protect +o = s:option(MultiValue, "upprotect", translate("Keep files when system upgrade")) +o:value("$binpath",translate("core bin")) +o:value("$configpath",translate("config file")) +o:value("$logfile",translate("log file")) +o:value("$workdir/data/sessions.db",translate("sessions.db")) +o:value("$workdir/data/stats.db",translate("stats.db")) +o:value("$workdir/data/querylog.json",translate("querylog.json")) +o:value("$workdir/data/filters",translate("filters")) +o.widget = "checkbox" +o.default = nil +o.optional=true +---- wait net on boot +o = s:option(Flag, "waitonboot", translate("On boot when network ok restart")) +o.default = 1 +o.optional = true +---- backup workdir on shutdown +local workdir=uci:get("AdGuardHome","AdGuardHome","workdir") or "/etc/AdGuardHome" +o = s:option(MultiValue, "backupfile", translate("Backup workdir files when shutdown")) +o1 = s:option(Value, "backupwdpath", translate("Backup workdir path")) +local name +o:value("filters","filters") +o:value("stats.db","stats.db") +o:value("querylog.json","querylog.json") +o:value("sessions.db","sessions.db") +o1:depends ("backupfile", "filters") +o1:depends ("backupfile", "stats.db") +o1:depends ("backupfile", "querylog.json") +o1:depends ("backupfile", "sessions.db") +for name in fs.glob(workdir.."/data/*") +do + name=fs.basename (name) + if name~="filters" and name~="stats.db" and name~="querylog.json" and name~="sessions.db" then + o:value(name,name) + o1:depends ("backupfile", name) + end +end +o.widget = "checkbox" +o.default = nil +o.optional=false +o.description=translate("Will be restore when workdir/data is empty") +----backup workdir path + +o1.default = "/etc/AdGuardHome" +o1.datatype = "string" +o1.optional = false +o1.validate=function(self, value) +if fs.stat(value,"type")=="reg" then + if m.message then + m.message =m.message.."\nerror!backup dir is a file" + else + m.message ="error!backup dir is a file" + end + return nil +end +if string.sub(value,-1)=="/" then + return string.sub(value, 1, -2) +else + return value +end +end + +----Crontab +o = s:option(MultiValue, "crontab", translate("Crontab task"),translate("Please change time and args in crontab")) +o:value("autoupdate",translate("Auto update core")) +o:value("cutquerylog",translate("Auto tail querylog")) +o:value("cutruntimelog",translate("Auto tail runtime log")) +o:value("autohost",translate("Auto update ipv6 hosts and restart adh")) +o:value("autogfw",translate("Auto update gfwlist and restart adh")) +o.widget = "checkbox" +o.default = nil +o.optional=true + +----downloadpath +o = s:option(TextValue, "downloadlinks",translate("Download links for update")) +o.optional = false +o.rows = 4 +o.wrap = "soft" +o.cfgvalue = function(self, section) + return fs.readfile("/usr/share/AdGuardHome/links.txt") +end +o.write = function(self, section, value) + fs.writefile("/usr/share/AdGuardHome/links.txt", value:gsub("\r\n", "\n")) +end +fs.writefile("/var/run/lucilogpos","0") +function m.on_commit(map) + if (fs.access("/var/run/AdGserverdis")) then + io.popen("/etc/init.d/AdGuardHome reload &") + return + end + local ucitracktest=uci:get("AdGuardHome","AdGuardHome","ucitracktest") + if ucitracktest=="1" then + return + elseif ucitracktest=="0" then + io.popen("/etc/init.d/AdGuardHome reload &") + else + if (fs.access("/var/run/AdGlucitest")) then + uci:set("AdGuardHome","AdGuardHome","ucitracktest","0") + io.popen("/etc/init.d/AdGuardHome reload &") + else + fs.writefile("/var/run/AdGlucitest","") + if (ucitracktest=="2") then + uci:set("AdGuardHome","AdGuardHome","ucitracktest","1") + else + uci:set("AdGuardHome","AdGuardHome","ucitracktest","2") + end + end + uci:save("AdGuardHome") + end +end +return m diff --git a/luci-app-adguardhome/luasrc/model/cbi/AdGuardHome/log.lua b/luci-app-adguardhome/luasrc/model/cbi/AdGuardHome/log.lua new file mode 100644 index 000000000..5d18a88db --- /dev/null +++ b/luci-app-adguardhome/luasrc/model/cbi/AdGuardHome/log.lua @@ -0,0 +1,16 @@ +local fs=require"nixio.fs" +local uci=require"luci.model.uci".cursor() +local f,t +f=SimpleForm("logview") +f.reset = false +f.submit = false +t=f:field(TextValue,"conf") +t.rmempty=true +t.rows=20 +t.template="AdGuardHome/log" +t.readonly="readonly" +local logfile=uci:get("AdGuardHome","AdGuardHome","logfile") or "" +t.timereplace=(logfile~="syslog" and logfile~="" ) +t.pollcheck=logfile~="" +fs.writefile("/var/run/lucilogreload","") +return f diff --git a/luci-app-adguardhome/luasrc/model/cbi/AdGuardHome/manual.lua b/luci-app-adguardhome/luasrc/model/cbi/AdGuardHome/manual.lua new file mode 100644 index 000000000..ecf072bbc --- /dev/null +++ b/luci-app-adguardhome/luasrc/model/cbi/AdGuardHome/manual.lua @@ -0,0 +1,97 @@ +local m, s, o +local fs = require "nixio.fs" +local uci=require"luci.model.uci".cursor() +local sys=require"luci.sys" +require("string") +require("io") +require("table") +function gen_template_config() + local b + local d="" + for cnt in io.lines("/tmp/resolv.conf.d/resolv.conf.auto") do + b=string.match (cnt,"^[^#]*nameserver%s+([^%s]+)$") + if (b~=nil) then + d=d.." - "..b.."\n" + end + end + local f=io.open("/usr/share/AdGuardHome/AdGuardHome_template.yaml", "r+") + local tbl = {} + local a="" + while (1) do + a=f:read("*l") + if (a=="#bootstrap_dns") then + a=d + elseif (a=="#upstream_dns") then + a=d + elseif (a==nil) then + break + end + table.insert(tbl, a) + end + f:close() + return table.concat(tbl, "\n") +end +m = Map("AdGuardHome") +local configpath = uci:get("AdGuardHome","AdGuardHome","configpath") +local binpath = uci:get("AdGuardHome","AdGuardHome","binpath") +s = m:section(TypedSection, "AdGuardHome") +s.anonymous=true +s.addremove=false +--- config +o = s:option(TextValue, "escconf") +o.rows = 66 +o.wrap = "off" +o.rmempty = true +o.cfgvalue = function(self, section) + return fs.readfile("/tmp/AdGuardHometmpconfig.yaml") or fs.readfile(configpath) or gen_template_config() or "" +end +o.validate=function(self, value) + fs.writefile("/tmp/AdGuardHometmpconfig.yaml", value:gsub("\r\n", "\n")) + if fs.access(binpath) then + if (sys.call(binpath.." -c /tmp/AdGuardHometmpconfig.yaml --check-config 2> /tmp/AdGuardHometest.log")==0) then + return value + end + else + return value + end + luci.http.redirect(luci.dispatcher.build_url("admin","services","AdGuardHome","manual")) + return nil +end +o.write = function(self, section, value) + fs.move("/tmp/AdGuardHometmpconfig.yaml",configpath) +end +o.remove = function(self, section, value) + fs.writefile(configpath, "") +end +--- js and reload button +o = s:option(DummyValue, "") +o.anonymous=true +o.template = "AdGuardHome/yamleditor" +if not fs.access(binpath) then + o.description=translate("WARNING!!! no bin found apply config will not be test") +end +--- log +if (fs.access("/tmp/AdGuardHometmpconfig.yaml")) then +local c=fs.readfile("/tmp/AdGuardHometest.log") +if (c~="") then +o = s:option(TextValue, "") +o.readonly=true +o.rows = 5 +o.rmempty = true +o.name="" +o.cfgvalue = function(self, section) + return fs.readfile("/tmp/AdGuardHometest.log") +end +end +end +function m.on_commit(map) + local ucitracktest=uci:get("AdGuardHome","AdGuardHome","ucitracktest") + if ucitracktest=="1" then + return + elseif ucitracktest=="0" then + io.popen("/etc/init.d/AdGuardHome reload &") + else + fs.writefile("/var/run/AdGlucitest","") + end +end +return m diff --git a/luci-app-adguardhome/luasrc/view/AdGuardHome/AdGuardHome_check.htm b/luci-app-adguardhome/luasrc/view/AdGuardHome/AdGuardHome_check.htm new file mode 100644 index 000000000..832a1df46 --- /dev/null +++ b/luci-app-adguardhome/luasrc/view/AdGuardHome/AdGuardHome_check.htm @@ -0,0 +1,78 @@ +<%+cbi/valueheader%> +<%local fs=require"nixio.fs"%> + + +<% if self.showfastconfig then %> + +<%end%> + + +<%+cbi/valuefooter%> diff --git a/luci-app-adguardhome/luasrc/view/AdGuardHome/AdGuardHome_chpass.htm b/luci-app-adguardhome/luasrc/view/AdGuardHome/AdGuardHome_chpass.htm new file mode 100644 index 000000000..b6ff3ebb3 --- /dev/null +++ b/luci-app-adguardhome/luasrc/view/AdGuardHome/AdGuardHome_chpass.htm @@ -0,0 +1,49 @@ +<%+cbi/valueheader%> + + 0, "data-choices", { self.keylist, self.vallist }) + %> /> + <% if self.password then %><% end %> + +<%+cbi/valuefooter%> diff --git a/luci-app-adguardhome/luasrc/view/AdGuardHome/AdGuardHome_status.htm b/luci-app-adguardhome/luasrc/view/AdGuardHome/AdGuardHome_status.htm new file mode 100644 index 000000000..7e924d119 --- /dev/null +++ b/luci-app-adguardhome/luasrc/view/AdGuardHome/AdGuardHome_status.htm @@ -0,0 +1,27 @@ + + +
+

+ <%:Collecting data...%> +

+
\ No newline at end of file diff --git a/luci-app-adguardhome/luasrc/view/AdGuardHome/log.htm b/luci-app-adguardhome/luasrc/view/AdGuardHome/log.htm new file mode 100644 index 000000000..11a1f787a --- /dev/null +++ b/luci-app-adguardhome/luasrc/view/AdGuardHome/log.htm @@ -0,0 +1,111 @@ +<%+cbi/valueheader%> +<%:reverse%> +<%if self.timereplace then%> +<%:localtime%>
+<%end%> + + + + +<%+cbi/valuefooter%> diff --git a/luci-app-adguardhome/luasrc/view/AdGuardHome/yamleditor.htm b/luci-app-adguardhome/luasrc/view/AdGuardHome/yamleditor.htm new file mode 100644 index 000000000..639cb9988 --- /dev/null +++ b/luci-app-adguardhome/luasrc/view/AdGuardHome/yamleditor.htm @@ -0,0 +1,39 @@ +<%+cbi/valueheader%> + + + + + + + + + +<%fs=require"nixio.fs"%> +<%if fs.access("/tmp/AdGuardHometmpconfig.yaml") then%> + +<%end%> + +<%+cbi/valuefooter%> \ No newline at end of file diff --git a/luci-app-adguardhome/po/zh-cn b/luci-app-adguardhome/po/zh-cn new file mode 100644 index 000000000..8d69574dd --- /dev/null +++ b/luci-app-adguardhome/po/zh-cn @@ -0,0 +1 @@ +zh_Hans \ No newline at end of file diff --git a/luci-app-adguardhome/po/zh_Hans/adguardhome.po b/luci-app-adguardhome/po/zh_Hans/adguardhome.po new file mode 100644 index 000000000..0ace89bae --- /dev/null +++ b/luci-app-adguardhome/po/zh_Hans/adguardhome.po @@ -0,0 +1,408 @@ +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" +"Project-Id-Version: PACKAGE VERSION\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: zh_Hans\n" +"MIME-Version: 1.0\n" +"Content-Transfer-Encoding: 8bit\n" + +#: /mnt/A/openwrt-latest/package/ctcgfw/luci-app-adguardhome/luasrc/model/cbi/AdGuardHome/base.lua:27 +msgid "" +"/dev/null 2>&1 + if [ $? -eq 0 ]; then + return + fi + uci delete dhcp.@dnsmasq[0].server 2>/dev/null + uci add_list dhcp.@dnsmasq[0].server=$addr + for server in $OLD_SERVER; do + if [ "$server" = "$addr" ]; then + continue + fi + # uci add_list dhcp.@dnsmasq[0].server=$server + done + uci delete dhcp.@dnsmasq[0].resolvfile 2>/dev/null + uci set dhcp.@dnsmasq[0].noresolv=1 + uci commit dhcp + /etc/init.d/dnsmasq restart +} + +stop_forward_dnsmasq() +{ + local OLD_PORT="$1" + addr="127.0.0.1#$OLD_PORT" + OLD_SERVER="`uci get dhcp.@dnsmasq[0].server 2>/dev/null`" + echo $OLD_SERVER | grep "^$addr" >/dev/null 2>&1 + if [ $? -ne 0 ]; then + return + fi + + uci del_list dhcp.@dnsmasq[0].server=$addr 2>/dev/null + addrlist="`uci get dhcp.@dnsmasq[0].server 2>/dev/null`" + if [ -z "$addrlist" ] ; then + uci set dhcp.@dnsmasq[0].resolvfile=/tmp/resolv.conf.d/resolv.conf.auto 2>/dev/null + uci delete dhcp.@dnsmasq[0].noresolv 2>/dev/null + fi + uci commit dhcp + /etc/init.d/dnsmasq restart +} + +set_iptable() +{ + local ipv6_server=$1 + local tcp_server=$2 + uci -q batch <<-EOF >/dev/null 2>&1 + delete firewall.AdGuardHome + set firewall.AdGuardHome=include + set firewall.AdGuardHome.type=script + set firewall.AdGuardHome.path=/usr/share/AdGuardHome/firewall.start + set firewall.AdGuardHome.reload=1 + commit firewall +EOF + + IPS="`ifconfig | grep "inet addr" | grep -v ":127" | grep "Bcast" | awk '{print $2}' | awk -F : '{print $2}'`" + for IP in $IPS + do + if [ "$tcp_server" == "1" ]; then + iptables -t nat -A PREROUTING -p tcp -d $IP --dport 53 -j REDIRECT --to-ports $AdGuardHome_PORT >/dev/null 2>&1 + fi + iptables -t nat -A PREROUTING -p udp -d $IP --dport 53 -j REDIRECT --to-ports $AdGuardHome_PORT >/dev/null 2>&1 + done + + if [ "$ipv6_server" == 0 ]; then + return + fi + + IPS="`ifconfig | grep "inet6 addr" | grep -v " fe80::" | grep -v " ::1" | grep "Global" | awk '{print $3}'`" + for IP in $IPS + do + if [ "$tcp_server" == "1" ]; then + ip6tables -t nat -A PREROUTING -p tcp -d $IP --dport 53 -j REDIRECT --to-ports $AdGuardHome_PORT >/dev/null 2>&1 + fi + ip6tables -t nat -A PREROUTING -p udp -d $IP --dport 53 -j REDIRECT --to-ports $AdGuardHome_PORT >/dev/null 2>&1 + done +} + +clear_iptable() +{ + uci -q batch <<-EOF >/dev/null 2>&1 + delete firewall.AdGuardHome + commit firewall +EOF + local OLD_PORT="$1" + local ipv6_server=$2 + IPS="`ifconfig | grep "inet addr" | grep -v ":127" | grep "Bcast" | awk '{print $2}' | awk -F : '{print $2}'`" + for IP in $IPS + do + iptables -t nat -D PREROUTING -p udp -d $IP --dport 53 -j REDIRECT --to-ports $OLD_PORT >/dev/null 2>&1 + iptables -t nat -D PREROUTING -p tcp -d $IP --dport 53 -j REDIRECT --to-ports $OLD_PORT >/dev/null 2>&1 + done + + if [ "$ipv6_server" == 0 ]; then + return + fi + echo "warn ip6tables nat mod is needed" + IPS="`ifconfig | grep "inet6 addr" | grep -v " fe80::" | grep -v " ::1" | grep "Global" | awk '{print $3}'`" + for IP in $IPS + do + ip6tables -t nat -D PREROUTING -p udp -d $IP --dport 53 -j REDIRECT --to-ports $OLD_PORT >/dev/null 2>&1 + ip6tables -t nat -D PREROUTING -p tcp -d $IP --dport 53 -j REDIRECT --to-ports $OLD_PORT >/dev/null 2>&1 + done +} + +service_triggers() { + procd_add_reload_trigger "$CONFIGURATION" + [ "$(uci get AdGuardHome.AdGuardHome.redirect)" == "redirect" ] && procd_add_reload_trigger firewall +} + +isrunning(){ + config_load "${CONFIGURATION}" + _isrunning + local r=$? + ([ "$r" == "0" ] && echo "running") || ([ "$r" == "1" ] && echo "not run" ) || echo "no bin" + return $r +} + +_isrunning(){ + config_get binpath $CONFIGURATION binpath "/usr/bin/AdGuardHome" + [ ! -f "$binpath" ] && return 2 + pgrep $binpath 2>&1 >/dev/null && return 0 + return 1 +} + +force_reload(){ + config_load "${CONFIGURATION}" + _isrunning && procd_send_signal "$CONFIGURATION" || start +} + +get_tz() +{ + SET_TZ="" + + if [ -e "/etc/localtime" ]; then + return + fi + + for tzfile in /etc/TZ /var/etc/TZ + do + if [ ! -e "$tzfile" ]; then + continue + fi + + tz="`cat $tzfile 2>/dev/null`" + done + + if [ -z "$tz" ]; then + return + fi + + SET_TZ=$tz +} + +rm_port53() +{ + local AdGuardHome_PORT=$(config_editor "dns.port" "" "$configpath" "1") + dnsmasq_port=$(uci get dhcp.@dnsmasq[0].port 2>/dev/null) + if [ -z "$dnsmasq_port" ]; then + dnsmasq_port="53" + fi + if [ "$dnsmasq_port" == "$AdGuardHome_PORT" ]; then + if [ "$dnsmasq_port" == "53" ]; then + dnsmasq_port="1745" + fi + elif [ "$dnsmasq_port" == "53" ]; then + return + fi + config_editor "dns.port" "$dnsmasq_port" "$configpath" + uci set dhcp.@dnsmasq[0].port="53" + uci commit dhcp + config_get binpath $CONFIGURATION binpath "/usr/bin/AdGuardHome" + killall -9 $binpath + /etc/init.d/dnsmasq restart +} + +use_port53() +{ + local AdGuardHome_PORT=$(config_editor "dns.port" "" "$configpath" "1") + dnsmasq_port=$(uci get dhcp.@dnsmasq[0].port 2>/dev/null) + if [ -z "$dnsmasq_port" ]; then + dnsmasq_port="53" + fi + if [ "$dnsmasq_port" == "$AdGuardHome_PORT" ]; then + if [ "$dnsmasq_port" == "53" ]; then + AdGuardHome_PORT="1745" + fi + elif [ "$AdGuardHome_PORT" == "53" ]; then + return + fi + config_editor "dns.port" "53" "$configpath" + uci set dhcp.@dnsmasq[0].port="$AdGuardHome_PORT" + uci commit dhcp + /etc/init.d/dnsmasq reload +} + +do_redirect() +{ + config_load "${CONFIGURATION}" + _do_redirect $1 +} + +_do_redirect() +{ + local section="$CONFIGURATION" + args="" + ipv6_server=1 + tcp_server=0 + enabled=$1 + if [ "$enabled" == "1" ]; then + echo -n "1">/var/run/AdGredir + else + echo -n "0">/var/run/AdGredir + fi + config_get configpath $CONFIGURATION configpath "/etc/AdGuardHome.yaml" + AdGuardHome_PORT=$(config_editor "dns.port" "" "$configpath" "1") + if [ ! -s "$configpath" ]; then + cp -f /usr/share/AdGuardHome/AdGuardHome_template.yaml $configpath + fi + if [ -z "$AdGuardHome_PORT" ]; then + AdGuardHome_PORT="0" + fi + config_get "redirect" "$section" "redirect" "none" + config_get "old_redirect" "$section" "old_redirect" "none" + config_get "old_port" "$section" "old_port" "0" + config_get "old_enabled" "$section" "old_enabled" "0" + uci get dhcp.@dnsmasq[0].port >/dev/null 2>&1 || uci set dhcp.@dnsmasq[0].port="53" >/dev/null 2>&1 + if [ "$old_enabled" = "1" -a "$old_redirect" == "exchange" ]; then + AdGuardHome_PORT=$(uci get dhcp.@dnsmasq[0].port 2>/dev/null) + fi + + if [ "$old_redirect" != "$redirect" ] || [ "$old_port" != "$AdGuardHome_PORT" ] || [ "$old_enabled" = "1" -a "$enabled" = "0" ]; then + if [ "$old_redirect" != "none" ]; then + if [ "$old_redirect" == "redirect" -a "$old_port" != "0" ]; then + clear_iptable "$old_port" "$ipv6_server" + elif [ "$old_redirect" == "dnsmasq-upstream" ]; then + stop_forward_dnsmasq "$old_port" + elif [ "$old_redirect" == "exchange" ]; then + rm_port53 + fi + fi + elif [ "$old_enabled" = "1" -a "$enabled" = "1" ]; then + if [ "$old_redirect" == "redirect" -a "$old_port" != "0" ]; then + clear_iptable "$old_port" "$ipv6_server" + fi + fi + uci delete AdGuardHome.@AdGuardHome[0].old_redirect 2>/dev/null + uci delete AdGuardHome.@AdGuardHome[0].old_port 2>/dev/null + uci delete AdGuardHome.@AdGuardHome[0].old_enabled 2>/dev/null + uci add_list AdGuardHome.@AdGuardHome[0].old_redirect="$redirect" 2>/dev/null + uci add_list AdGuardHome.@AdGuardHome[0].old_port="$AdGuardHome_PORT" 2>/dev/null + uci add_list AdGuardHome.@AdGuardHome[0].old_enabled="$enabled" 2>/dev/null + uci commit AdGuardHome + [ "$enabled" == "0" ] && return 1 + if [ "$AdGuardHome_PORT" == "0" ]; then + return 1 + fi + if [ "$redirect" = "redirect" ]; then + set_iptable $ipv6_server $tcp_server + elif [ "$redirect" = "dnsmasq-upstream" ]; then + set_forward_dnsmasq "$AdGuardHome_PORT" + elif [ "$redirect" == "exchange" -a "$(uci get dhcp.@dnsmasq[0].port 2>/dev/null)" == "53" ]; then + use_port53 + fi +} + +get_filesystem() +{ +# print out path filesystem + echo $1 | awk ' + BEGIN{ + while (("mount"| getline ret) > 0) + { + split(ret,d); + fs[d[3]]=d[5]; + m=index(d[1],":") + if (m==0) + { + pt[d[3]]=d[1] + }else{ + pt[d[3]]=substr(d[1],m+1) + }}}{ + split($0,d,"/"); + if ("/" in fs) + { + result1=fs["/"]; + } + if ("/" in pt) + { + result2=pt["/"]; + } + for (i=2;i<=length(d);i++) + { + p[i]=p[i-1]"/"d[i]; + if (p[i] in fs) + { + result1=fs[p[i]]; + result2=pt[p[i]]; + } + } + if (result2 in fs){ + result=fs[result2]} + else{ + result=result1} + print(result);}' +} + +config_editor() +{ + awk -v yaml="$1" -v value="$2" -v file="$3" -v ro="$4" ' + BEGIN{split(yaml,part,"\.");s="";i=1;l=length(part);} + { + if (match($0,s""part[i]":")) + { + if (i==l) + { + split($0,t,": "); + if (ro==""){ + system("sed -i '\''"FNR"c \\"t[1]": "value"'\'' "file); + }else{ + print(t[2]); + } + exit; + } + s=s"[- ]{2}"; + i++; + } + }' $3 +} + +boot_service() { + rm /var/run/AdGserverdis >/dev/null 2>&1 + config_load "${CONFIGURATION}" + config_get waitonboot $CONFIGURATION waitonboot "0" + config_get_bool enabled $CONFIGURATION enabled 0 + config_get binpath $CONFIGURATION binpath "/usr/bin/AdGuardHome" + [ -f "$binpath" ] && start_service + if [ "$enabled" == "1" ] && [ "$waitonboot" == "1" ]; then + procd_open_instance "waitnet" + procd_set_param command "/usr/share/AdGuardHome/waitnet.sh" + procd_close_instance + echo "no net start pinging" + fi +} + +testbackup(){ + config_load "${CONFIGURATION}" + if [ "$1" == "backup" ]; then + backup + elif [ "$1" == "restore" ]; then + restore + fi +} + +restore() +{ + config_get workdir $CONFIGURATION workdir "/etc/AdGuardHome" + config_get backupwdpath $CONFIGURATION backupwdpath "/etc/AdGuardHome" + cp -u -r -f $backupwdpath/data $workdir +} + +backup() { + config_get backupwdpath $CONFIGURATION backupwdpath "/etc/AdGuardHome" + mkdir -p $backupwdpath/data + config_get workdir $CONFIGURATION workdir "/etc/AdGuardHome" + config_get backupfile $CONFIGURATION backupfile "" + for one in $backupfile; + do + while : + do + if [ -d "$backupwdpath/data/$one" ]; then + cpret=$(cp -u -r -f $workdir/data/$one $backupwdpath/data 2>&1) + else + cpret=$(cp -u -r -f $workdir/data/$one $backupwdpath/data/$one 2>&1) + fi + echo "$cpret" + echo "$cpret" | grep "no space left on device" + if [ "$?" == "0" ]; then + echo "磁盘已满,删除log重试中" + del_querylog && continue + rm -f -r $backupwdpath/data/filters + rm -f -r $workdir/data/filters && continue + echo "backup failed" + fi + break + done + done +} + +start_service() { + # Reading config + rm /var/run/AdGserverdis >/dev/null 2>&1 + config_load "${CONFIGURATION}" + # update password + config_get hashpass $CONFIGURATION hashpass "" + config_get configpath $CONFIGURATION configpath "/etc/AdGuardHome.yaml" + if [ -n "$hashpass" ]; then + config_editor "users.password" "$hashpass" "$configpath" + uci set $CONFIGURATION.$CONFIGURATION.hashpass="" + fi + local enabled + config_get_bool enabled $CONFIGURATION enabled 0 + # update crontab + do_crontab + if [ "$enabled" == "0" ]; then + _do_redirect 0 + return + fi + #what need to do before reload + config_get workdir $CONFIGURATION workdir "/etc/AdGuardHome" + + config_get backupfile $CONFIGURATION backupfile "" + mkdir -p $workdir/data + if [ -n "$backupfile" ] && [ ! -d "$workdir/data" ]; then + restore + fi + # for overlay data-stk-oo not suppport + local cwdfs=$(get_filesystem $workdir) + echo "workdir is a $cwdfs filesystem" + if [ "$cwdfs" == "jffs2" ]; then + echo "fs error ln db to tmp $workdir $cwdfs" + logger "AdGuardHome" "warning db redirect to tmp" + touch $workdir/data/stats.db + if [ ! -L $workdir/data/stats.db ]; then + mv -f $workdir/data/stats.db /tmp/stats.db 2>/dev/null + ln -s /tmp/stats.db $workdir/data/stats.db 2>/dev/null + fi + touch $workdir/data/sessions.db + if [ ! -L $workdir/data/sessions.db ]; then + mv -f $workdir/data/sessions.db /tmp/sessions.db 2>/dev/null + ln -s /tmp/sessions.db $workdir/data/sessions.db 2>/dev/null + fi + fi + local ADDITIONAL_ARGS="" + config_get binpath $CONFIGURATION binpath "/usr/bin/AdGuardHome" + + mkdir -p ${binpath%/*} + ADDITIONAL_ARGS="$ADDITIONAL_ARGS -c $configpath" + ADDITIONAL_ARGS="$ADDITIONAL_ARGS -w $workdir" + config_get httpport $CONFIGURATION httpport 3000 + ADDITIONAL_ARGS="$ADDITIONAL_ARGS -p $httpport" + + # hack to save config file when upgrade system + config_get upprotect $CONFIGURATION upprotect "" + eval upprotect=${upprotect// /\\\\n} + echo -e "$upprotect">/lib/upgrade/keep.d/luci-app-adguardhome + + config_get logfile $CONFIGURATION logfile "" + if [ -n "$logfile" ]; then + ADDITIONAL_ARGS="$ADDITIONAL_ARGS -l $logfile" + fi + + if [ ! -f "$binpath" ]; then + _do_redirect 0 + /usr/share/AdGuardHome/update_core.sh 2>&1 >/tmp/AdGuardHome_update.log & + exit 0 + fi + + config_get_bool verbose $CONFIGURATION verbose 0 + if [ "$verbose" -eq 1 ]; then + ADDITIONAL_ARGS="$ADDITIONAL_ARGS -v" + fi + + procd_open_instance + get_tz + if [ -n "$SET_TZ" ]; then + procd_set_param env TZ="$SET_TZ" + fi + procd_set_param respawn ${respawn_threshold:-3600} ${respawn_timeout:-5} ${respawn_retry:-5} + procd_set_param limits core="unlimited" nofile="65535 65535" + procd_set_param stderr 1 + procd_set_param command $binpath $ADDITIONAL_ARGS + procd_set_param file "$configpath" "/etc/hosts" "/etc/config/AdGuardHome" + procd_close_instance + if [ -f "$configpath" ]; then + _do_redirect 1 + else + _do_redirect 0 + config_get "redirect" "AdGuardHome" "redirect" "none" + if [ "$redirect" != "none" ]; then + procd_open_instance "waitconfig" + procd_set_param command "/usr/share/AdGuardHome/watchconfig.sh" + procd_close_instance + echo "no config start watching" + fi + fi + echo "AdGuardHome service enabled" + echo "luci enable switch=$enabled" + (sleep 10 && [ -z "$(pgrep $binpath)" ] && logger "AdGuardHome" "no process in 10s cancel redirect" && _do_redirect 0 )& + if [[ "`uci get bypass.@global[0].global_server 2>/dev/null`" && "`uci get bypass.@global[0].adguardhome 2>/dev/null`" == 1 && "$(uci get dhcp.@dnsmasq[0].port)" == "53" ]]; then + uci -q set AdGuardHome.AdGuardHome.redirect='exchange' + uci commit AdGuardHome + do_redirect 1 + fi +} + +reload_service() +{ + rm /var/run/AdGlucitest >/dev/null 2>&1 + echo "AdGuardHome reloading" + start +} + +del_querylog(){ + local btarget=$(ls $backupwdpath/data | grep -F "querylog.json" | sort -r | head -n 1) + local wtarget=$(ls $workdir/data | grep -F "querylog.json" | sort -r | head -n 1) + if [ "$btarget"x == "$wtarget"x ]; then + [ -z "$btarget" ] && return 1 + rm -f $workdir/data/$wtarget + rm -f $backupwdpath/data/$btarget + return 0 + fi + if [ "$btarget" \> "$wtarget" ]; then + rm -f $backupwdpath/data/$btarget + return 0 + else + rm -f $workdir/data/$wtarget + return 0 + fi +} + +stop_service() +{ + config_load "${CONFIGURATION}" + _do_redirect 0 + do_crontab + if [ "$1" != "nobackup" ]; then + config_get backupfile $CONFIGURATION backupfile "0" + if [ -n "$backupfile" ]; then + backup + fi + fi + echo "AdGuardHome service disabled" + touch /var/run/AdGserverdis +} + +boot() { + rc_procd boot_service "$@" + if eval "type service_started" 2>/dev/null >/dev/null; then + service_started + fi +} + +test_crontab(){ + config_load "${CONFIGURATION}" + do_crontab +} + +do_crontab(){ + config_get_bool enabled $CONFIGURATION enabled 0 + config_get crontab $CONFIGURATION crontab "" + local findstr default cronenable replace commit + local cronreload=0 + local commit=0 + findstr="/usr/share/AdGuardHome/update_core.sh" + default="30 3 * * * /usr/share/AdGuardHome/update_core.sh 2>&1" + [ "$enabled" == "0" ] || [ "${crontab//autoupdate/}" == "$crontab" ] && cronenable=0 || cronenable=1 + crontab_editor + + config_get workdir $CONFIGURATION workdir "/etc/AdGuardHome" + config_get lastworkdir $CONFIGURATION lastworkdir "/etc/AdGuardHome" + findstr="/usr/share/AdGuardHome/tailto.sh [0-9]* \$(uci get AdGuardHome.AdGuardHome.workdir)/data/querylog.json" + #[ -n "$lastworkdir" ] && findstr="/usr/share/AdGuardHome/tailto.sh [0-9]* $lastworkdir/data/querylog.json" && [ "$lastworkdir" != "$workdir" ] && replace="${lastworkdir//\//\\/}/${workdir//\//\\/}" + default="0 * * * * /usr/share/AdGuardHome/tailto.sh 2000 \$(uci get AdGuardHome.AdGuardHome.workdir)/data/querylog.json" + [ "$enabled" == "0" ] || [ "${crontab//cutquerylog/}" == "$crontab" ] && cronenable=0 || cronenable=1 + crontab_editor + #[ "$lastworkdir" != "$workdir" ] && uci set AdGuardHome.AdGuardHome.lastworkdir="$workdir" && commit=1 + + config_get logfile $CONFIGURATION logfile "" + config_get lastlogfile $CONFIGURATION lastlogfile "" + findstr="/usr/share/AdGuardHome/tailto.sh [0-9]* \$(uci get AdGuardHome.AdGuardHome.logfile)" + default="30 3 * * * /usr/share/AdGuardHome/tailto.sh 2000 \$(uci get AdGuardHome.AdGuardHome.logfile)" + #[ -n "$lastlogfile" ] && findstr="/usr/share/AdGuardHome/tailto.sh [0-9]* $lastlogfile" && [ -n "$logfile" ] && [ "$lastlogfile" != "$logfile" ] && replace="${lastlogfile//\//\\/}/${logfile//\//\\/}" + [ "$logfile" == "syslog" ] || [ "$logfile" == "" ] || [ "$enabled" == "0" ] || [ "${crontab//cutruntimelog/}" == "$crontab" ] && cronenable=0 || cronenable=1 + crontab_editor + #[ -n "$logfile" ] && [ "$lastlogfile" != "$logfile" ] && uci set AdGuardHome.AdGuardHome.lastlogfile="$logfile" && commit=1 + + findstr="/usr/share/AdGuardHome/addhost.sh" + default="0 * * * * /usr/share/AdGuardHome/addhost.sh" + [ "$enabled" == "0" ] || [ "${crontab//autohost/}" == "$crontab" ] && cronenable=0 || cronenable=1 + crontab_editor + [ "$cronenable" == "0" ] && /usr/share/AdGuardHome/addhost.sh "del" "noreload" || /usr/share/AdGuardHome/addhost.sh "" "noreload" + + findstr="/usr/share/AdGuardHome/gfw2adg.sh" + default="30 3 * * * /usr/share/AdGuardHome/gfw2adg.sh" + [ "$enabled" == "0" ] || [ "${crontab//autogfw/}" == "$crontab" ] && cronenable=0 || cronenable=1 + crontab_editor + [ "$cronreload" -gt 0 ] && /etc/init.d/cron restart + #[ "$commit" -gt 0 ] && uci commit AdGuardHome +} + +crontab_editor(){ + #usage input: + #findstr= + #default= + #cronenable= + #replace="${last//\//\\/}/${now//\//\\/}" + #output:cronreload:if >1 please /etc/init.d/cron restart manual + local testline reload + local line="$(grep "$findstr" $CRON_FILE)" + [ -n "$replace" ] && [ -n "$line" ] && eval testline="\${line//$replace}" && [ "$testline" != "$line" ] && line="$testline" && reload="1" && replace="" + if [ "${line:0:1}" != "#" ]; then + if [ $cronenable -eq 1 ]; then + [ -z "$line" ] && line="$default" && reload="1" + if [ -n "$reload" ]; then + sed -i "\,$findstr,d" $CRON_FILE + echo "$line" >> $CRON_FILE + cronreload=$((cronreload+1)) + fi + elif [ -n "$line" ]; then + sed -i "\,$findstr,d" $CRON_FILE + echo "#$line" >> $CRON_FILE + cronreload=$((cronreload+1)) + fi + else + if [ $cronenable -eq 1 ]; then + sed -i "\,$findstr,d" $CRON_FILE + echo "${line:1}" >> $CRON_FILE + cronreload=$((cronreload+1)) + elif [ -z "$reload" ]; then + sed -i "\,$findstr,d" $CRON_FILE + echo "$line" >> $CRON_FILE + fi + fi +} diff --git a/luci-app-adguardhome/root/etc/uci-defaults/40_luci-AdGuardHome b/luci-app-adguardhome/root/etc/uci-defaults/40_luci-AdGuardHome new file mode 100644 index 000000000..37e192cdd --- /dev/null +++ b/luci-app-adguardhome/root/etc/uci-defaults/40_luci-AdGuardHome @@ -0,0 +1,15 @@ +#!/bin/sh + +uci -q batch <<-EOF >/dev/null 2>&1 + delete ucitrack.@AdGuardHome[-1] + add ucitrack AdGuardHome + set ucitrack.@AdGuardHome[-1].init=AdGuardHome + commit ucitrack + delete AdGuardHome.AdGuardHome.ucitracktest + /etc/init.d/AdGuardHome restart +EOF + +rm -f /tmp/luci-indexcache + +chmod +x /etc/init.d/AdGuardHome /usr/share/AdGuardHome/* +exit 0 diff --git a/luci-app-adguardhome/root/usr/share/AdGuardHome/AdGuardHome_template.yaml b/luci-app-adguardhome/root/usr/share/AdGuardHome/AdGuardHome_template.yaml new file mode 100644 index 000000000..612a57706 --- /dev/null +++ b/luci-app-adguardhome/root/usr/share/AdGuardHome/AdGuardHome_template.yaml @@ -0,0 +1,131 @@ +bind_host: 0.0.0.0 +bind_port: 3000 +beta_bind_port: 0 +users: +- name: root + password: $2y$10$dwn0hTYoECQMZETBErGlzOId2VANOVsPHsuH13TM/8KnysM5Dh/ve +auth_attempts: 5 +block_auth_min: 15 +http_proxy: "" +language: zh-cn +debug_pprof: false +web_session_ttl: 720 +dns: + bind_hosts: + - 0.0.0.0 + port: 1745 + statistics_interval: 30 + querylog_enabled: true + querylog_file_enabled: true + querylog_interval: 6h + querylog_size_memory: 1000 + anonymize_client_ip: false + protection_enabled: true + blocking_mode: default + blocking_ipv4: "" + blocking_ipv6: "" + blocked_response_ttl: 60 + parental_block_host: family-block.dns.adguard.com + safebrowsing_block_host: standard-block.dns.adguard.com + ratelimit: 0 + ratelimit_whitelist: [] + refuse_any: false + upstream_dns: + - 223.5.5.5 + upstream_dns_file: "" + bootstrap_dns: + - 119.29.29.29 + - 223.5.5.5 + all_servers: false + fastest_addr: false + fastest_timeout: 1s + allowed_clients: [] + disallowed_clients: [] + blocked_hosts: + - version.bind + - id.server + - hostname.bind + trusted_proxies: + - 127.0.0.0/8 + - ::1/128 + cache_size: 4194304 + cache_ttl_min: 0 + cache_ttl_max: 0 + cache_optimistic: true + bogus_nxdomain: [] + aaaa_disabled: false + enable_dnssec: false + edns_client_subnet: false + max_goroutines: 300 + ipset: [] + filtering_enabled: true + filters_update_interval: 24 + parental_enabled: false + safesearch_enabled: false + safebrowsing_enabled: false + safebrowsing_cache_size: 1048576 + safesearch_cache_size: 1048576 + parental_cache_size: 1048576 + cache_time: 30 + rewrites: [] + blocked_services: [] + upstream_timeout: 10s + local_domain_name: lan + resolve_clients: true + use_private_ptr_resolvers: true + local_ptr_upstreams: [] +tls: + enabled: false + server_name: "" + force_https: false + port_https: 443 + port_dns_over_tls: 853 + port_dns_over_quic: 784 + port_dnscrypt: 0 + dnscrypt_config_file: "" + allow_unencrypted_doh: false + strict_sni_check: false + certificate_chain: "" + private_key: "" + certificate_path: "" + private_key_path: "" +filters: +- enabled: true + url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + name: AdGuard DNS filter + id: 1628750870 +- enabled: true + url: https://anti-ad.net/easylist.txt + name: 'CHN: anti-AD' + id: 1628750871 +whitelist_filters: [] +user_rules: [] +dhcp: + enabled: false + interface_name: "" + dhcpv4: + gateway_ip: "" + subnet_mask: "" + range_start: "" + range_end: "" + lease_duration: 86400 + icmp_timeout_msec: 1000 + options: [] + dhcpv6: + range_start: "" + lease_duration: 86400 + ra_slaac_only: false + ra_allow_slaac: false +clients: [] +log_compress: false +log_localtime: false +log_max_backups: 0 +log_max_size: 100 +log_max_age: 3 +log_file: "" +verbose: false +os: + group: "" + user: "" + rlimit_nofile: 0 +schema_version: 12 diff --git a/luci-app-adguardhome/root/usr/share/AdGuardHome/addhost.sh b/luci-app-adguardhome/root/usr/share/AdGuardHome/addhost.sh new file mode 100644 index 000000000..6c6b7b769 --- /dev/null +++ b/luci-app-adguardhome/root/usr/share/AdGuardHome/addhost.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +checkmd5(){ +local nowmd5=$(md5sum /etc/hosts) +nowmd5=${nowmd5%% *} +local lastmd5=$(uci get AdGuardHome.AdGuardHome.hostsmd5 2>/dev/null) +if [ "$nowmd5" != "$lastmd5" ]; then + uci set AdGuardHome.AdGuardHome.hostsmd5="$nowmd5" + uci commit AdGuardHome + [ "$1" == "noreload" ] || /etc/init.d/AdGuardHome reload +fi +} + +[ "$1" == "del" ] && sed -i '/programaddstart/,/programaddend/d' /etc/hosts && checkmd5 "$2" && exit 0 +/usr/bin/awk 'BEGIN{ +while ((getline < "/tmp/dhcp.leases") > 0) +{ + a[$2]=$4; +} +while (("ip -6 neighbor show | grep -v fe80" | getline) > 0) +{ + if (a[$5]) {print $1" "a[$5] >"/tmp/tmphost"; } +} +print "#programaddend" >"/tmp/tmphost"; +}' +grep programaddstart /etc/hosts >/dev/null 2>&1 +if [ "$?" == "0" ]; then + sed -i '/programaddstart/,/programaddend/c\#programaddstart' /etc/hosts + sed -i '/programaddstart/'r/tmp/tmphost /etc/hosts +else + echo "#programaddstart" >>/etc/hosts + cat /tmp/tmphost >> /etc/hosts +fi +rm /tmp/tmphost +checkmd5 "$2" diff --git a/luci-app-adguardhome/root/usr/share/AdGuardHome/firewall.start b/luci-app-adguardhome/root/usr/share/AdGuardHome/firewall.start new file mode 100644 index 000000000..562117e52 --- /dev/null +++ b/luci-app-adguardhome/root/usr/share/AdGuardHome/firewall.start @@ -0,0 +1,8 @@ +#!/bin/sh + +AdGuardHome_enable=$(uci get AdGuardHome.AdGuardHome.enabled) +redirect=$(uci get AdGuardHome.AdGuardHome.redirect) + +if [ $AdGuardHome_enable -eq 1 -a "$redirect" == "redirect" ]; then + /etc/init.d/AdGuardHome do_redirect 1 +fi diff --git a/luci-app-adguardhome/root/usr/share/AdGuardHome/getsyslog.sh b/luci-app-adguardhome/root/usr/share/AdGuardHome/getsyslog.sh new file mode 100644 index 000000000..908bdf631 --- /dev/null +++ b/luci-app-adguardhome/root/usr/share/AdGuardHome/getsyslog.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +PATH="/usr/sbin:/usr/bin:/sbin:/bin" +logread -e AdGuardHome > /tmp/AdGuardHometmp.log +logread -e AdGuardHome -f >> /tmp/AdGuardHometmp.log & +pid=$! +echo "1">/var/run/AdGuardHomesyslog +while true +do + sleep 12 + watchdog=$(cat /var/run/AdGuardHomesyslog) + if [ "$watchdog"x == "0"x ]; then + kill $pid + rm /tmp/AdGuardHometmp.log + rm /var/run/AdGuardHomesyslog + exit 0 + else + echo "0">/var/run/AdGuardHomesyslog + fi +done diff --git a/luci-app-adguardhome/root/usr/share/AdGuardHome/gfw2adg.sh b/luci-app-adguardhome/root/usr/share/AdGuardHome/gfw2adg.sh new file mode 100644 index 000000000..a3add84e8 --- /dev/null +++ b/luci-app-adguardhome/root/usr/share/AdGuardHome/gfw2adg.sh @@ -0,0 +1,89 @@ +#!/bin/sh + +PATH="/usr/sbin:/usr/bin:/sbin:/bin" + +checkmd5(){ +local nowmd5=$(md5sum /tmp/adguard.list 2>/dev/null) +nowmd5=${nowmd5%% *} +local lastmd5=$(uci get AdGuardHome.AdGuardHome.gfwlistmd5 2>/dev/null) +if [ "$nowmd5" != "$lastmd5" ]; then + uci set AdGuardHome.AdGuardHome.gfwlistmd5="$nowmd5" + uci commit AdGuardHome + [ "$1" == "noreload" ] || /etc/init.d/AdGuardHome reload +fi +} + +configpath=$(uci get AdGuardHome.AdGuardHome.configpath 2>/dev/null) +[ "$1" == "del" ] && sed -i '/programaddstart/,/programaddend/d' $configpath && checkmd5 "$2" && exit 0 +gfwupstream=$(uci get AdGuardHome.AdGuardHome.gfwupstream 2>/dev/null) +if [ -z $gfwupstream ]; then +gfwupstream="tcp://208.67.220.220:5353" +fi +if [ ! -f "$configpath" ]; then + echo "please make a config first" + exit 1 +fi +wget-ssl --no-check-certificate https://cdn.jsdelivr.net/gh/gfwlist/gfwlist/gfwlist.txt -O- | base64 -d > /tmp/gfwlist.txt +cat /tmp/gfwlist.txt | awk -v upst="$gfwupstream" 'BEGIN{getline;}{ +s1=substr($0,1,1); +if (s1=="!") +{next;} +if (s1=="@"){ + $0=substr($0,3); + s1=substr($0,1,1); + white=1;} +else{ + white=0; +} + +if (s1=="|") + {s2=substr($0,2,1); + if (s2=="|") + { + $0=substr($0,3); + split($0,d,"/"); + $0=d[1]; + }else{ + split($0,d,"/"); + $0=d[3]; + }} +else{ + split($0,d,"/"); + $0=d[1]; +} +star=index($0,"*"); +if (star!=0) +{ + $0=substr($0,star+1); + dot=index($0,"."); + if (dot!=0) + $0=substr($0,dot+1); + else + next; + s1=substr($0,1,1); +} +if (s1==".") +{fin=substr($0,2);} +else{fin=$0;} +if (index(fin,".")==0) next; +if (index(fin,"%")!=0) next; +if (index(fin,":")!=0) next; +match(fin,"^[0-9\.]+") +if (RSTART==1 && RLENGTH==length(fin)) {print "ipset add gfwlist "fin>"/tmp/doipset.sh";next;} +if (fin=="" || finl==fin) next; +finl=fin; +if (white==0) + {print(" - '\''[/"fin"/]"upst"'\''");} +else{ + print(" - '\''[/"fin"/]#'\''");} +}END{print(" - '\''[/programaddend/]#'\''")}' > /tmp/adguard.list +grep programaddstart $configpath +if [ "$?" == "0" ]; then + sed -i '/programaddstart/,/programaddend/c\ - '\''\[\/programaddstart\/\]#'\''' $configpath + sed -i '/programaddstart/'r/tmp/adguard.list $configpath +else + sed -i '1i\ - '\''[/programaddstart/]#'\''' /tmp/adguard.list + sed -i '/upstream_dns:/'r/tmp/adguard.list $configpath +fi +checkmd5 "$2" +rm -f /tmp/gfwlist.txt /tmp/adguard.list diff --git a/luci-app-adguardhome/root/usr/share/AdGuardHome/links.txt b/luci-app-adguardhome/root/usr/share/AdGuardHome/links.txt new file mode 100644 index 000000000..e4f1c8fa7 --- /dev/null +++ b/luci-app-adguardhome/root/usr/share/AdGuardHome/links.txt @@ -0,0 +1,3 @@ +https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_${Arch}.tar.gz +https://github.com/AdguardTeam/AdGuardHome/releases/download/${latest_ver}/AdGuardHome_linux_${Arch}.tar.gz +https://static.adguard.com/adguardhome/release/AdGuardHome_linux_${Arch}.tar.gz diff --git a/luci-app-adguardhome/root/usr/share/AdGuardHome/tailto.sh b/luci-app-adguardhome/root/usr/share/AdGuardHome/tailto.sh new file mode 100644 index 000000000..9ccc21903 --- /dev/null +++ b/luci-app-adguardhome/root/usr/share/AdGuardHome/tailto.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +tail -n $1 "$2" > /var/run/tailtmp +cat /var/run/tailtmp > "$2" +rm /var/run/tailtmp diff --git a/luci-app-adguardhome/root/usr/share/AdGuardHome/update_core.sh b/luci-app-adguardhome/root/usr/share/AdGuardHome/update_core.sh new file mode 100644 index 000000000..74ba7268d --- /dev/null +++ b/luci-app-adguardhome/root/usr/share/AdGuardHome/update_core.sh @@ -0,0 +1,236 @@ +#!/bin/bash + +PATH="/usr/sbin:/usr/bin:/sbin:/bin" +binpath=$(uci get AdGuardHome.AdGuardHome.binpath) +if [ -z "$binpath" ]; then +uci set AdGuardHome.AdGuardHome.binpath="/tmp/AdGuardHome/AdGuardHome" +binpath="/tmp/AdGuardHome/AdGuardHome" +fi +mkdir -p ${binpath%/*} +upxflag=$(uci get AdGuardHome.AdGuardHome.upxflag 2>/dev/null) + +check_if_already_running(){ + running_tasks="$(ps |grep "AdGuardHome" |grep "update_core" |grep -v "grep" |awk '{print $1}' |wc -l)" + [ "${running_tasks}" -gt "2" ] && echo -e "\nA task is already running." && EXIT 2 +} + +check_wgetcurl(){ + which curl && downloader="curl -L -k --retry 2 --connect-timeout 20 -o" && return + which wget-ssl && downloader="wget-ssl --no-check-certificate -t 2 -T 20 -O" && return + [ -z "$1" ] && opkg update || (echo error opkg && EXIT 1) + [ -z "$1" ] && (opkg remove wget wget-nossl --force-depends ; opkg install wget ; check_wgetcurl 1 ;return) + [ "$1" == "1" ] && (opkg install curl ; check_wgetcurl 2 ; return) + echo error curl and wget && EXIT 1 +} + +check_latest_version(){ + check_wgetcurl + latest_ver="$($downloader - https://api.github.com/repos/AdguardTeam/AdGuardHome/releases/latest 2>/dev/null|grep -E 'tag_name' |grep -E 'v[0-9.]+' -o 2>/dev/null)" + if [ -z "${latest_ver}" ]; then + echo -e "\nFailed to check latest version, please try again later." && EXIT 1 + fi + now_ver="$($binpath -c /dev/null --check-config 2>&1| grep -m 1 -E 'v[0-9.]+' -o)" + if [ "${latest_ver}"x != "${now_ver}"x ] || [ "$1" == "force" ]; then + echo -e "Local version: ${now_ver}., cloud version: ${latest_ver}." + doupdate_core + else + echo -e "\nLocal version: ${now_ver}, cloud version: ${latest_ver}." + echo -e "You're already using the latest version." + if [ ! -z "$upxflag" ]; then + filesize=$(ls -l $binpath | awk '{ print $5 }') + if [ $filesize -gt 8000000 ]; then + echo -e "start upx may take a long time" + doupx + mkdir -p "/tmp/AdGuardHomeupdate/AdGuardHome" >/dev/null 2>&1 + rm -fr /tmp/AdGuardHomeupdate/AdGuardHome/${binpath##*/} + /tmp/upx-${upx_latest_ver}-${Arch}_linux/upx $upxflag $binpath -o /tmp/AdGuardHomeupdate/AdGuardHome/${binpath##*/} + rm -rf /tmp/upx-${upx_latest_ver}-${Arch}_linux + /etc/init.d/AdGuardHome stop nobackup + rm $binpath + mv -f /tmp/AdGuardHomeupdate/AdGuardHome/${binpath##*/} $binpath + /etc/init.d/AdGuardHome start + echo -e "finished" + fi + fi + EXIT 0 + fi +} + +doupx(){ + Archt="$(opkg info kernel | grep Architecture | awk -F "[ _]" '{print($2)}')" + case $Archt in + "i386") + Arch="i386" + ;; + "i686") + Arch="i386" + echo -e "i686 use $Arch may have bug" + ;; + "x86") + Arch="amd64" + ;; + "mipsel") + Arch="mipsel" + ;; + "mips64el") + Arch="mips64el" + Arch="mipsel" + echo -e "mips64el use $Arch may have bug" + ;; + "mips") + Arch="mips" + ;; + "mips64") + Arch="mips64" + Arch="mips" + echo -e "mips64 use $Arch may have bug" + ;; + "arm") + Arch="arm" + ;; + "armeb") + Arch="armeb" + ;; + "aarch64") + Arch="arm64" + ;; + "powerpc") + Arch="powerpc" + ;; + "powerpc64") + Arch="powerpc64" + ;; + *) + echo -e "error not support $Archt if you can use offical release please issue a bug" + EXIT 1 + ;; + esac + upx_latest_ver="$($downloader - https://api.github.com/repos/upx/upx/releases/latest 2>/dev/null|grep -E 'tag_name' |grep -E '[0-9.]+' -o 2>/dev/null)" + $downloader /tmp/upx-${upx_latest_ver}-${Arch}_linux.tar.xz "https://github.com/upx/upx/releases/download/v${upx_latest_ver}/upx-${upx_latest_ver}-${Arch}_linux.tar.xz" 2>&1 + #tar xvJf + which xz || (opkg list | grep ^xz || opkg update && opkg install xz) || (echo "xz download fail" && EXIT 1) + mkdir -p /tmp/upx-${upx_latest_ver}-${Arch}_linux + xz -d -c /tmp/upx-${upx_latest_ver}-${Arch}_linux.tar.xz| tar -x -C "/tmp" >/dev/null 2>&1 + if [ ! -e "/tmp/upx-${upx_latest_ver}-${Arch}_linux/upx" ]; then + echo -e "Failed to download upx." + EXIT 1 + fi + rm /tmp/upx-${upx_latest_ver}-${Arch}_linux.tar.xz +} + +doupdate_core(){ + echo -e "Updating core..." + mkdir -p "/tmp/AdGuardHomeupdate" + rm -rf /tmp/AdGuardHomeupdate/* >/dev/null 2>&1 + Archt="$(opkg info kernel | grep Architecture | awk -F "[ _]" '{print($2)}')" + case $Archt in + "i386") + Arch="386" + ;; + "i686") + Arch="386" + ;; + "x86") + Arch="amd64" + ;; + "mipsel") + Arch="mipsle" + ;; + "mips64el") + Arch="mips64le" + Arch="mipsle" + echo -e "mips64el use $Arch may have bug" + ;; + "mips") + Arch="mips" + ;; + "mips64") + Arch="mips64" + Arch="mips" + echo -e "mips64 use $Arch may have bug" + ;; + "arm") + Arch="arm" + ;; + "aarch64") + Arch="arm64" + ;; + "powerpc") + Arch="ppc" + echo -e "error not support $Archt" + EXIT 1 + ;; + "powerpc64") + Arch="ppc64" + echo -e "error not support $Archt" + EXIT 1 + ;; + *) + echo -e "error not support $Archt if you can use offical release please issue a bug" + EXIT 1 + ;; + esac + echo -e "start download" + grep -v "^#" /usr/share/AdGuardHome/links.txt >/tmp/run/AdHlinks.txt + while read link + do + eval link="$link" + $downloader /tmp/AdGuardHomeupdate/${link##*/} "$link" 2>&1 + if [ "$?" != "0" ]; then + echo "download failed try another download" + rm -f /tmp/AdGuardHomeupdate/${link##*/} + else + local success="1" + break + fi + done < "/tmp/run/AdHlinks.txt" + rm /tmp/run/AdHlinks.txt + [ -z "$success" ] && echo "no download success" && EXIT 1 + if [ "${link##*.}" == "gz" ]; then + tar -zxf "/tmp/AdGuardHomeupdate/${link##*/}" -C "/tmp/AdGuardHomeupdate/" + if [ ! -e "/tmp/AdGuardHomeupdate/AdGuardHome" ]; then + echo -e "Failed to download core." + rm -rf "/tmp/AdGuardHomeupdate" >/dev/null 2>&1 + EXIT 1 + fi + downloadbin="/tmp/AdGuardHomeupdate/AdGuardHome/AdGuardHome" + else + downloadbin="/tmp/AdGuardHomeupdate/${link##*/}" + fi + chmod 755 $downloadbin + echo -e "download success start copy" + if [ -n "$upxflag" ]; then + echo -e "start upx may take a long time" + doupx + /tmp/upx-${upx_latest_ver}-${Arch}_linux/upx $upxflag $downloadbin + rm -rf /tmp/upx-${upx_latest_ver}-${Arch}_linux + fi + echo -e "start copy" + /etc/init.d/AdGuardHome stop nobackup + rm "$binpath" + mv -f "$downloadbin" "$binpath" + if [ "$?" == "1" ]; then + echo "mv failed maybe not enough space please use upx or change bin to /tmp/AdGuardHome" + EXIT 1 + fi + /etc/init.d/AdGuardHome start + rm -rf "/tmp/AdGuardHomeupdate" >/dev/null 2>&1 + echo -e "Succeeded in updating core." + echo -e "Local version: ${latest_ver}, cloud version: ${latest_ver}.\n" + EXIT 0 +} + +EXIT(){ + rm /var/run/update_core 2>/dev/null + [ "$1" != "0" ] && touch /var/run/update_core_error + exit $1 +} + +main(){ + check_if_already_running + check_latest_version $1 +} + trap "EXIT 1" SIGTERM SIGINT + touch /var/run/update_core + rm /var/run/update_core_error 2>/dev/null + main $1 diff --git a/luci-app-adguardhome/root/usr/share/AdGuardHome/waitnet.sh b/luci-app-adguardhome/root/usr/share/AdGuardHome/waitnet.sh new file mode 100644 index 000000000..c7745e101 --- /dev/null +++ b/luci-app-adguardhome/root/usr/share/AdGuardHome/waitnet.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +PATH="/usr/sbin:/usr/bin:/sbin:/bin" +count=0 +while : +do + ping -c 1 -W 1 -q www.baidu.com 1>/dev/null 2>&1 + if [ "$?" == "0" ]; then + /etc/init.d/AdGuardHome force_reload + break + fi + ping -c 1 -W 1 -q 202.108.22.5 1>/dev/null 2>&1 + if [ "$?" == "0" ]; then + /etc/init.d/AdGuardHome force_reload + break + fi + sleep 5 + ping -c 1 -W 1 -q www.google.com 1>/dev/null 2>&1 + if [ "$?" == "0" ]; then + /etc/init.d/AdGuardHome force_reload + break + fi + ping -c 1 -W 1 -q 8.8.8.8 1>/dev/null 2>&1 + if [ "$?" == "0" ]; then + /etc/init.d/AdGuardHome force_reload + break + fi + sleep 5 + count=$((count+1)) + if [ $count -gt 18 ]; then + /etc/init.d/AdGuardHome force_reload + break + fi +done +return 0 diff --git a/luci-app-adguardhome/root/usr/share/AdGuardHome/watchconfig.sh b/luci-app-adguardhome/root/usr/share/AdGuardHome/watchconfig.sh new file mode 100644 index 000000000..61ba09de7 --- /dev/null +++ b/luci-app-adguardhome/root/usr/share/AdGuardHome/watchconfig.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +PATH="/usr/sbin:/usr/bin:/sbin:/bin" +configpath=$(uci get AdGuardHome.AdGuardHome.configpath) +while : +do + sleep 10 + if [ -f "$configpath" ]; then + /etc/init.d/AdGuardHome do_redirect 1 + break + fi +done +return 0 diff --git a/luci-app-adguardhome/root/usr/share/rpcd/acl.d/luci-app-adguardhome.json b/luci-app-adguardhome/root/usr/share/rpcd/acl.d/luci-app-adguardhome.json new file mode 100644 index 000000000..485aa6205 --- /dev/null +++ b/luci-app-adguardhome/root/usr/share/rpcd/acl.d/luci-app-adguardhome.json @@ -0,0 +1,11 @@ +{ + "luci-app-adguardhome": { + "description": "Grant UCI access for luci-app-adguardhome", + "read": { + "uci": [ "AdGuardHome" ] + }, + "write": { + "uci": [ "AdGuardHome" ] + } + } +} diff --git a/luci-app-adguardhome/root/www/luci-static/resources/codemirror/addon/fold/foldcode.js b/luci-app-adguardhome/root/www/luci-static/resources/codemirror/addon/fold/foldcode.js new file mode 100644 index 000000000..f93d42b7f --- /dev/null +++ b/luci-app-adguardhome/root/www/luci-static/resources/codemirror/addon/fold/foldcode.js @@ -0,0 +1 @@ +!function(n){"object"==typeof exports&&"object"==typeof module?n(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],n):n(CodeMirror)}(function(n){"use strict";function e(e,o,i,t){if(i&&i.call){var l=i;i=null}else l=r(e,i,"rangeFinder");"number"==typeof o&&(o=n.Pos(o,0));var f=r(e,i,"minFoldSize");function d(n){var r=l(e,o);if(!r||r.to.line-r.from.linee.firstLine();)o=n.Pos(o.line-1,0),u=d(!1);if(u&&!u.cleared&&"unfold"!==t){var a=function(n,e){var o=r(n,e,"widget");if("string"==typeof o){var i=document.createTextNode(o);(o=document.createElement("span")).appendChild(i),o.className="CodeMirror-foldmarker"}else o&&(o=o.cloneNode(!0));return o}(e,i);n.on(a,"mousedown",function(e){c.clear(),n.e_preventDefault(e)});var c=e.markText(u.from,u.to,{replacedWith:a,clearOnEnter:r(e,i,"clearOnEnter"),__isFold:!0});c.on("clear",function(o,r){n.signal(e,"unfold",e,o,r)}),n.signal(e,"fold",e,u.from,u.to)}}n.newFoldFunction=function(n,o){return function(r,i){e(r,i,{rangeFinder:n,widget:o})}},n.defineExtension("foldCode",function(n,o,r){e(this,n,o,r)}),n.defineExtension("isFolded",function(n){for(var e=this.findMarksAt(n),o=0;o=u){if(s&&f&&s.test(f.className))return;i=r(a.indicatorOpen)}}(i||f)&&t.setGutterMarker(n,a.gutter,i)})}function i(t){return new RegExp("(^|\\s)"+t+"(?:$|\\s)\\s*")}function f(t){var o=t.getViewport(),e=t.state.foldGutter;e&&(t.operation(function(){n(t,o.from,o.to)}),e.from=o.from,e.to=o.to)}function a(t,r,n){var i=t.state.foldGutter;if(i){var f=i.options;if(n==f.gutter){var a=e(t,r);a?a.clear():t.foldCode(o(r,0),f)}}}function d(t){var o=t.state.foldGutter;if(o){var e=o.options;o.from=o.to=0,clearTimeout(o.changeUpdate),o.changeUpdate=setTimeout(function(){f(t)},e.foldOnChangeTimeSpan||600)}}function u(t){var o=t.state.foldGutter;if(o){var e=o.options;clearTimeout(o.changeUpdate),o.changeUpdate=setTimeout(function(){var e=t.getViewport();o.from==o.to||e.from-o.to>20||o.from-e.to>20?f(t):t.operation(function(){e.fromo.to&&(n(t,o.to,e.to),o.to=e.to)})},e.updateViewportTimeSpan||400)}}function l(t,o){var e=t.state.foldGutter;if(e){var r=o.line;r>=e.from&&ro))break;r=l}}return r?{from:e.Pos(i.line,t.getLine(i.line).length),to:e.Pos(r,t.getLine(r).length)}:void 0}})}); \ No newline at end of file diff --git a/luci-app-adguardhome/root/www/luci-static/resources/codemirror/lib/codemirror.css b/luci-app-adguardhome/root/www/luci-static/resources/codemirror/lib/codemirror.css new file mode 100644 index 000000000..43ac1a9fa --- /dev/null +++ b/luci-app-adguardhome/root/www/luci-static/resources/codemirror/lib/codemirror.css @@ -0,0 +1 @@ +.CodeMirror{font-family:monospace;height:500px;color:black;direction:ltr}.CodeMirror-lines{padding:4px 0}.CodeMirror pre.CodeMirror-line,.CodeMirror pre.CodeMirror-line-like{padding:0 4px}.CodeMirror-scrollbar-filler,.CodeMirror-gutter-filler{background-color:white}.CodeMirror-gutters{border-right:1px solid #ddd;background-color:#f7f7f7;white-space:nowrap}.CodeMirror-linenumber{padding:0 3px 0 5px;min-width:20px;text-align:right;color:#999;white-space:nowrap}.CodeMirror-guttermarker{color:black}.CodeMirror-guttermarker-subtle{color:#999}.CodeMirror-cursor{border-left:1px solid black;border-right:0;width:0}.CodeMirror div.CodeMirror-secondarycursor{border-left:1px solid silver}.cm-fat-cursor .CodeMirror-cursor{width:auto;border:0 !important;background:#7e7}.cm-fat-cursor div.CodeMirror-cursors{z-index:1}.cm-fat-cursor-mark{background-color:rgba(20,255,20,0.5);-webkit-animation:blink 1.06s steps(1) infinite;-moz-animation:blink 1.06s steps(1) infinite;animation:blink 1.06s steps(1) infinite}.cm-animate-fat-cursor{width:auto;border:0;-webkit-animation:blink 1.06s steps(1) infinite;-moz-animation:blink 1.06s steps(1) infinite;animation:blink 1.06s steps(1) infinite;background-color:#7e7}@-moz-keyframes blink{50%{background-color:transparent}}@-webkit-keyframes blink{50%{background-color:transparent}}@keyframes blink{50%{background-color:transparent}}.cm-tab{display:inline-block;text-decoration:inherit}.CodeMirror-rulers{position:absolute;left:0;right:0;top:-50px;bottom:0;overflow:hidden}.CodeMirror-ruler{border-left:1px solid #ccc;top:0;bottom:0;position:absolute}.cm-s-default .cm-header{color:blue}.cm-s-default .cm-quote{color:#090}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-header,.cm-strong{font-weight:bold}.cm-em{font-style:italic}.cm-link{text-decoration:underline}.cm-strikethrough{text-decoration:line-through}.cm-s-default .cm-keyword{color:#708}.cm-s-default .cm-atom{color:#219}.cm-s-default .cm-number{color:#164}.cm-s-default .cm-def{color:#00f}.cm-s-default .cm-variable-2{color:#05a}.cm-s-default .cm-variable-3,.cm-s-default .cm-type{color:#085}.cm-s-default .cm-comment{color:#a50}.cm-s-default .cm-string{color:#a11}.cm-s-default .cm-string-2{color:#f50}.cm-s-default .cm-meta{color:#555}.cm-s-default .cm-qualifier{color:#555}.cm-s-default .cm-builtin{color:#30a}.cm-s-default .cm-bracket{color:#997}.cm-s-default .cm-tag{color:#170}.cm-s-default .cm-attribute{color:#00c}.cm-s-default .cm-hr{color:#999}.cm-s-default .cm-link{color:#00c}.cm-s-default .cm-error{color:red}.cm-invalidchar{color:red}.CodeMirror-composing{border-bottom:2px solid}div.CodeMirror span.CodeMirror-matchingbracket{color:#0b0}div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#a22}.CodeMirror-matchingtag{background:rgba(255,150,0,.3)}.CodeMirror-activeline-background{background:#e8f2ff}.CodeMirror{position:relative;overflow:hidden;background:white}.CodeMirror-scroll{overflow:scroll !important;margin-bottom:-30px;margin-right:-30px;padding-bottom:30px;height:100%;outline:0;position:relative}.CodeMirror-sizer{position:relative;border-right:30px solid transparent}.CodeMirror-vscrollbar,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-gutter-filler{position:absolute;z-index:6;display:none}.CodeMirror-vscrollbar{right:0;top:0;overflow-x:hidden;overflow-y:scroll}.CodeMirror-hscrollbar{bottom:0;left:0;overflow-y:hidden;overflow-x:scroll}.CodeMirror-scrollbar-filler{right:0;bottom:0}.CodeMirror-gutter-filler{left:0;bottom:0}.CodeMirror-gutters{position:absolute;left:0;top:0;min-height:100%;z-index:3}.CodeMirror-gutter{white-space:normal;height:100%;display:inline-block;vertical-align:top;margin-bottom:-30px}.CodeMirror-gutter-wrapper{position:absolute;z-index:4;background:none !important;border:none !important}.CodeMirror-gutter-background{position:absolute;top:0;bottom:0;z-index:4}.CodeMirror-gutter-elt{position:absolute;cursor:default;z-index:4}.CodeMirror-gutter-wrapper ::selection{background-color:transparent}.CodeMirror-gutter-wrapper ::-moz-selection{background-color:transparent}.CodeMirror-lines{cursor:text;min-height:1px}.CodeMirror pre.CodeMirror-line,.CodeMirror pre.CodeMirror-line-like{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;border-width:0;background:transparent;font-family:inherit;font-size:inherit;margin:0;white-space:pre;word-wrap:normal;line-height:inherit;color:inherit;z-index:2;position:relative;overflow:visible;-webkit-tap-highlight-color:transparent;-webkit-font-variant-ligatures:contextual;font-variant-ligatures:contextual}.CodeMirror-wrap pre.CodeMirror-line,.CodeMirror-wrap pre.CodeMirror-line-like{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.CodeMirror-linebackground{position:absolute;left:0;right:0;top:0;bottom:0;z-index:0}.CodeMirror-linewidget{position:relative;z-index:2;padding:.1px}.CodeMirror-rtl pre{direction:rtl}.CodeMirror-code{outline:0}.CodeMirror-scroll,.CodeMirror-sizer,.CodeMirror-gutter,.CodeMirror-gutters,.CodeMirror-linenumber{-moz-box-sizing:content-box;box-sizing:content-box}.CodeMirror-measure{position:absolute;width:100%;height:0;overflow:hidden;visibility:hidden}.CodeMirror-cursor{position:absolute;pointer-events:none}.CodeMirror-measure pre{position:static}div.CodeMirror-cursors{visibility:hidden;position:relative;z-index:3}div.CodeMirror-dragcursors{visibility:visible}.CodeMirror-focused div.CodeMirror-cursors{visibility:visible}.CodeMirror-selected{background:#d9d9d9}.CodeMirror-focused .CodeMirror-selected{background:#d7d4f0}.CodeMirror-crosshair{cursor:crosshair}.CodeMirror-line::selection,.CodeMirror-line>span::selection,.CodeMirror-line>span>span::selection{background:#d7d4f0}.CodeMirror-line::-moz-selection,.CodeMirror-line>span::-moz-selection,.CodeMirror-line>span>span::-moz-selection{background:#d7d4f0}.cm-searching{background-color:#ffa;background-color:rgba(255,255,0,.4)}.cm-force-border{padding-right:.1px}@media print{.CodeMirror div.CodeMirror-cursors{visibility:hidden}}.cm-tab-wrap-hack:after{content:''}span.CodeMirror-selectedtext{background:0} diff --git a/luci-app-adguardhome/root/www/luci-static/resources/codemirror/lib/codemirror.js b/luci-app-adguardhome/root/www/luci-static/resources/codemirror/lib/codemirror.js new file mode 100644 index 000000000..d01f072ee --- /dev/null +++ b/luci-app-adguardhome/root/www/luci-static/resources/codemirror/lib/codemirror.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.CodeMirror=t()}(this,function(){"use strict";var e=navigator.userAgent,t=navigator.platform,r=/gecko\/\d/i.test(e),n=/MSIE \d/.test(e),i=/Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(e),o=/Edge\/(\d+)/.exec(e),l=n||i||o,s=l&&(n?document.documentMode||6:+(o||i)[1]),a=!o&&/WebKit\//.test(e),u=a&&/Qt\/\d+\.\d+/.test(e),c=!o&&/Chrome\//.test(e),h=/Opera\//.test(e),f=/Apple Computer/.test(navigator.vendor),d=/Mac OS X 1\d\D([8-9]|\d\d)\D/.test(e),p=/PhantomJS/.test(e),g=!o&&/AppleWebKit/.test(e)&&/Mobile\/\w+/.test(e),v=/Android/.test(e),m=g||v||/webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(e),y=g||/Mac/.test(t),b=/\bCrOS\b/.test(e),w=/win/i.test(t),x=h&&e.match(/Version\/(\d*\.\d*)/);x&&(x=Number(x[1])),x&&x>=15&&(h=!1,a=!0);var C=y&&(u||h&&(null==x||x<12.11)),S=r||l&&s>=9;function L(e){return new RegExp("(^|\\s)"+e+"(?:$|\\s)\\s*")}var k,T=function(e,t){var r=e.className,n=L(t).exec(r);if(n){var i=r.slice(n.index+n[0].length);e.className=r.slice(0,n.index)+(i?n[1]+i:"")}};function M(e){for(var t=e.childNodes.length;t>0;--t)e.removeChild(e.firstChild);return e}function N(e,t){return M(e).appendChild(t)}function O(e,t,r,n){var i=document.createElement(e);if(r&&(i.className=r),n&&(i.style.cssText=n),"string"==typeof t)i.appendChild(document.createTextNode(t));else if(t)for(var o=0;o=t)return l+(t-o);l+=s-o,l+=r-l%r,o=s+1}}g?P=function(e){e.selectionStart=0,e.selectionEnd=e.value.length}:l&&(P=function(e){try{e.select()}catch(e){}});var R=function(){this.id=null,this.f=null,this.time=0,this.handler=E(this.onTimeout,this)};function B(e,t){for(var r=0;r=t)return n+Math.min(l,t-i);if(i+=o-n,n=o+1,(i+=r-i%r)>=t)return n}}var Y=[""];function _(e){for(;Y.length<=e;)Y.push($(Y)+" ");return Y[e]}function $(e){return e[e.length-1]}function q(e,t){for(var r=[],n=0;n"€"&&(e.toUpperCase()!=e.toLowerCase()||J.test(e))}function te(e,t){return t?!!(t.source.indexOf("\\w")>-1&&ee(e))||t.test(e):ee(e)}function re(e){for(var t in e)if(e.hasOwnProperty(t)&&e[t])return!1;return!0}var ne=/[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/;function ie(e){return e.charCodeAt(0)>=768&&ne.test(e)}function oe(e,t,r){for(;(r<0?t>0:tr?-1:1;;){if(t==r)return t;var i=(t+r)/2,o=n<0?Math.ceil(i):Math.floor(i);if(o==t)return e(o)?t:r;e(o)?r=o:t=o+n}}var se=null;function ae(e,t,r){var n;se=null;for(var i=0;it)return i;o.to==t&&(o.from!=o.to&&"before"==r?n=i:se=i),o.from==t&&(o.from!=o.to&&"before"!=r?n=i:se=i)}return null!=n?n:se}var ue=function(){var e="bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN",t="nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111";var r=/[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/,n=/[stwN]/,i=/[LRr]/,o=/[Lb1n]/,l=/[1n]/;function s(e,t,r){this.level=e,this.from=t,this.to=r}return function(a,u){var c="ltr"==u?"L":"R";if(0==a.length||"ltr"==u&&!r.test(a))return!1;for(var h,f=a.length,d=[],p=0;p-1&&(n[t]=i.slice(0,o).concat(i.slice(o+1)))}}}function ge(e,t){var r=de(e,t);if(r.length)for(var n=Array.prototype.slice.call(arguments,2),i=0;i0}function be(e){e.prototype.on=function(e,t){fe(this,e,t)},e.prototype.off=function(e,t){pe(this,e,t)}}function we(e){e.preventDefault?e.preventDefault():e.returnValue=!1}function xe(e){e.stopPropagation?e.stopPropagation():e.cancelBubble=!0}function Ce(e){return null!=e.defaultPrevented?e.defaultPrevented:0==e.returnValue}function Se(e){we(e),xe(e)}function Le(e){return e.target||e.srcElement}function ke(e){var t=e.which;return null==t&&(1&e.button?t=1:2&e.button?t=3:4&e.button&&(t=2)),y&&e.ctrlKey&&1==t&&(t=3),t}var Te,Me,Ne=function(){if(l&&s<9)return!1;var e=O("div");return"draggable"in e||"dragDrop"in e}();function Oe(e){if(null==Te){var t=O("span","​");N(e,O("span",[t,document.createTextNode("x")])),0!=e.firstChild.offsetHeight&&(Te=t.offsetWidth<=1&&t.offsetHeight>2&&!(l&&s<8))}var r=Te?O("span","​"):O("span"," ",null,"display: inline-block; width: 1px; margin-right: -1px");return r.setAttribute("cm-text",""),r}function Ae(e){if(null!=Me)return Me;var t=N(e,document.createTextNode("AخA")),r=k(t,0,1).getBoundingClientRect(),n=k(t,1,2).getBoundingClientRect();return M(e),!(!r||r.left==r.right)&&(Me=n.right-r.right<3)}var De,We=3!="\n\nb".split(/\n/).length?function(e){for(var t=0,r=[],n=e.length;t<=n;){var i=e.indexOf("\n",t);-1==i&&(i=e.length);var o=e.slice(t,"\r"==e.charAt(i-1)?i-1:i),l=o.indexOf("\r");-1!=l?(r.push(o.slice(0,l)),t+=l+1):(r.push(o),t=i+1)}return r}:function(e){return e.split(/\r\n?|\n/)},He=window.getSelection?function(e){try{return e.selectionStart!=e.selectionEnd}catch(e){return!1}}:function(e){var t;try{t=e.ownerDocument.selection.createRange()}catch(e){}return!(!t||t.parentElement()!=e)&&0!=t.compareEndPoints("StartToEnd",t)},Fe="oncopy"in(De=O("div"))||(De.setAttribute("oncopy","return;"),"function"==typeof De.oncopy),Pe=null;var Ee={},Ie={};function ze(e){if("string"==typeof e&&Ie.hasOwnProperty(e))e=Ie[e];else if(e&&"string"==typeof e.name&&Ie.hasOwnProperty(e.name)){var t=Ie[e.name];"string"==typeof t&&(t={name:t}),(e=Q(t,e)).name=t.name}else{if("string"==typeof e&&/^[\w\-]+\/[\w\-]+\+xml$/.test(e))return ze("application/xml");if("string"==typeof e&&/^[\w\-]+\/[\w\-]+\+json$/.test(e))return ze("application/json")}return"string"==typeof e?{name:e}:e||{name:"null"}}function Re(e,t){t=ze(t);var r=Ee[t.name];if(!r)return Re(e,"text/plain");var n=r(e,t);if(Be.hasOwnProperty(t.name)){var i=Be[t.name];for(var o in i)i.hasOwnProperty(o)&&(n.hasOwnProperty(o)&&(n["_"+o]=n[o]),n[o]=i[o])}if(n.name=t.name,t.helperType&&(n.helperType=t.helperType),t.modeProps)for(var l in t.modeProps)n[l]=t.modeProps[l];return n}var Be={};function Ge(e,t){I(t,Be.hasOwnProperty(e)?Be[e]:Be[e]={})}function Ue(e,t){if(!0===t)return t;if(e.copyState)return e.copyState(t);var r={};for(var n in t){var i=t[n];i instanceof Array&&(i=i.concat([])),r[n]=i}return r}function Ve(e,t){for(var r;e.innerMode&&(r=e.innerMode(t))&&r.mode!=e;)t=r.state,e=r.mode;return r||{mode:e,state:t}}function Ke(e,t,r){return!e.startState||e.startState(t,r)}var je=function(e,t,r){this.pos=this.start=0,this.string=e,this.tabSize=t||8,this.lastColumnPos=this.lastColumnValue=0,this.lineStart=0,this.lineOracle=r};function Xe(e,t){if((t-=e.first)<0||t>=e.size)throw new Error("There is no line "+(t+e.first)+" in the document.");for(var r=e;!r.lines;)for(var n=0;;++n){var i=r.children[n],o=i.chunkSize();if(t=e.first&&tr?et(r,Xe(e,r).text.length):function(e,t){var r=e.ch;return null==r||r>t?et(e.line,t):r<0?et(e.line,0):e}(t,Xe(e,t.line).text.length)}function at(e,t){for(var r=[],n=0;n=this.string.length},je.prototype.sol=function(){return this.pos==this.lineStart},je.prototype.peek=function(){return this.string.charAt(this.pos)||void 0},je.prototype.next=function(){if(this.post},je.prototype.eatSpace=function(){for(var e=this.pos;/[\s\u00a0]/.test(this.string.charAt(this.pos));)++this.pos;return this.pos>e},je.prototype.skipToEnd=function(){this.pos=this.string.length},je.prototype.skipTo=function(e){var t=this.string.indexOf(e,this.pos);if(t>-1)return this.pos=t,!0},je.prototype.backUp=function(e){this.pos-=e},je.prototype.column=function(){return this.lastColumnPos0?null:(n&&!1!==t&&(this.pos+=n[0].length),n)}var i=function(e){return r?e.toLowerCase():e};if(i(this.string.substr(this.pos,e.length))==i(e))return!1!==t&&(this.pos+=e.length),!0},je.prototype.current=function(){return this.string.slice(this.start,this.pos)},je.prototype.hideFirstChars=function(e,t){this.lineStart+=e;try{return t()}finally{this.lineStart-=e}},je.prototype.lookAhead=function(e){var t=this.lineOracle;return t&&t.lookAhead(e)},je.prototype.baseToken=function(){var e=this.lineOracle;return e&&e.baseToken(this.pos)};var ut=function(e,t){this.state=e,this.lookAhead=t},ct=function(e,t,r,n){this.state=t,this.doc=e,this.line=r,this.maxLookAhead=n||0,this.baseTokens=null,this.baseTokenPos=1};function ht(e,t,r,n){var i=[e.state.modeGen],o={};wt(e,t.text,e.doc.mode,r,function(e,t){return i.push(e,t)},o,n);for(var l=r.state,s=function(n){r.baseTokens=i;var s=e.state.overlays[n],a=1,u=0;r.state=!0,wt(e,t.text,s.mode,r,function(e,t){for(var r=a;ue&&i.splice(a,1,e,i[a+1],n),a+=2,u=Math.min(e,n)}if(t)if(s.opaque)i.splice(r,a-r,e,"overlay "+t),a=r+2;else for(;re.options.maxHighlightLength&&Ue(e.doc.mode,n.state),o=ht(e,t,n);i&&(n.state=i),t.stateAfter=n.save(!i),t.styles=o.styles,o.classes?t.styleClasses=o.classes:t.styleClasses&&(t.styleClasses=null),r===e.doc.highlightFrontier&&(e.doc.modeFrontier=Math.max(e.doc.modeFrontier,++e.doc.highlightFrontier))}return t.styles}function dt(e,t,r){var n=e.doc,i=e.display;if(!n.mode.startState)return new ct(n,!0,t);var o=function(e,t,r){for(var n,i,o=e.doc,l=r?-1:t-(e.doc.mode.innerMode?1e3:100),s=t;s>l;--s){if(s<=o.first)return o.first;var a=Xe(o,s-1),u=a.stateAfter;if(u&&(!r||s+(u instanceof ut?u.lookAhead:0)<=o.modeFrontier))return s;var c=z(a.text,null,e.options.tabSize);(null==i||n>c)&&(i=s-1,n=c)}return i}(e,t,r),l=o>n.first&&Xe(n,o-1).stateAfter,s=l?ct.fromSaved(n,l,o):new ct(n,Ke(n.mode),o);return n.iter(o,t,function(r){pt(e,r.text,s);var n=s.line;r.stateAfter=n==t-1||n%5==0||n>=i.viewFrom&&nt.start)return o}throw new Error("Mode "+e.name+" failed to advance stream.")}ct.prototype.lookAhead=function(e){var t=this.doc.getLine(this.line+e);return null!=t&&e>this.maxLookAhead&&(this.maxLookAhead=e),t},ct.prototype.baseToken=function(e){if(!this.baseTokens)return null;for(;this.baseTokens[this.baseTokenPos]<=e;)this.baseTokenPos+=2;var t=this.baseTokens[this.baseTokenPos+1];return{type:t&&t.replace(/( |^)overlay .*/,""),size:this.baseTokens[this.baseTokenPos]-e}},ct.prototype.nextLine=function(){this.line++,this.maxLookAhead>0&&this.maxLookAhead--},ct.fromSaved=function(e,t,r){return t instanceof ut?new ct(e,Ue(e.mode,t.state),r,t.lookAhead):new ct(e,Ue(e.mode,t),r)},ct.prototype.save=function(e){var t=!1!==e?Ue(this.doc.mode,this.state):this.state;return this.maxLookAhead>0?new ut(t,this.maxLookAhead):t};var mt=function(e,t,r){this.start=e.start,this.end=e.pos,this.string=e.current(),this.type=t||null,this.state=r};function yt(e,t,r,n){var i,o,l=e.doc,s=l.mode,a=Xe(l,(t=st(l,t)).line),u=dt(e,t.line,r),c=new je(a.text,e.options.tabSize,u);for(n&&(o=[]);(n||c.pose.options.maxHighlightLength?(s=!1,l&&pt(e,t,n,h.pos),h.pos=t.length,a=null):a=bt(vt(r,h,n.state,f),o),f){var d=f[0].name;d&&(a="m-"+(a?d+" "+a:d))}if(!s||c!=a){for(;u=t:o.to>t);(n||(n=[])).push(new St(l,o.from,s?null:o.to))}}return n}(r,i,l),a=function(e,t,r){var n;if(e)for(var i=0;i=t:o.to>t)||o.from==t&&"bookmark"==l.type&&(!r||o.marker.insertLeft)){var s=null==o.from||(l.inclusiveLeft?o.from<=t:o.from0&&s)for(var b=0;bt)&&(!r||Wt(r,o.marker)<0)&&(r=o.marker)}return r}function It(e,t,r,n,i){var o=Xe(e,t),l=Ct&&o.markedSpans;if(l)for(var s=0;s=0&&h<=0||c<=0&&h>=0)&&(c<=0&&(a.marker.inclusiveRight&&i.inclusiveLeft?tt(u.to,r)>=0:tt(u.to,r)>0)||c>=0&&(a.marker.inclusiveRight&&i.inclusiveLeft?tt(u.from,n)<=0:tt(u.from,n)<0)))return!0}}}function zt(e){for(var t;t=Ft(e);)e=t.find(-1,!0).line;return e}function Rt(e,t){var r=Xe(e,t),n=zt(r);return r==n?t:qe(n)}function Bt(e,t){if(t>e.lastLine())return t;var r,n=Xe(e,t);if(!Gt(e,n))return t;for(;r=Pt(n);)n=r.find(1,!0).line;return qe(n)+1}function Gt(e,t){var r=Ct&&t.markedSpans;if(r)for(var n=void 0,i=0;it.maxLineLength&&(t.maxLineLength=r,t.maxLine=e)})}var Xt=function(e,t,r){this.text=e,Ot(this,t),this.height=r?r(this):1};function Yt(e){e.parent=null,Nt(e)}Xt.prototype.lineNo=function(){return qe(this)},be(Xt);var _t={},$t={};function qt(e,t){if(!e||/^\s*$/.test(e))return null;var r=t.addModeClass?$t:_t;return r[e]||(r[e]=e.replace(/\S+/g,"cm-$&"))}function Zt(e,t){var r=A("span",null,null,a?"padding-right: .1px":null),n={pre:A("pre",[r],"CodeMirror-line"),content:r,col:0,pos:0,cm:e,trailingSpace:!1,splitSpaces:e.getOption("lineWrapping")};t.measure={};for(var i=0;i<=(t.rest?t.rest.length:0);i++){var o=i?t.rest[i-1]:t.line,l=void 0;n.pos=0,n.addToken=Jt,Ae(e.display.measure)&&(l=ce(o,e.doc.direction))&&(n.addToken=er(n.addToken,l)),n.map=[],rr(o,n,ft(e,o,t!=e.display.externalMeasured&&qe(o))),o.styleClasses&&(o.styleClasses.bgClass&&(n.bgClass=F(o.styleClasses.bgClass,n.bgClass||"")),o.styleClasses.textClass&&(n.textClass=F(o.styleClasses.textClass,n.textClass||""))),0==n.map.length&&n.map.push(0,0,n.content.appendChild(Oe(e.display.measure))),0==i?(t.measure.map=n.map,t.measure.cache={}):((t.measure.maps||(t.measure.maps=[])).push(n.map),(t.measure.caches||(t.measure.caches=[])).push({}))}if(a){var s=n.content.lastChild;(/\bcm-tab\b/.test(s.className)||s.querySelector&&s.querySelector(".cm-tab"))&&(n.content.className="cm-tab-wrap-hack")}return ge(e,"renderLine",e,t.line,n.pre),n.pre.className&&(n.textClass=F(n.pre.className,n.textClass||"")),n}function Qt(e){var t=O("span","•","cm-invalidchar");return t.title="\\u"+e.charCodeAt(0).toString(16),t.setAttribute("aria-label",t.title),t}function Jt(e,t,r,n,i,o,a){if(t){var u,c=e.splitSpaces?function(e,t){if(e.length>1&&!/ /.test(e))return e;for(var r=t,n="",i=0;iu&&h.from<=u);f++);if(h.to>=c)return e(r,n,i,o,l,s,a);e(r,n.slice(0,h.to-u),i,o,null,s,a),o=null,n=n.slice(h.to-u),u=h.to}}}function tr(e,t,r,n){var i=!n&&r.widgetNode;i&&e.map.push(e.pos,e.pos+t,i),!n&&e.cm.display.input.needsContentAttribute&&(i||(i=e.content.appendChild(document.createElement("span"))),i.setAttribute("cm-marker",r.id)),i&&(e.cm.display.input.setUneditable(i),e.content.appendChild(i)),e.pos+=t,e.trailingSpace=!1}function rr(e,t,r){var n=e.markedSpans,i=e.text,o=0;if(n)for(var l,s,a,u,c,h,f,d=i.length,p=0,g=1,v="",m=0;;){if(m==p){a=u=c=s="",f=null,h=null,m=1/0;for(var y=[],b=void 0,w=0;wp||C.collapsed&&x.to==p&&x.from==p)){if(null!=x.to&&x.to!=p&&m>x.to&&(m=x.to,u=""),C.className&&(a+=" "+C.className),C.css&&(s=(s?s+";":"")+C.css),C.startStyle&&x.from==p&&(c+=" "+C.startStyle),C.endStyle&&x.to==m&&(b||(b=[])).push(C.endStyle,x.to),C.title&&((f||(f={})).title=C.title),C.attributes)for(var S in C.attributes)(f||(f={}))[S]=C.attributes[S];C.collapsed&&(!h||Wt(h.marker,C)<0)&&(h=x)}else x.from>p&&m>x.from&&(m=x.from)}if(b)for(var L=0;L=d)break;for(var T=Math.min(d,m);;){if(v){var M=p+v.length;if(!h){var N=M>T?v.slice(0,T-p):v;t.addToken(t,N,l?l+a:a,c,p+N.length==m?u:"",s,f)}if(M>=T){v=v.slice(T-p),p=T;break}p=M,c=""}v=i.slice(o,o=r[g++]),l=qt(r[g++],t.cm.options)}}else for(var O=1;Or)return{map:e.measure.maps[i],cache:e.measure.caches[i],before:!0}}function Or(e,t,r,n){return Wr(e,Dr(e,t),r,n)}function Ar(e,t){if(t>=e.display.viewFrom&&t=r.lineN&&t2&&o.push((a.bottom+u.top)/2-r.top)}}o.push(r.bottom-r.top)}}(e,t.view,t.rect),t.hasHeights=!0),(o=function(e,t,r,n){var i,o=Pr(t.map,r,n),a=o.node,u=o.start,c=o.end,h=o.collapse;if(3==a.nodeType){for(var f=0;f<4;f++){for(;u&&ie(t.line.text.charAt(o.coverStart+u));)--u;for(;o.coverStart+c1}(e))return t;var r=screen.logicalXDPI/screen.deviceXDPI,n=screen.logicalYDPI/screen.deviceYDPI;return{left:t.left*r,right:t.right*r,top:t.top*n,bottom:t.bottom*n}}(e.display.measure,i))}else{var d;u>0&&(h=n="right"),i=e.options.lineWrapping&&(d=a.getClientRects()).length>1?d["right"==n?d.length-1:0]:a.getBoundingClientRect()}if(l&&s<9&&!u&&(!i||!i.left&&!i.right)){var p=a.parentNode.getClientRects()[0];i=p?{left:p.left,right:p.left+tn(e.display),top:p.top,bottom:p.bottom}:Fr}for(var g=i.top-t.rect.top,v=i.bottom-t.rect.top,m=(g+v)/2,y=t.view.measure.heights,b=0;bt)&&(i=(o=a-s)-1,t>=a&&(l="right")),null!=i){if(n=e[u+2],s==a&&r==(n.insertLeft?"left":"right")&&(l=r),"left"==r&&0==i)for(;u&&e[u-2]==e[u-3]&&e[u-1].insertLeft;)n=e[2+(u-=3)],l="left";if("right"==r&&i==a-s)for(;u=0&&(r=e[i]).left==r.right;i--);return r}function Ir(e){if(e.measure&&(e.measure.cache={},e.measure.heights=null,e.rest))for(var t=0;t=n.text.length?(a=n.text.length,u="before"):a<=0&&(a=0,u="after"),!s)return l("before"==u?a-1:a,"before"==u);function c(e,t,r){return l(r?e-1:e,1==s[t].level!=r)}var h=ae(s,a,u),f=se,d=c(a,h,"before"==u);return null!=f&&(d.other=c(a,f,"before"!=u)),d}function Yr(e,t){var r=0;t=st(e.doc,t),e.options.lineWrapping||(r=tn(e.display)*t.ch);var n=Xe(e.doc,t.line),i=Vt(n)+Cr(e.display);return{left:r,right:r,top:i,bottom:i+n.height}}function _r(e,t,r,n,i){var o=et(e,t,r);return o.xRel=i,n&&(o.outside=n),o}function $r(e,t,r){var n=e.doc;if((r+=e.display.viewOffset)<0)return _r(n.first,0,null,-1,-1);var i=Ze(n,r),o=n.first+n.size-1;if(i>o)return _r(n.first+n.size-1,Xe(n,o).text.length,null,1,1);t<0&&(t=0);for(var l=Xe(n,i);;){var s=Jr(e,l,i,t,r),a=Et(l,s.ch+(s.xRel>0||s.outside>0?1:0));if(!a)return s;var u=a.find(1);if(u.line==i)return u;l=Xe(n,i=u.line)}}function qr(e,t,r,n){n-=Ur(t);var i=t.text.length,o=le(function(t){return Wr(e,r,t-1).bottom<=n},i,0);return{begin:o,end:i=le(function(t){return Wr(e,r,t).top>n},o,i)}}function Zr(e,t,r,n){return r||(r=Dr(e,t)),qr(e,t,r,Vr(e,t,Wr(e,r,n),"line").top)}function Qr(e,t,r,n){return!(e.bottom<=r)&&(e.top>r||(n?e.left:e.right)>t)}function Jr(e,t,r,n,i){i-=Vt(t);var o=Dr(e,t),l=Ur(t),s=0,a=t.text.length,u=!0,c=ce(t,e.doc.direction);if(c){var h=(e.options.lineWrapping?function(e,t,r,n,i,o,l){var s=qr(e,t,n,l),a=s.begin,u=s.end;/\s/.test(t.text.charAt(u-1))&&u--;for(var c=null,h=null,f=0;f=u||d.to<=a)){var p=1!=d.level,g=Wr(e,n,p?Math.min(u,d.to)-1:Math.max(a,d.from)).right,v=gv)&&(c=d,h=v)}}c||(c=i[i.length-1]);c.fromu&&(c={from:c.from,to:u,level:c.level});return c}:function(e,t,r,n,i,o,l){var s=le(function(s){var a=i[s],u=1!=a.level;return Qr(Xr(e,et(r,u?a.to:a.from,u?"before":"after"),"line",t,n),o,l,!0)},0,i.length-1),a=i[s];if(s>0){var u=1!=a.level,c=Xr(e,et(r,u?a.from:a.to,u?"after":"before"),"line",t,n);Qr(c,o,l,!0)&&c.top>l&&(a=i[s-1])}return a})(e,t,r,o,c,n,i);s=(u=1!=h.level)?h.from:h.to-1,a=u?h.to:h.from-1}var f,d,p=null,g=null,v=le(function(t){var r=Wr(e,o,t);return r.top+=l,r.bottom+=l,!!Qr(r,n,i,!1)&&(r.top<=i&&r.left<=n&&(p=t,g=r),!0)},s,a),m=!1;if(g){var y=n-g.left=w.bottom?1:0}return _r(r,v=oe(t.text,v,1),d,m,n-f)}function en(e){if(null!=e.cachedTextHeight)return e.cachedTextHeight;if(null==Hr){Hr=O("pre",null,"CodeMirror-line-like");for(var t=0;t<49;++t)Hr.appendChild(document.createTextNode("x")),Hr.appendChild(O("br"));Hr.appendChild(document.createTextNode("x"))}N(e.measure,Hr);var r=Hr.offsetHeight/50;return r>3&&(e.cachedTextHeight=r),M(e.measure),r||1}function tn(e){if(null!=e.cachedCharWidth)return e.cachedCharWidth;var t=O("span","xxxxxxxxxx"),r=O("pre",[t],"CodeMirror-line-like");N(e.measure,r);var n=t.getBoundingClientRect(),i=(n.right-n.left)/10;return i>2&&(e.cachedCharWidth=i),i||10}function rn(e){for(var t=e.display,r={},n={},i=t.gutters.clientLeft,o=t.gutters.firstChild,l=0;o;o=o.nextSibling,++l){var s=e.display.gutterSpecs[l].className;r[s]=o.offsetLeft+o.clientLeft+i,n[s]=o.clientWidth}return{fixedPos:nn(t),gutterTotalWidth:t.gutters.offsetWidth,gutterLeft:r,gutterWidth:n,wrapperWidth:t.wrapper.clientWidth}}function nn(e){return e.scroller.getBoundingClientRect().left-e.sizer.getBoundingClientRect().left}function on(e){var t=en(e.display),r=e.options.lineWrapping,n=r&&Math.max(5,e.display.scroller.clientWidth/tn(e.display)-3);return function(i){if(Gt(e.doc,i))return 0;var o=0;if(i.widgets)for(var l=0;l=e.display.viewTo)return null;if((t-=e.display.viewFrom)<0)return null;for(var r=e.display.view,n=0;nt)&&(i.updateLineNumbers=t),e.curOp.viewChanged=!0,t>=i.viewTo)Ct&&Rt(e.doc,t)i.viewFrom?hn(e):(i.viewFrom+=n,i.viewTo+=n);else if(t<=i.viewFrom&&r>=i.viewTo)hn(e);else if(t<=i.viewFrom){var o=fn(e,r,r+n,1);o?(i.view=i.view.slice(o.index),i.viewFrom=o.lineN,i.viewTo+=n):hn(e)}else if(r>=i.viewTo){var l=fn(e,t,t,-1);l?(i.view=i.view.slice(0,l.index),i.viewTo=l.lineN):hn(e)}else{var s=fn(e,t,t,-1),a=fn(e,r,r+n,1);s&&a?(i.view=i.view.slice(0,s.index).concat(ir(e,s.lineN,a.lineN)).concat(i.view.slice(a.index)),i.viewTo+=n):hn(e)}var u=i.externalMeasured;u&&(r=i.lineN&&t=n.viewTo)){var o=n.view[an(e,t)];if(null!=o.node){var l=o.changes||(o.changes=[]);-1==B(l,r)&&l.push(r)}}}function hn(e){e.display.viewFrom=e.display.viewTo=e.doc.first,e.display.view=[],e.display.viewOffset=0}function fn(e,t,r,n){var i,o=an(e,t),l=e.display.view;if(!Ct||r==e.doc.first+e.doc.size)return{index:o,lineN:r};for(var s=e.display.viewFrom,a=0;a0){if(o==l.length-1)return null;i=s+l[o].size-t,o++}else i=s-t;t+=i,r+=i}for(;Rt(e.doc,r)!=r;){if(o==(n<0?0:l.length-1))return null;r+=n*l[o-(n<0?1:0)].size,o+=n}return{index:o,lineN:r}}function dn(e){for(var t=e.display.view,r=0,n=0;n=e.display.viewTo||s.to().linet||t==r&&l.to==t)&&(n(Math.max(l.from,t),Math.min(l.to,r),1==l.level?"rtl":"ltr",o),i=!0)}i||n(t,r,"ltr")}(g,r||0,null==n?f:n,function(e,t,i,h){var v="ltr"==i,m=d(e,v?"left":"right"),y=d(t-1,v?"right":"left"),b=null==r&&0==e,w=null==n&&t==f,x=0==h,C=!g||h==g.length-1;if(y.top-m.top<=3){var S=(u?w:b)&&C,L=(u?b:w)&&x?s:(v?m:y).left,k=S?a:(v?y:m).right;c(L,m.top,k-L,m.bottom)}else{var T,M,N,O;v?(T=u&&b&&x?s:m.left,M=u?a:p(e,i,"before"),N=u?s:p(t,i,"after"),O=u&&w&&C?a:y.right):(T=u?p(e,i,"before"):s,M=!u&&b&&x?a:m.right,N=!u&&w&&C?s:y.left,O=u?p(t,i,"after"):a),c(T,m.top,M-T,m.bottom),m.bottom0?t.blinker=setInterval(function(){return t.cursorDiv.style.visibility=(r=!r)?"":"hidden"},e.options.cursorBlinkRate):e.options.cursorBlinkRate<0&&(t.cursorDiv.style.visibility="hidden")}}function wn(e){e.state.focused||(e.display.input.focus(),Cn(e))}function xn(e){e.state.delayingBlurEvent=!0,setTimeout(function(){e.state.delayingBlurEvent&&(e.state.delayingBlurEvent=!1,Sn(e))},100)}function Cn(e,t){e.state.delayingBlurEvent&&(e.state.delayingBlurEvent=!1),"nocursor"!=e.options.readOnly&&(e.state.focused||(ge(e,"focus",e,t),e.state.focused=!0,H(e.display.wrapper,"CodeMirror-focused"),e.curOp||e.display.selForContextMenu==e.doc.sel||(e.display.input.reset(),a&&setTimeout(function(){return e.display.input.reset(!0)},20)),e.display.input.receivedFocus()),bn(e))}function Sn(e,t){e.state.delayingBlurEvent||(e.state.focused&&(ge(e,"blur",e,t),e.state.focused=!1,T(e.display.wrapper,"CodeMirror-focused")),clearInterval(e.display.blinker),setTimeout(function(){e.state.focused||(e.display.shift=!1)},150))}function Ln(e){for(var t=e.display,r=t.lineDiv.offsetTop,n=0;n.005||f<-.005)&&($e(i.line,a),kn(i.line),i.rest))for(var d=0;de.display.sizerWidth){var p=Math.ceil(u/tn(e.display));p>e.display.maxLineLength&&(e.display.maxLineLength=p,e.display.maxLine=i.line,e.display.maxLineChanged=!0)}}}}function kn(e){if(e.widgets)for(var t=0;t=l&&(o=Ze(t,Vt(Xe(t,a))-e.wrapper.clientHeight),l=a)}return{from:o,to:Math.max(l,o+1)}}function Mn(e,t){var r=e.display,n=en(e.display);t.top<0&&(t.top=0);var i=e.curOp&&null!=e.curOp.scrollTop?e.curOp.scrollTop:r.scroller.scrollTop,o=Mr(e),l={};t.bottom-t.top>o&&(t.bottom=t.top+o);var s=e.doc.height+Sr(r),a=t.tops-n;if(t.topi+o){var c=Math.min(t.top,(u?s:t.bottom)-o);c!=i&&(l.scrollTop=c)}var h=e.curOp&&null!=e.curOp.scrollLeft?e.curOp.scrollLeft:r.scroller.scrollLeft,f=Tr(e)-(e.options.fixedGutter?r.gutters.offsetWidth:0),d=t.right-t.left>f;return d&&(t.right=t.left+f),t.left<10?l.scrollLeft=0:t.leftf+h-3&&(l.scrollLeft=t.right+(d?0:10)-f),l}function Nn(e,t){null!=t&&(Dn(e),e.curOp.scrollTop=(null==e.curOp.scrollTop?e.doc.scrollTop:e.curOp.scrollTop)+t)}function On(e){Dn(e);var t=e.getCursor();e.curOp.scrollToPos={from:t,to:t,margin:e.options.cursorScrollMargin}}function An(e,t,r){null==t&&null==r||Dn(e),null!=t&&(e.curOp.scrollLeft=t),null!=r&&(e.curOp.scrollTop=r)}function Dn(e){var t=e.curOp.scrollToPos;t&&(e.curOp.scrollToPos=null,Wn(e,Yr(e,t.from),Yr(e,t.to),t.margin))}function Wn(e,t,r,n){var i=Mn(e,{left:Math.min(t.left,r.left),top:Math.min(t.top,r.top)-n,right:Math.max(t.right,r.right),bottom:Math.max(t.bottom,r.bottom)+n});An(e,i.scrollLeft,i.scrollTop)}function Hn(e,t){Math.abs(e.doc.scrollTop-t)<2||(r||oi(e,{top:t}),Fn(e,t,!0),r&&oi(e),ei(e,100))}function Fn(e,t,r){t=Math.min(e.display.scroller.scrollHeight-e.display.scroller.clientHeight,t),(e.display.scroller.scrollTop!=t||r)&&(e.doc.scrollTop=t,e.display.scrollbars.setScrollTop(t),e.display.scroller.scrollTop!=t&&(e.display.scroller.scrollTop=t))}function Pn(e,t,r,n){t=Math.min(t,e.display.scroller.scrollWidth-e.display.scroller.clientWidth),(r?t==e.doc.scrollLeft:Math.abs(e.doc.scrollLeft-t)<2)&&!n||(e.doc.scrollLeft=t,ai(e),e.display.scroller.scrollLeft!=t&&(e.display.scroller.scrollLeft=t),e.display.scrollbars.setScrollLeft(t))}function En(e){var t=e.display,r=t.gutters.offsetWidth,n=Math.round(e.doc.height+Sr(e.display));return{clientHeight:t.scroller.clientHeight,viewHeight:t.wrapper.clientHeight,scrollWidth:t.scroller.scrollWidth,clientWidth:t.scroller.clientWidth,viewWidth:t.wrapper.clientWidth,barLeft:e.options.fixedGutter?r:0,docHeight:n,scrollHeight:n+kr(e)+t.barHeight,nativeBarWidth:t.nativeBarWidth,gutterWidth:r}}var In=function(e,t,r){this.cm=r;var n=this.vert=O("div",[O("div",null,null,"min-width: 1px")],"CodeMirror-vscrollbar"),i=this.horiz=O("div",[O("div",null,null,"height: 100%; min-height: 1px")],"CodeMirror-hscrollbar");n.tabIndex=i.tabIndex=-1,e(n),e(i),fe(n,"scroll",function(){n.clientHeight&&t(n.scrollTop,"vertical")}),fe(i,"scroll",function(){i.clientWidth&&t(i.scrollLeft,"horizontal")}),this.checkedZeroWidth=!1,l&&s<8&&(this.horiz.style.minHeight=this.vert.style.minWidth="18px")};In.prototype.update=function(e){var t=e.scrollWidth>e.clientWidth+1,r=e.scrollHeight>e.clientHeight+1,n=e.nativeBarWidth;if(r){this.vert.style.display="block",this.vert.style.bottom=t?n+"px":"0";var i=e.viewHeight-(t?n:0);this.vert.firstChild.style.height=Math.max(0,e.scrollHeight-e.clientHeight+i)+"px"}else this.vert.style.display="",this.vert.firstChild.style.height="0";if(t){this.horiz.style.display="block",this.horiz.style.right=r?n+"px":"0",this.horiz.style.left=e.barLeft+"px";var o=e.viewWidth-e.barLeft-(r?n:0);this.horiz.firstChild.style.width=Math.max(0,e.scrollWidth-e.clientWidth+o)+"px"}else this.horiz.style.display="",this.horiz.firstChild.style.width="0";return!this.checkedZeroWidth&&e.clientHeight>0&&(0==n&&this.zeroWidthHack(),this.checkedZeroWidth=!0),{right:r?n:0,bottom:t?n:0}},In.prototype.setScrollLeft=function(e){this.horiz.scrollLeft!=e&&(this.horiz.scrollLeft=e),this.disableHoriz&&this.enableZeroWidthBar(this.horiz,this.disableHoriz,"horiz")},In.prototype.setScrollTop=function(e){this.vert.scrollTop!=e&&(this.vert.scrollTop=e),this.disableVert&&this.enableZeroWidthBar(this.vert,this.disableVert,"vert")},In.prototype.zeroWidthHack=function(){var e=y&&!d?"12px":"18px";this.horiz.style.height=this.vert.style.width=e,this.horiz.style.pointerEvents=this.vert.style.pointerEvents="none",this.disableHoriz=new R,this.disableVert=new R},In.prototype.enableZeroWidthBar=function(e,t,r){e.style.pointerEvents="auto",t.set(1e3,function n(){var i=e.getBoundingClientRect();("vert"==r?document.elementFromPoint(i.right-1,(i.top+i.bottom)/2):document.elementFromPoint((i.right+i.left)/2,i.bottom-1))!=e?e.style.pointerEvents="none":t.set(1e3,n)})},In.prototype.clear=function(){var e=this.horiz.parentNode;e.removeChild(this.horiz),e.removeChild(this.vert)};var zn=function(){};function Rn(e,t){t||(t=En(e));var r=e.display.barWidth,n=e.display.barHeight;Bn(e,t);for(var i=0;i<4&&r!=e.display.barWidth||n!=e.display.barHeight;i++)r!=e.display.barWidth&&e.options.lineWrapping&&Ln(e),Bn(e,En(e)),r=e.display.barWidth,n=e.display.barHeight}function Bn(e,t){var r=e.display,n=r.scrollbars.update(t);r.sizer.style.paddingRight=(r.barWidth=n.right)+"px",r.sizer.style.paddingBottom=(r.barHeight=n.bottom)+"px",r.heightForcer.style.borderBottom=n.bottom+"px solid transparent",n.right&&n.bottom?(r.scrollbarFiller.style.display="block",r.scrollbarFiller.style.height=n.bottom+"px",r.scrollbarFiller.style.width=n.right+"px"):r.scrollbarFiller.style.display="",n.bottom&&e.options.coverGutterNextToScrollbar&&e.options.fixedGutter?(r.gutterFiller.style.display="block",r.gutterFiller.style.height=n.bottom+"px",r.gutterFiller.style.width=t.gutterWidth+"px"):r.gutterFiller.style.display=""}zn.prototype.update=function(){return{bottom:0,right:0}},zn.prototype.setScrollLeft=function(){},zn.prototype.setScrollTop=function(){},zn.prototype.clear=function(){};var Gn={native:In,null:zn};function Un(e){e.display.scrollbars&&(e.display.scrollbars.clear(),e.display.scrollbars.addClass&&T(e.display.wrapper,e.display.scrollbars.addClass)),e.display.scrollbars=new Gn[e.options.scrollbarStyle](function(t){e.display.wrapper.insertBefore(t,e.display.scrollbarFiller),fe(t,"mousedown",function(){e.state.focused&&setTimeout(function(){return e.display.input.focus()},0)}),t.setAttribute("cm-not-content","true")},function(t,r){"horizontal"==r?Pn(e,t):Hn(e,t)},e),e.display.scrollbars.addClass&&H(e.display.wrapper,e.display.scrollbars.addClass)}var Vn=0;function Kn(e){var t;e.curOp={cm:e,viewChanged:!1,startHeight:e.doc.height,forceUpdate:!1,updateInput:0,typing:!1,changeObjs:null,cursorActivityHandlers:null,cursorActivityCalled:0,selectionChanged:!1,updateMaxLine:!1,scrollLeft:null,scrollTop:null,scrollToPos:null,focus:!1,id:++Vn},t=e.curOp,or?or.ops.push(t):t.ownsGroup=or={ops:[t],delayedCallbacks:[]}}function jn(e){var t=e.curOp;t&&function(e,t){var r=e.ownsGroup;if(r)try{!function(e){var t=e.delayedCallbacks,r=0;do{for(;r=r.viewTo)||r.maxLineChanged&&t.options.lineWrapping,e.update=e.mustUpdate&&new ri(t,e.mustUpdate&&{top:e.scrollTop,ensure:e.scrollToPos},e.forceUpdate)}function Yn(e){var t=e.cm,r=t.display;e.updatedDisplay&&Ln(t),e.barMeasure=En(t),r.maxLineChanged&&!t.options.lineWrapping&&(e.adjustWidthTo=Or(t,r.maxLine,r.maxLine.text.length).left+3,t.display.sizerWidth=e.adjustWidthTo,e.barMeasure.scrollWidth=Math.max(r.scroller.clientWidth,r.sizer.offsetLeft+e.adjustWidthTo+kr(t)+t.display.barWidth),e.maxScrollLeft=Math.max(0,r.sizer.offsetLeft+e.adjustWidthTo-Tr(t))),(e.updatedDisplay||e.selectionChanged)&&(e.preparedSelection=r.input.prepareSelection())}function _n(e){var t=e.cm;null!=e.adjustWidthTo&&(t.display.sizer.style.minWidth=e.adjustWidthTo+"px",e.maxScrollLeft(window.innerHeight||document.documentElement.clientHeight)&&(i=!1),null!=i&&!p){var o=O("div","​",null,"position: absolute;\n top: "+(t.top-r.viewOffset-Cr(e.display))+"px;\n height: "+(t.bottom-t.top+kr(e)+r.barHeight)+"px;\n left: "+t.left+"px; width: "+Math.max(2,t.right-t.left)+"px;");e.display.lineSpace.appendChild(o),o.scrollIntoView(i),e.display.lineSpace.removeChild(o)}}}(t,function(e,t,r,n){var i;null==n&&(n=0),e.options.lineWrapping||t!=r||(r="before"==(t=t.ch?et(t.line,"before"==t.sticky?t.ch-1:t.ch,"after"):t).sticky?et(t.line,t.ch+1,"before"):t);for(var o=0;o<5;o++){var l=!1,s=Xr(e,t),a=r&&r!=t?Xr(e,r):s,u=Mn(e,i={left:Math.min(s.left,a.left),top:Math.min(s.top,a.top)-n,right:Math.max(s.left,a.left),bottom:Math.max(s.bottom,a.bottom)+n}),c=e.doc.scrollTop,h=e.doc.scrollLeft;if(null!=u.scrollTop&&(Hn(e,u.scrollTop),Math.abs(e.doc.scrollTop-c)>1&&(l=!0)),null!=u.scrollLeft&&(Pn(e,u.scrollLeft),Math.abs(e.doc.scrollLeft-h)>1&&(l=!0)),!l)break}return i}(t,st(n,e.scrollToPos.from),st(n,e.scrollToPos.to),e.scrollToPos.margin));var i=e.maybeHiddenMarkers,o=e.maybeUnhiddenMarkers;if(i)for(var l=0;l=e.display.viewTo)){var r=+new Date+e.options.workTime,n=dt(e,t.highlightFrontier),i=[];t.iter(n.line,Math.min(t.first+t.size,e.display.viewTo+500),function(o){if(n.line>=e.display.viewFrom){var l=o.styles,s=o.text.length>e.options.maxHighlightLength?Ue(t.mode,n.state):null,a=ht(e,o,n,!0);s&&(n.state=s),o.styles=a.styles;var u=o.styleClasses,c=a.classes;c?o.styleClasses=c:u&&(o.styleClasses=null);for(var h=!l||l.length!=o.styles.length||u!=c&&(!u||!c||u.bgClass!=c.bgClass||u.textClass!=c.textClass),f=0;!h&&fr)return ei(e,e.options.workDelay),!0}),t.highlightFrontier=n.line,t.modeFrontier=Math.max(t.modeFrontier,n.line),i.length&&qn(e,function(){for(var t=0;t=r.viewFrom&&t.visible.to<=r.viewTo&&(null==r.updateLineNumbers||r.updateLineNumbers>=r.viewTo)&&r.renderedView==r.view&&0==dn(e))return!1;ui(e)&&(hn(e),t.dims=rn(e));var i=n.first+n.size,o=Math.max(t.visible.from-e.options.viewportMargin,n.first),l=Math.min(i,t.visible.to+e.options.viewportMargin);r.viewFroml&&r.viewTo-l<20&&(l=Math.min(i,r.viewTo)),Ct&&(o=Rt(e.doc,o),l=Bt(e.doc,l));var s=o!=r.viewFrom||l!=r.viewTo||r.lastWrapHeight!=t.wrapperHeight||r.lastWrapWidth!=t.wrapperWidth;!function(e,t,r){var n=e.display;0==n.view.length||t>=n.viewTo||r<=n.viewFrom?(n.view=ir(e,t,r),n.viewFrom=t):(n.viewFrom>t?n.view=ir(e,t,n.viewFrom).concat(n.view):n.viewFromr&&(n.view=n.view.slice(0,an(e,r)))),n.viewTo=r}(e,o,l),r.viewOffset=Vt(Xe(e.doc,r.viewFrom)),e.display.mover.style.top=r.viewOffset+"px";var u=dn(e);if(!s&&0==u&&!t.force&&r.renderedView==r.view&&(null==r.updateLineNumbers||r.updateLineNumbers>=r.viewTo))return!1;var c=function(e){if(e.hasFocus())return null;var t=W();if(!t||!D(e.display.lineDiv,t))return null;var r={activeElt:t};if(window.getSelection){var n=window.getSelection();n.anchorNode&&n.extend&&D(e.display.lineDiv,n.anchorNode)&&(r.anchorNode=n.anchorNode,r.anchorOffset=n.anchorOffset,r.focusNode=n.focusNode,r.focusOffset=n.focusOffset)}return r}(e);return u>4&&(r.lineDiv.style.display="none"),function(e,t,r){var n=e.display,i=e.options.lineNumbers,o=n.lineDiv,l=o.firstChild;function s(t){var r=t.nextSibling;return a&&y&&e.display.currentWheelTarget==t?t.style.display="none":t.parentNode.removeChild(t),r}for(var u=n.view,c=n.viewFrom,h=0;h-1&&(d=!1),ur(e,f,c,r)),d&&(M(f.lineNumber),f.lineNumber.appendChild(document.createTextNode(Je(e.options,c)))),l=f.node.nextSibling}else{var p=vr(e,f,c,r);o.insertBefore(p,l)}c+=f.size}for(;l;)l=s(l)}(e,r.updateLineNumbers,t.dims),u>4&&(r.lineDiv.style.display=""),r.renderedView=r.view,function(e){if(e&&e.activeElt&&e.activeElt!=W()&&(e.activeElt.focus(),e.anchorNode&&D(document.body,e.anchorNode)&&D(document.body,e.focusNode))){var t=window.getSelection(),r=document.createRange();r.setEnd(e.anchorNode,e.anchorOffset),r.collapse(!1),t.removeAllRanges(),t.addRange(r),t.extend(e.focusNode,e.focusOffset)}}(c),M(r.cursorDiv),M(r.selectionDiv),r.gutters.style.height=r.sizer.style.minHeight=0,s&&(r.lastWrapHeight=t.wrapperHeight,r.lastWrapWidth=t.wrapperWidth,ei(e,400)),r.updateLineNumbers=null,!0}function ii(e,t){for(var r=t.viewport,n=!0;(n&&e.options.lineWrapping&&t.oldDisplayWidth!=Tr(e)||(r&&null!=r.top&&(r={top:Math.min(e.doc.height+Sr(e.display)-Mr(e),r.top)}),t.visible=Tn(e.display,e.doc,r),!(t.visible.from>=e.display.viewFrom&&t.visible.to<=e.display.viewTo)))&&ni(e,t);n=!1){Ln(e);var i=En(e);pn(e),Rn(e,i),si(e,i),t.force=!1}t.signal(e,"update",e),e.display.viewFrom==e.display.reportedViewFrom&&e.display.viewTo==e.display.reportedViewTo||(t.signal(e,"viewportChange",e,e.display.viewFrom,e.display.viewTo),e.display.reportedViewFrom=e.display.viewFrom,e.display.reportedViewTo=e.display.viewTo)}function oi(e,t){var r=new ri(e,t);if(ni(e,r)){Ln(e),ii(e,r);var n=En(e);pn(e),Rn(e,n),si(e,n),r.finish()}}function li(e){var t=e.gutters.offsetWidth;e.sizer.style.marginLeft=t+"px"}function si(e,t){e.display.sizer.style.minHeight=t.docHeight+"px",e.display.heightForcer.style.top=t.docHeight+"px",e.display.gutters.style.height=t.docHeight+e.display.barHeight+kr(e)+"px"}function ai(e){var t=e.display,r=t.view;if(t.alignWidgets||t.gutters.firstChild&&e.options.fixedGutter){for(var n=nn(t)-t.scroller.scrollLeft+e.doc.scrollLeft,i=t.gutters.offsetWidth,o=n+"px",l=0;ls.clientWidth,c=s.scrollHeight>s.clientHeight;if(i&&u||o&&c){if(o&&y&&a)e:for(var f=t.target,d=l.view;f!=s;f=f.parentNode)for(var p=0;p=0&&tt(e,n.to())<=0)return r}return-1};var bi=function(e,t){this.anchor=e,this.head=t};function wi(e,t,r){var n=e&&e.options.selectionsMayTouch,i=t[r];t.sort(function(e,t){return tt(e.from(),t.from())}),r=B(t,i);for(var o=1;o0:a>=0){var u=ot(s.from(),l.from()),c=it(s.to(),l.to()),h=s.empty()?l.from()==l.head:s.from()==s.head;o<=r&&--r,t.splice(--o,2,new bi(h?c:u,h?u:c))}}return new yi(t,r)}function xi(e,t){return new yi([new bi(e,t||e)],0)}function Ci(e){return e.text?et(e.from.line+e.text.length-1,$(e.text).length+(1==e.text.length?e.from.ch:0)):e.to}function Si(e,t){if(tt(e,t.from)<0)return e;if(tt(e,t.to)<=0)return Ci(t);var r=e.line+t.text.length-(t.to.line-t.from.line)-1,n=e.ch;return e.line==t.to.line&&(n+=Ci(t).ch-t.to.ch),et(r,n)}function Li(e,t){for(var r=[],n=0;n1&&e.remove(s.line+1,p-1),e.insert(s.line+1,m)}sr(e,"change",e,t)}function Ai(e,t,r){!function e(n,i,o){if(n.linked)for(var l=0;ls-(e.cm?e.cm.options.historyEventDelay:500)||"*"==t.origin.charAt(0)))&&(o=function(e,t){return t?(Pi(e.done),$(e.done)):e.done.length&&!$(e.done).ranges?$(e.done):e.done.length>1&&!e.done[e.done.length-2].ranges?(e.done.pop(),$(e.done)):void 0}(i,i.lastOp==n)))l=$(o.changes),0==tt(t.from,t.to)&&0==tt(t.from,l.to)?l.to=Ci(t):o.changes.push(Fi(e,t));else{var a=$(i.done);for(a&&a.ranges||zi(e.sel,i.done),o={changes:[Fi(e,t)],generation:i.generation},i.done.push(o);i.done.length>i.undoDepth;)i.done.shift(),i.done[0].ranges||i.done.shift()}i.done.push(r),i.generation=++i.maxGeneration,i.lastModTime=i.lastSelTime=s,i.lastOp=i.lastSelOp=n,i.lastOrigin=i.lastSelOrigin=t.origin,l||ge(e,"historyAdded")}function Ii(e,t,r,n){var i=e.history,o=n&&n.origin;r==i.lastSelOp||o&&i.lastSelOrigin==o&&(i.lastModTime==i.lastSelTime&&i.lastOrigin==o||function(e,t,r,n){var i=t.charAt(0);return"*"==i||"+"==i&&r.ranges.length==n.ranges.length&&r.somethingSelected()==n.somethingSelected()&&new Date-e.history.lastSelTime<=(e.cm?e.cm.options.historyEventDelay:500)}(e,o,$(i.done),t))?i.done[i.done.length-1]=t:zi(t,i.done),i.lastSelTime=+new Date,i.lastSelOrigin=o,i.lastSelOp=r,n&&!1!==n.clearRedo&&Pi(i.undone)}function zi(e,t){var r=$(t);r&&r.ranges&&r.equals(e)||t.push(e)}function Ri(e,t,r,n){var i=t["spans_"+e.id],o=0;e.iter(Math.max(e.first,r),Math.min(e.first+e.size,n),function(r){r.markedSpans&&((i||(i=t["spans_"+e.id]={}))[o]=r.markedSpans),++o})}function Bi(e){if(!e)return null;for(var t,r=0;r-1&&($(s)[h]=u[h],delete u[h])}}}return n}function Vi(e,t,r,n){if(n){var i=e.anchor;if(r){var o=tt(t,i)<0;o!=tt(r,i)<0?(i=t,t=r):o!=tt(t,r)<0&&(t=r)}return new bi(i,t)}return new bi(r||t,t)}function Ki(e,t,r,n,i){null==i&&(i=e.cm&&(e.cm.display.shift||e.extend)),$i(e,new yi([Vi(e.sel.primary(),t,r,i)],0),n)}function ji(e,t,r){for(var n=[],i=e.cm&&(e.cm.display.shift||e.extend),o=0;o=t.ch:s.to>t.ch))){if(i&&(ge(a,"beforeCursorEnter"),a.explicitlyCleared)){if(o.markedSpans){--l;continue}break}if(!a.atomic)continue;if(r){var h=a.find(n<0?1:-1),f=void 0;if((n<0?c:u)&&(h=ro(e,h,-n,h&&h.line==t.line?o:null)),h&&h.line==t.line&&(f=tt(h,r))&&(n<0?f<0:f>0))return eo(e,h,t,n,i)}var d=a.find(n<0?-1:1);return(n<0?u:c)&&(d=ro(e,d,n,d.line==t.line?o:null)),d?eo(e,d,t,n,i):null}}return t}function to(e,t,r,n,i){var o=n||1,l=eo(e,t,r,o,i)||!i&&eo(e,t,r,o,!0)||eo(e,t,r,-o,i)||!i&&eo(e,t,r,-o,!0);return l||(e.cantEdit=!0,et(e.first,0))}function ro(e,t,r,n){return r<0&&0==t.ch?t.line>e.first?st(e,et(t.line-1)):null:r>0&&t.ch==(n||Xe(e,t.line)).text.length?t.line0)){var c=[a,1],h=tt(u.from,s.from),f=tt(u.to,s.to);(h<0||!l.inclusiveLeft&&!h)&&c.push({from:u.from,to:s.from}),(f>0||!l.inclusiveRight&&!f)&&c.push({from:s.to,to:u.to}),i.splice.apply(i,c),a+=c.length-3}}return i}(e,t.from,t.to);if(n)for(var i=n.length-1;i>=0;--i)lo(e,{from:n[i].from,to:n[i].to,text:i?[""]:t.text,origin:t.origin});else lo(e,t)}}function lo(e,t){if(1!=t.text.length||""!=t.text[0]||0!=tt(t.from,t.to)){var r=Li(e,t);Ei(e,t,r,e.cm?e.cm.curOp.id:NaN),uo(e,t,r,Tt(e,t));var n=[];Ai(e,function(e,r){r||-1!=B(n,e.history)||(po(e.history,t),n.push(e.history)),uo(e,t,null,Tt(e,t))})}}function so(e,t,r){var n=e.cm&&e.cm.state.suppressEdits;if(!n||r){for(var i,o=e.history,l=e.sel,s="undo"==t?o.done:o.undone,a="undo"==t?o.undone:o.done,u=0;u=0;--d){var p=f(d);if(p)return p.v}}}}function ao(e,t){if(0!=t&&(e.first+=t,e.sel=new yi(q(e.sel.ranges,function(e){return new bi(et(e.anchor.line+t,e.anchor.ch),et(e.head.line+t,e.head.ch))}),e.sel.primIndex),e.cm)){un(e.cm,e.first,e.first-t,t);for(var r=e.cm.display,n=r.viewFrom;ne.lastLine())){if(t.from.lineo&&(t={from:t.from,to:et(o,Xe(e,o).text.length),text:[t.text[0]],origin:t.origin}),t.removed=Ye(e,t.from,t.to),r||(r=Li(e,t)),e.cm?function(e,t,r){var n=e.doc,i=e.display,o=t.from,l=t.to,s=!1,a=o.line;e.options.lineWrapping||(a=qe(zt(Xe(n,o.line))),n.iter(a,l.line+1,function(e){if(e==i.maxLine)return s=!0,!0}));n.sel.contains(t.from,t.to)>-1&&me(e);Oi(n,t,r,on(e)),e.options.lineWrapping||(n.iter(a,o.line+t.text.length,function(e){var t=Kt(e);t>i.maxLineLength&&(i.maxLine=e,i.maxLineLength=t,i.maxLineChanged=!0,s=!1)}),s&&(e.curOp.updateMaxLine=!0));(function(e,t){if(e.modeFrontier=Math.min(e.modeFrontier,t),!(e.highlightFrontierr;n--){var i=Xe(e,n).stateAfter;if(i&&(!(i instanceof ut)||n+i.lookAhead1||!(this.children[0]instanceof vo))){var s=[];this.collapse(s),this.children=[new vo(s)],this.children[0].parent=this}},collapse:function(e){for(var t=0;t50){for(var l=i.lines.length%25+25,s=l;s10);e.parent.maybeSpill()}},iterN:function(e,t,r){for(var n=0;n0||0==l&&!1!==o.clearWhenEmpty)return o;if(o.replacedWith&&(o.collapsed=!0,o.widgetNode=A("span",[o.replacedWith],"CodeMirror-widget"),n.handleMouseEvents||o.widgetNode.setAttribute("cm-ignore-events","true"),n.insertLeft&&(o.widgetNode.insertLeft=!0)),o.collapsed){if(It(e,t.line,t,r,o)||t.line!=r.line&&It(e,r.line,t,r,o))throw new Error("Inserting collapsed marker partially overlapping an existing one");Ct=!0}o.addToHistory&&Ei(e,{from:t,to:r,origin:"markText"},e.sel,NaN);var s,a=t.line,u=e.cm;if(e.iter(a,r.line+1,function(e){u&&o.collapsed&&!u.options.lineWrapping&&zt(e)==u.display.maxLine&&(s=!0),o.collapsed&&a!=t.line&&$e(e,0),function(e,t){e.markedSpans=e.markedSpans?e.markedSpans.concat([t]):[t],t.marker.attachLine(e)}(e,new St(o,a==t.line?t.ch:null,a==r.line?r.ch:null)),++a}),o.collapsed&&e.iter(t.line,r.line+1,function(t){Gt(e,t)&&$e(t,0)}),o.clearOnEnter&&fe(o,"beforeCursorEnter",function(){return o.clear()}),o.readOnly&&(xt=!0,(e.history.done.length||e.history.undone.length)&&e.clearHistory()),o.collapsed&&(o.id=++wo,o.atomic=!0),u){if(s&&(u.curOp.updateMaxLine=!0),o.collapsed)un(u,t.line,r.line+1);else if(o.className||o.startStyle||o.endStyle||o.css||o.attributes||o.title)for(var c=t.line;c<=r.line;c++)cn(u,c,"text");o.atomic&&Qi(u.doc),sr(u,"markerAdded",u,o)}return o}xo.prototype.clear=function(){if(!this.explicitlyCleared){var e=this.doc.cm,t=e&&!e.curOp;if(t&&Kn(e),ye(this,"clear")){var r=this.find();r&&sr(this,"clear",r.from,r.to)}for(var n=null,i=null,o=0;oe.display.maxLineLength&&(e.display.maxLine=u,e.display.maxLineLength=c,e.display.maxLineChanged=!0)}null!=n&&e&&this.collapsed&&un(e,n,i+1),this.lines.length=0,this.explicitlyCleared=!0,this.atomic&&this.doc.cantEdit&&(this.doc.cantEdit=!1,e&&Qi(e.doc)),e&&sr(e,"markerCleared",e,this,n,i),t&&jn(e),this.parent&&this.parent.clear()}},xo.prototype.find=function(e,t){var r,n;null==e&&"bookmark"==this.type&&(e=1);for(var i=0;i=0;a--)oo(this,n[a]);s?_i(this,s):this.cm&&On(this.cm)}),undo:Jn(function(){so(this,"undo")}),redo:Jn(function(){so(this,"redo")}),undoSelection:Jn(function(){so(this,"undo",!0)}),redoSelection:Jn(function(){so(this,"redo",!0)}),setExtending:function(e){this.extend=e},getExtending:function(){return this.extend},historySize:function(){for(var e=this.history,t=0,r=0,n=0;n=e.ch)&&t.push(i.marker.parent||i.marker)}return t},findMarks:function(e,t,r){e=st(this,e),t=st(this,t);var n=[],i=e.line;return this.iter(e.line,t.line+1,function(o){var l=o.markedSpans;if(l)for(var s=0;s=a.to||null==a.from&&i!=e.line||null!=a.from&&i==t.line&&a.from>=t.ch||r&&!r(a.marker)||n.push(a.marker.parent||a.marker)}++i}),n},getAllMarks:function(){var e=[];return this.iter(function(t){var r=t.markedSpans;if(r)for(var n=0;ne)return t=e,!0;e-=o,++r}),st(this,et(r,t))},indexFromPos:function(e){var t=(e=st(this,e)).ch;if(e.linet&&(t=e.from),null!=e.to&&e.to-1)return t.state.draggingText(e),void setTimeout(function(){return t.display.input.focus()},20);try{var c=e.dataTransfer.getData("Text");if(c){var h;if(t.state.draggingText&&!t.state.draggingText.copy&&(h=t.listSelections()),qi(t.doc,xi(r,r)),h)for(var f=0;f=0;t--)co(e.doc,"",n[t].from,n[t].to,"+delete");On(e)})}function _o(e,t,r){var n=oe(e.text,t+r,r);return n<0||n>e.text.length?null:n}function $o(e,t,r){var n=_o(e,t.ch,r);return null==n?null:new et(t.line,n,r<0?"after":"before")}function qo(e,t,r,n,i){if(e){var o=ce(r,t.doc.direction);if(o){var l,s=i<0?$(o):o[0],a=i<0==(1==s.level)?"after":"before";if(s.level>0||"rtl"==t.doc.direction){var u=Dr(t,r);l=i<0?r.text.length-1:0;var c=Wr(t,u,l).top;l=le(function(e){return Wr(t,u,e).top==c},i<0==(1==s.level)?s.from:s.to-1,l),"before"==a&&(l=_o(r,l,1))}else l=i<0?s.to:s.from;return new et(n,l,a)}}return new et(n,i<0?r.text.length:0,i<0?"before":"after")}Ro.basic={Left:"goCharLeft",Right:"goCharRight",Up:"goLineUp",Down:"goLineDown",End:"goLineEnd",Home:"goLineStartSmart",PageUp:"goPageUp",PageDown:"goPageDown",Delete:"delCharAfter",Backspace:"delCharBefore","Shift-Backspace":"delCharBefore",Tab:"defaultTab","Shift-Tab":"indentAuto",Enter:"newlineAndIndent",Insert:"toggleOverwrite",Esc:"singleSelection"},Ro.pcDefault={"Ctrl-A":"selectAll","Ctrl-D":"deleteLine","Ctrl-Z":"undo","Shift-Ctrl-Z":"redo","Ctrl-Y":"redo","Ctrl-Home":"goDocStart","Ctrl-End":"goDocEnd","Ctrl-Up":"goLineUp","Ctrl-Down":"goLineDown","Ctrl-Left":"goGroupLeft","Ctrl-Right":"goGroupRight","Alt-Left":"goLineStart","Alt-Right":"goLineEnd","Ctrl-Backspace":"delGroupBefore","Ctrl-Delete":"delGroupAfter","Ctrl-S":"save","Ctrl-F":"find","Ctrl-G":"findNext","Shift-Ctrl-G":"findPrev","Shift-Ctrl-F":"replace","Shift-Ctrl-R":"replaceAll","Ctrl-[":"indentLess","Ctrl-]":"indentMore","Ctrl-U":"undoSelection","Shift-Ctrl-U":"redoSelection","Alt-U":"redoSelection",fallthrough:"basic"},Ro.emacsy={"Ctrl-F":"goCharRight","Ctrl-B":"goCharLeft","Ctrl-P":"goLineUp","Ctrl-N":"goLineDown","Alt-F":"goWordRight","Alt-B":"goWordLeft","Ctrl-A":"goLineStart","Ctrl-E":"goLineEnd","Ctrl-V":"goPageDown","Shift-Ctrl-V":"goPageUp","Ctrl-D":"delCharAfter","Ctrl-H":"delCharBefore","Alt-D":"delWordAfter","Alt-Backspace":"delWordBefore","Ctrl-K":"killLine","Ctrl-T":"transposeChars","Ctrl-O":"openLine"},Ro.macDefault={"Cmd-A":"selectAll","Cmd-D":"deleteLine","Cmd-Z":"undo","Shift-Cmd-Z":"redo","Cmd-Y":"redo","Cmd-Home":"goDocStart","Cmd-Up":"goDocStart","Cmd-End":"goDocEnd","Cmd-Down":"goDocEnd","Alt-Left":"goGroupLeft","Alt-Right":"goGroupRight","Cmd-Left":"goLineLeft","Cmd-Right":"goLineRight","Alt-Backspace":"delGroupBefore","Ctrl-Alt-Backspace":"delGroupAfter","Alt-Delete":"delGroupAfter","Cmd-S":"save","Cmd-F":"find","Cmd-G":"findNext","Shift-Cmd-G":"findPrev","Cmd-Alt-F":"replace","Shift-Cmd-Alt-F":"replaceAll","Cmd-[":"indentLess","Cmd-]":"indentMore","Cmd-Backspace":"delWrappedLineLeft","Cmd-Delete":"delWrappedLineRight","Cmd-U":"undoSelection","Shift-Cmd-U":"redoSelection","Ctrl-Up":"goDocStart","Ctrl-Down":"goDocEnd",fallthrough:["basic","emacsy"]},Ro.default=y?Ro.macDefault:Ro.pcDefault;var Zo={selectAll:no,singleSelection:function(e){return e.setSelection(e.getCursor("anchor"),e.getCursor("head"),V)},killLine:function(e){return Yo(e,function(t){if(t.empty()){var r=Xe(e.doc,t.head.line).text.length;return t.head.ch==r&&t.head.line0)i=new et(i.line,i.ch+1),e.replaceRange(o.charAt(i.ch-1)+o.charAt(i.ch-2),et(i.line,i.ch-2),i,"+transpose");else if(i.line>e.doc.first){var l=Xe(e.doc,i.line-1).text;l&&(i=new et(i.line,1),e.replaceRange(o.charAt(0)+e.doc.lineSeparator()+l.charAt(l.length-1),et(i.line-1,l.length-1),i,"+transpose"))}r.push(new bi(i,i))}e.setSelections(r)})},newlineAndIndent:function(e){return qn(e,function(){for(var t=e.listSelections(),r=t.length-1;r>=0;r--)e.replaceRange(e.doc.lineSeparator(),t[r].anchor,t[r].head,"+input");t=e.listSelections();for(var n=0;n-1&&(tt((i=u.ranges[i]).from(),t)<0||t.xRel>0)&&(tt(i.to(),t)>0||t.xRel<0)?function(e,t,r,n){var i=e.display,o=!1,u=Zn(e,function(t){a&&(i.scroller.draggable=!1),e.state.draggingText=!1,pe(i.wrapper.ownerDocument,"mouseup",u),pe(i.wrapper.ownerDocument,"mousemove",c),pe(i.scroller,"dragstart",h),pe(i.scroller,"drop",u),o||(we(t),n.addNew||Ki(e.doc,r,null,null,n.extend),a||l&&9==s?setTimeout(function(){i.wrapper.ownerDocument.body.focus(),i.input.focus()},20):i.input.focus())}),c=function(e){o=o||Math.abs(t.clientX-e.clientX)+Math.abs(t.clientY-e.clientY)>=10},h=function(){return o=!0};a&&(i.scroller.draggable=!0);e.state.draggingText=u,u.copy=!n.moveOnDrag,i.scroller.dragDrop&&i.scroller.dragDrop();fe(i.wrapper.ownerDocument,"mouseup",u),fe(i.wrapper.ownerDocument,"mousemove",c),fe(i.scroller,"dragstart",h),fe(i.scroller,"drop",u),xn(e),setTimeout(function(){return i.input.focus()},20)}(e,n,t,o):function(e,t,r,n){var i=e.display,o=e.doc;we(t);var l,s,a=o.sel,u=a.ranges;n.addNew&&!n.extend?(s=o.sel.contains(r),l=s>-1?u[s]:new bi(r,r)):(l=o.sel.primary(),s=o.sel.primIndex);if("rectangle"==n.unit)n.addNew||(l=new bi(r,r)),r=sn(e,t,!0,!0),s=-1;else{var c=dl(e,r,n.unit);l=n.extend?Vi(l,c.anchor,c.head,n.extend):c}n.addNew?-1==s?(s=u.length,$i(o,wi(e,u.concat([l]),s),{scroll:!1,origin:"*mouse"})):u.length>1&&u[s].empty()&&"char"==n.unit&&!n.extend?($i(o,wi(e,u.slice(0,s).concat(u.slice(s+1)),0),{scroll:!1,origin:"*mouse"}),a=o.sel):Xi(o,s,l,K):(s=0,$i(o,new yi([l],0),K),a=o.sel);var h=r;function f(t){if(0!=tt(h,t))if(h=t,"rectangle"==n.unit){for(var i=[],u=e.options.tabSize,c=z(Xe(o,r.line).text,r.ch,u),f=z(Xe(o,t.line).text,t.ch,u),d=Math.min(c,f),p=Math.max(c,f),g=Math.min(r.line,t.line),v=Math.min(e.lastLine(),Math.max(r.line,t.line));g<=v;g++){var m=Xe(o,g).text,y=X(m,d,u);d==p?i.push(new bi(et(g,y),et(g,y))):m.length>y&&i.push(new bi(et(g,y),et(g,X(m,p,u))))}i.length||i.push(new bi(r,r)),$i(o,wi(e,a.ranges.slice(0,s).concat(i),s),{origin:"*mouse",scroll:!1}),e.scrollIntoView(t)}else{var b,w=l,x=dl(e,t,n.unit),C=w.anchor;tt(x.anchor,C)>0?(b=x.head,C=ot(w.from(),x.anchor)):(b=x.anchor,C=it(w.to(),x.head));var S=a.ranges.slice(0);S[s]=function(e,t){var r=t.anchor,n=t.head,i=Xe(e.doc,r.line);if(0==tt(r,n)&&r.sticky==n.sticky)return t;var o=ce(i);if(!o)return t;var l=ae(o,r.ch,r.sticky),s=o[l];if(s.from!=r.ch&&s.to!=r.ch)return t;var a,u=l+(s.from==r.ch==(1!=s.level)?0:1);if(0==u||u==o.length)return t;if(n.line!=r.line)a=(n.line-r.line)*("ltr"==e.doc.direction?1:-1)>0;else{var c=ae(o,n.ch,n.sticky),h=c-l||(n.ch-r.ch)*(1==s.level?-1:1);a=c==u-1||c==u?h<0:h>0}var f=o[u+(a?-1:0)],d=a==(1==f.level),p=d?f.from:f.to,g=d?"after":"before";return r.ch==p&&r.sticky==g?t:new bi(new et(r.line,p,g),n)}(e,new bi(st(o,C),b)),$i(o,wi(e,S,s),K)}}var d=i.wrapper.getBoundingClientRect(),p=0;function g(t){e.state.selectingText=!1,p=1/0,t&&(we(t),i.input.focus()),pe(i.wrapper.ownerDocument,"mousemove",v),pe(i.wrapper.ownerDocument,"mouseup",m),o.history.lastSelOrigin=null}var v=Zn(e,function(t){0!==t.buttons&&ke(t)?function t(r){var l=++p;var s=sn(e,r,!0,"rectangle"==n.unit);if(!s)return;if(0!=tt(s,h)){e.curOp.focus=W(),f(s);var a=Tn(i,o);(s.line>=a.to||s.lined.bottom?20:0;u&&setTimeout(Zn(e,function(){p==l&&(i.scroller.scrollTop+=u,t(r))}),50)}}(t):g(t)}),m=Zn(e,g);e.state.selectingText=m,fe(i.wrapper.ownerDocument,"mousemove",v),fe(i.wrapper.ownerDocument,"mouseup",m)}(e,n,t,o)}(t,n,o,e):Le(e)==r.scroller&&we(e):2==i?(n&&Ki(t.doc,n),setTimeout(function(){return r.input.focus()},20)):3==i&&(S?t.display.input.onContextMenu(e):xn(t)))}}function dl(e,t,r){if("char"==r)return new bi(t,t);if("word"==r)return e.findWordAt(t);if("line"==r)return new bi(et(t.line,0),st(e.doc,et(t.line+1,0)));var n=r(e,t);return new bi(n.from,n.to)}function pl(e,t,r,n){var i,o;if(t.touches)i=t.touches[0].clientX,o=t.touches[0].clientY;else try{i=t.clientX,o=t.clientY}catch(t){return!1}if(i>=Math.floor(e.display.gutters.getBoundingClientRect().right))return!1;n&&we(t);var l=e.display,s=l.lineDiv.getBoundingClientRect();if(o>s.bottom||!ye(e,r))return Ce(t);o-=s.top-l.viewOffset;for(var a=0;a=i)return ge(e,r,e,Ze(e.doc,o),e.display.gutterSpecs[a].className,t),Ce(t)}}function gl(e,t){return pl(e,t,"gutterClick",!0)}function vl(e,t){xr(e.display,t)||function(e,t){if(!ye(e,"gutterContextMenu"))return!1;return pl(e,t,"gutterContextMenu",!1)}(e,t)||ve(e,t,"contextmenu")||S||e.display.input.onContextMenu(t)}function ml(e){e.display.wrapper.className=e.display.wrapper.className.replace(/\s*cm-s-\S+/g,"")+e.options.theme.replace(/(^|\s)\s*/g," cm-s-"),Rr(e)}hl.prototype.compare=function(e,t,r){return this.time+400>e&&0==tt(t,this.pos)&&r==this.button};var yl={toString:function(){return"CodeMirror.Init"}},bl={},wl={};function xl(e,t,r){if(!t!=!(r&&r!=yl)){var n=e.display.dragFunctions,i=t?fe:pe;i(e.display.scroller,"dragstart",n.start),i(e.display.scroller,"dragenter",n.enter),i(e.display.scroller,"dragover",n.over),i(e.display.scroller,"dragleave",n.leave),i(e.display.scroller,"drop",n.drop)}}function Cl(e){e.options.lineWrapping?(H(e.display.wrapper,"CodeMirror-wrap"),e.display.sizer.style.minWidth="",e.display.sizerWidth=null):(T(e.display.wrapper,"CodeMirror-wrap"),jt(e)),ln(e),un(e),Rr(e),setTimeout(function(){return Rn(e)},100)}function Sl(e,t){var n=this;if(!(this instanceof Sl))return new Sl(e,t);this.options=t=t?I(t):{},I(bl,t,!1);var i=t.value;"string"==typeof i?i=new Mo(i,t.mode,null,t.lineSeparator,t.direction):t.mode&&(i.modeOption=t.mode),this.doc=i;var o=new Sl.inputStyles[t.inputStyle](this),u=this.display=new function(e,t,n,i){var o=this;this.input=n,o.scrollbarFiller=O("div",null,"CodeMirror-scrollbar-filler"),o.scrollbarFiller.setAttribute("cm-not-content","true"),o.gutterFiller=O("div",null,"CodeMirror-gutter-filler"),o.gutterFiller.setAttribute("cm-not-content","true"),o.lineDiv=A("div",null,"CodeMirror-code"),o.selectionDiv=O("div",null,null,"position: relative; z-index: 1"),o.cursorDiv=O("div",null,"CodeMirror-cursors"),o.measure=O("div",null,"CodeMirror-measure"),o.lineMeasure=O("div",null,"CodeMirror-measure"),o.lineSpace=A("div",[o.measure,o.lineMeasure,o.selectionDiv,o.cursorDiv,o.lineDiv],null,"position: relative; outline: none");var u=A("div",[o.lineSpace],"CodeMirror-lines");o.mover=O("div",[u],null,"position: relative"),o.sizer=O("div",[o.mover],"CodeMirror-sizer"),o.sizerWidth=null,o.heightForcer=O("div",null,null,"position: absolute; height: "+G+"px; width: 1px;"),o.gutters=O("div",null,"CodeMirror-gutters"),o.lineGutter=null,o.scroller=O("div",[o.sizer,o.heightForcer,o.gutters],"CodeMirror-scroll"),o.scroller.setAttribute("tabIndex","-1"),o.wrapper=O("div",[o.scrollbarFiller,o.gutterFiller,o.scroller],"CodeMirror"),l&&s<8&&(o.gutters.style.zIndex=-1,o.scroller.style.paddingRight=0),a||r&&m||(o.scroller.draggable=!0),e&&(e.appendChild?e.appendChild(o.wrapper):e(o.wrapper)),o.viewFrom=o.viewTo=t.first,o.reportedViewFrom=o.reportedViewTo=t.first,o.view=[],o.renderedView=null,o.externalMeasured=null,o.viewOffset=0,o.lastWrapHeight=o.lastWrapWidth=0,o.updateLineNumbers=null,o.nativeBarWidth=o.barHeight=o.barWidth=0,o.scrollbarsClipped=!1,o.lineNumWidth=o.lineNumInnerWidth=o.lineNumChars=null,o.alignWidgets=!1,o.cachedCharWidth=o.cachedTextHeight=o.cachedPaddingH=null,o.maxLine=null,o.maxLineLength=0,o.maxLineChanged=!1,o.wheelDX=o.wheelDY=o.wheelStartX=o.wheelStartY=null,o.shift=!1,o.selForContextMenu=null,o.activeTouch=null,o.gutterSpecs=ci(i.gutters,i.lineNumbers),hi(o),n.init(o)}(e,i,o,t);for(var c in u.wrapper.CodeMirror=this,ml(this),t.lineWrapping&&(this.display.wrapper.className+=" CodeMirror-wrap"),Un(this),this.state={keyMaps:[],overlays:[],modeGen:0,overwrite:!1,delayingBlurEvent:!1,focused:!1,suppressEdits:!1,pasteIncoming:-1,cutIncoming:-1,selectingText:!1,draggingText:!1,highlight:new R,keySeq:null,specialChars:null},t.autofocus&&!m&&u.input.focus(),l&&s<11&&setTimeout(function(){return n.display.input.reset(!0)},20),function(e){var t=e.display;fe(t.scroller,"mousedown",Zn(e,fl)),fe(t.scroller,"dblclick",l&&s<11?Zn(e,function(t){if(!ve(e,t)){var r=sn(e,t);if(r&&!gl(e,t)&&!xr(e.display,t)){we(t);var n=e.findWordAt(r);Ki(e.doc,n.anchor,n.head)}}}):function(t){return ve(e,t)||we(t)});fe(t.scroller,"contextmenu",function(t){return vl(e,t)});var r,n={end:0};function i(){t.activeTouch&&(r=setTimeout(function(){return t.activeTouch=null},1e3),(n=t.activeTouch).end=+new Date)}function o(e,t){if(null==t.left)return!0;var r=t.left-e.left,n=t.top-e.top;return r*r+n*n>400}fe(t.scroller,"touchstart",function(i){if(!ve(e,i)&&!function(e){if(1!=e.touches.length)return!1;var t=e.touches[0];return t.radiusX<=1&&t.radiusY<=1}(i)&&!gl(e,i)){t.input.ensurePolled(),clearTimeout(r);var o=+new Date;t.activeTouch={start:o,moved:!1,prev:o-n.end<=300?n:null},1==i.touches.length&&(t.activeTouch.left=i.touches[0].pageX,t.activeTouch.top=i.touches[0].pageY)}}),fe(t.scroller,"touchmove",function(){t.activeTouch&&(t.activeTouch.moved=!0)}),fe(t.scroller,"touchend",function(r){var n=t.activeTouch;if(n&&!xr(t,r)&&null!=n.left&&!n.moved&&new Date-n.start<300){var l,s=e.coordsChar(t.activeTouch,"page");l=!n.prev||o(n,n.prev)?new bi(s,s):!n.prev.prev||o(n,n.prev.prev)?e.findWordAt(s):new bi(et(s.line,0),st(e.doc,et(s.line+1,0))),e.setSelection(l.anchor,l.head),e.focus(),we(r)}i()}),fe(t.scroller,"touchcancel",i),fe(t.scroller,"scroll",function(){t.scroller.clientHeight&&(Hn(e,t.scroller.scrollTop),Pn(e,t.scroller.scrollLeft,!0),ge(e,"scroll",e))}),fe(t.scroller,"mousewheel",function(t){return mi(e,t)}),fe(t.scroller,"DOMMouseScroll",function(t){return mi(e,t)}),fe(t.wrapper,"scroll",function(){return t.wrapper.scrollTop=t.wrapper.scrollLeft=0}),t.dragFunctions={enter:function(t){ve(e,t)||Se(t)},over:function(t){ve(e,t)||(!function(e,t){var r=sn(e,t);if(r){var n=document.createDocumentFragment();vn(e,r,n),e.display.dragCursor||(e.display.dragCursor=O("div",null,"CodeMirror-cursors CodeMirror-dragcursors"),e.display.lineSpace.insertBefore(e.display.dragCursor,e.display.cursorDiv)),N(e.display.dragCursor,n)}}(e,t),Se(t))},start:function(t){return function(e,t){if(l&&(!e.state.draggingText||+new Date-No<100))Se(t);else if(!ve(e,t)&&!xr(e.display,t)&&(t.dataTransfer.setData("Text",e.getSelection()),t.dataTransfer.effectAllowed="copyMove",t.dataTransfer.setDragImage&&!f)){var r=O("img",null,null,"position: fixed; left: 0; top: 0;");r.src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==",h&&(r.width=r.height=1,e.display.wrapper.appendChild(r),r._top=r.offsetTop),t.dataTransfer.setDragImage(r,0,0),h&&r.parentNode.removeChild(r)}}(e,t)},drop:Zn(e,Oo),leave:function(t){ve(e,t)||Ao(e)}};var a=t.input.getField();fe(a,"keyup",function(t){return sl.call(e,t)}),fe(a,"keydown",Zn(e,ll)),fe(a,"keypress",Zn(e,al)),fe(a,"focus",function(t){return Cn(e,t)}),fe(a,"blur",function(t){return Sn(e,t)})}(this),Ho(),Kn(this),this.curOp.forceUpdate=!0,Di(this,i),t.autofocus&&!m||this.hasFocus()?setTimeout(E(Cn,this),20):Sn(this),wl)wl.hasOwnProperty(c)&&wl[c](n,t[c],yl);ui(this),t.finishInit&&t.finishInit(this);for(var d=0;d150)){if(!n)return;r="prev"}}else u=0,r="not";"prev"==r?u=t>o.first?z(Xe(o,t-1).text,null,l):0:"add"==r?u=a+e.options.indentUnit:"subtract"==r?u=a-e.options.indentUnit:"number"==typeof r&&(u=a+r),u=Math.max(0,u);var h="",f=0;if(e.options.indentWithTabs)for(var d=Math.floor(u/l);d;--d)f+=l,h+="\t";if(fl,a=We(t),u=null;if(s&&n.ranges.length>1)if(Tl&&Tl.text.join("\n")==t){if(n.ranges.length%Tl.text.length==0){u=[];for(var c=0;c=0;f--){var d=n.ranges[f],p=d.from(),g=d.to();d.empty()&&(r&&r>0?p=et(p.line,p.ch-r):e.state.overwrite&&!s?g=et(g.line,Math.min(Xe(o,g.line).text.length,g.ch+$(a).length)):s&&Tl&&Tl.lineWise&&Tl.text.join("\n")==t&&(p=g=et(p.line,0)));var v={from:p,to:g,text:u?u[f%u.length]:a,origin:i||(s?"paste":e.state.cutIncoming>l?"cut":"+input")};oo(e.doc,v),sr(e,"inputRead",e,v)}t&&!s&&Al(e,t),On(e),e.curOp.updateInput<2&&(e.curOp.updateInput=h),e.curOp.typing=!0,e.state.pasteIncoming=e.state.cutIncoming=-1}function Ol(e,t){var r=e.clipboardData&&e.clipboardData.getData("Text");if(r)return e.preventDefault(),t.isReadOnly()||t.options.disableInput||qn(t,function(){return Nl(t,r,0,null,"paste")}),!0}function Al(e,t){if(e.options.electricChars&&e.options.smartIndent)for(var r=e.doc.sel,n=r.ranges.length-1;n>=0;n--){var i=r.ranges[n];if(!(i.head.ch>100||n&&r.ranges[n-1].head.line==i.head.line)){var o=e.getModeAt(i.head),l=!1;if(o.electricChars){for(var s=0;s-1){l=kl(e,i.head.line,"smart");break}}else o.electricInput&&o.electricInput.test(Xe(e.doc,i.head.line).text.slice(0,i.head.ch))&&(l=kl(e,i.head.line,"smart"));l&&sr(e,"electricInput",e,i.head.line)}}}function Dl(e){for(var t=[],r=[],n=0;n=t.text.length?(r.ch=t.text.length,r.sticky="before"):r.ch<=0&&(r.ch=0,r.sticky="after");var o=ae(i,r.ch,r.sticky),l=i[o];if("ltr"==e.doc.direction&&l.level%2==0&&(n>0?l.to>r.ch:l.from=l.from&&f>=c.begin)){var d=h?"before":"after";return new et(r.line,f,d)}}var p=function(e,t,n){for(var o=function(e,t){return t?new et(r.line,a(e,1),"before"):new et(r.line,e,"after")};e>=0&&e0==(1!=l.level),u=s?n.begin:a(n.end,-1);if(l.from<=u&&u0?c.end:a(c.begin,-1);return null==v||n>0&&v==t.text.length||!(g=p(n>0?0:i.length-1,n,u(v)))?null:g}(e.cm,s,t,r):$o(s,t,r))){if(n||(l=t.line+r)=e.first+e.size||(t=new et(l,t.ch,t.sticky),!(s=Xe(e,l))))return!1;t=qo(i,e.cm,s,t.line,r)}else t=o;return!0}if("char"==n)a();else if("column"==n)a(!0);else if("word"==n||"group"==n)for(var u=null,c="group"==n,h=e.cm&&e.cm.getHelper(t,"wordChars"),f=!0;!(r<0)||a(!f);f=!1){var d=s.text.charAt(t.ch)||"\n",p=te(d,h)?"w":c&&"\n"==d?"n":!c||/\s/.test(d)?null:"p";if(!c||f||p||(p="s"),u&&u!=p){r<0&&(r=1,a(),t.sticky="after");break}if(p&&(u=p),r>0&&!a(!f))break}var g=to(e,t,o,l,!0);return rt(o,g)&&(g.hitSide=!0),g}function Pl(e,t,r,n){var i,o,l=e.doc,s=t.left;if("page"==n){var a=Math.min(e.display.wrapper.clientHeight,window.innerHeight||document.documentElement.clientHeight),u=Math.max(a-.5*en(e.display),3);i=(r>0?t.bottom:t.top)+r*u}else"line"==n&&(i=r>0?t.bottom+3:t.top-3);for(;(o=$r(e,s,i)).outside;){if(r<0?i<=0:i>=l.height){o.hitSide=!0;break}i+=5*r}return o}var El=function(e){this.cm=e,this.lastAnchorNode=this.lastAnchorOffset=this.lastFocusNode=this.lastFocusOffset=null,this.polling=new R,this.composing=null,this.gracePeriod=!1,this.readDOMTimeout=null};function Il(e,t){var r=Ar(e,t.line);if(!r||r.hidden)return null;var n=Xe(e.doc,t.line),i=Nr(r,n,t.line),o=ce(n,e.doc.direction),l="left";o&&(l=ae(o,t.ch)%2?"right":"left");var s=Pr(i.map,t.ch,l);return s.offset="right"==s.collapse?s.end:s.start,s}function zl(e,t){return t&&(e.bad=!0),e}function Rl(e,t,r){var n;if(t==e.display.lineDiv){if(!(n=e.display.lineDiv.childNodes[r]))return zl(e.clipPos(et(e.display.viewTo-1)),!0);t=null,r=0}else for(n=t;;n=n.parentNode){if(!n||n==e.display.lineDiv)return null;if(n.parentNode&&n.parentNode==e.display.lineDiv)break}for(var i=0;i=t.display.viewTo||o.line=t.display.viewFrom&&Il(t,i)||{node:a[0].measure.map[2],offset:0},c=o.linen.firstLine()&&(l=et(l.line-1,Xe(n.doc,l.line-1).length)),s.ch==Xe(n.doc,s.line).text.length&&s.linei.viewTo-1)return!1;l.line==i.viewFrom||0==(e=an(n,l.line))?(t=qe(i.view[0].line),r=i.view[0].node):(t=qe(i.view[e].line),r=i.view[e-1].node.nextSibling);var a,u,c=an(n,s.line);if(c==i.view.length-1?(a=i.viewTo-1,u=i.lineDiv.lastChild):(a=qe(i.view[c+1].line)-1,u=i.view[c+1].node.previousSibling),!r)return!1;for(var h=n.doc.splitLines(function(e,t,r,n,i){var o="",l=!1,s=e.doc.lineSeparator(),a=!1;function u(){l&&(o+=s,a&&(o+=s),l=a=!1)}function c(e){e&&(u(),o+=e)}function h(t){if(1==t.nodeType){var r=t.getAttribute("cm-text");if(r)return void c(r);var o,f=t.getAttribute("cm-marker");if(f){var d=e.findMarks(et(n,0),et(i+1,0),(v=+f,function(e){return e.id==v}));return void(d.length&&(o=d[0].find(0))&&c(Ye(e.doc,o.from,o.to).join(s)))}if("false"==t.getAttribute("contenteditable"))return;var p=/^(pre|div|p|li|table|br)$/i.test(t.nodeName);if(!/^br$/i.test(t.nodeName)&&0==t.textContent.length)return;p&&u();for(var g=0;g1&&f.length>1;)if($(h)==$(f))h.pop(),f.pop(),a--;else{if(h[0]!=f[0])break;h.shift(),f.shift(),t++}for(var d=0,p=0,g=h[0],v=f[0],m=Math.min(g.length,v.length);dl.ch&&y.charCodeAt(y.length-p-1)==b.charCodeAt(b.length-p-1);)d--,p++;h[h.length-1]=y.slice(0,y.length-p).replace(/^\u200b+/,""),h[0]=h[0].slice(d).replace(/\u200b+$/,"");var x=et(t,d),C=et(a,f.length?$(f).length-p:0);return h.length>1||h[0]||tt(x,C)?(co(n.doc,h,x,C,"+input"),!0):void 0},El.prototype.ensurePolled=function(){this.forceCompositionEnd()},El.prototype.reset=function(){this.forceCompositionEnd()},El.prototype.forceCompositionEnd=function(){this.composing&&(clearTimeout(this.readDOMTimeout),this.composing=null,this.updateFromDOM(),this.div.blur(),this.div.focus())},El.prototype.readFromDOMSoon=function(){var e=this;null==this.readDOMTimeout&&(this.readDOMTimeout=setTimeout(function(){if(e.readDOMTimeout=null,e.composing){if(!e.composing.done)return;e.composing=null}e.updateFromDOM()},80))},El.prototype.updateFromDOM=function(){var e=this;!this.cm.isReadOnly()&&this.pollContent()||qn(this.cm,function(){return un(e.cm)})},El.prototype.setUneditable=function(e){e.contentEditable="false"},El.prototype.onKeyPress=function(e){0==e.charCode||this.composing||(e.preventDefault(),this.cm.isReadOnly()||Zn(this.cm,Nl)(this.cm,String.fromCharCode(null==e.charCode?e.keyCode:e.charCode),0))},El.prototype.readOnlyChanged=function(e){this.div.contentEditable=String("nocursor"!=e)},El.prototype.onContextMenu=function(){},El.prototype.resetPosition=function(){},El.prototype.needsContentAttribute=!0;var Gl=function(e){this.cm=e,this.prevInput="",this.pollingFast=!1,this.polling=new R,this.hasSelection=!1,this.composing=null};Gl.prototype.init=function(e){var t=this,r=this,n=this.cm;this.createField(e);var i=this.textarea;function o(e){if(!ve(n,e)){if(n.somethingSelected())Ml({lineWise:!1,text:n.getSelections()});else{if(!n.options.lineWiseCopyCut)return;var t=Dl(n);Ml({lineWise:!0,text:t.text}),"cut"==e.type?n.setSelections(t.ranges,null,V):(r.prevInput="",i.value=t.text.join("\n"),P(i))}"cut"==e.type&&(n.state.cutIncoming=+new Date)}}e.wrapper.insertBefore(this.wrapper,e.wrapper.firstChild),g&&(i.style.width="0px"),fe(i,"input",function(){l&&s>=9&&t.hasSelection&&(t.hasSelection=null),r.poll()}),fe(i,"paste",function(e){ve(n,e)||Ol(e,n)||(n.state.pasteIncoming=+new Date,r.fastPoll())}),fe(i,"cut",o),fe(i,"copy",o),fe(e.scroller,"paste",function(t){if(!xr(e,t)&&!ve(n,t)){if(!i.dispatchEvent)return n.state.pasteIncoming=+new Date,void r.focus();var o=new Event("paste");o.clipboardData=t.clipboardData,i.dispatchEvent(o)}}),fe(e.lineSpace,"selectstart",function(t){xr(e,t)||we(t)}),fe(i,"compositionstart",function(){var e=n.getCursor("from");r.composing&&r.composing.range.clear(),r.composing={start:e,range:n.markText(e,n.getCursor("to"),{className:"CodeMirror-composing"})}}),fe(i,"compositionend",function(){r.composing&&(r.poll(),r.composing.range.clear(),r.composing=null)})},Gl.prototype.createField=function(e){this.wrapper=Hl(),this.textarea=this.wrapper.firstChild},Gl.prototype.prepareSelection=function(){var e=this.cm,t=e.display,r=e.doc,n=gn(e);if(e.options.moveInputWithCursor){var i=Xr(e,r.sel.primary().head,"div"),o=t.wrapper.getBoundingClientRect(),l=t.lineDiv.getBoundingClientRect();n.teTop=Math.max(0,Math.min(t.wrapper.clientHeight-10,i.top+l.top-o.top)),n.teLeft=Math.max(0,Math.min(t.wrapper.clientWidth-10,i.left+l.left-o.left))}return n},Gl.prototype.showSelection=function(e){var t=this.cm.display;N(t.cursorDiv,e.cursors),N(t.selectionDiv,e.selection),null!=e.teTop&&(this.wrapper.style.top=e.teTop+"px",this.wrapper.style.left=e.teLeft+"px")},Gl.prototype.reset=function(e){if(!this.contextMenuPending&&!this.composing){var t=this.cm;if(t.somethingSelected()){this.prevInput="";var r=t.getSelection();this.textarea.value=r,t.state.focused&&P(this.textarea),l&&s>=9&&(this.hasSelection=r)}else e||(this.prevInput=this.textarea.value="",l&&s>=9&&(this.hasSelection=null))}},Gl.prototype.getField=function(){return this.textarea},Gl.prototype.supportsTouch=function(){return!1},Gl.prototype.focus=function(){if("nocursor"!=this.cm.options.readOnly&&(!m||W()!=this.textarea))try{this.textarea.focus()}catch(e){}},Gl.prototype.blur=function(){this.textarea.blur()},Gl.prototype.resetPosition=function(){this.wrapper.style.top=this.wrapper.style.left=0},Gl.prototype.receivedFocus=function(){this.slowPoll()},Gl.prototype.slowPoll=function(){var e=this;this.pollingFast||this.polling.set(this.cm.options.pollInterval,function(){e.poll(),e.cm.state.focused&&e.slowPoll()})},Gl.prototype.fastPoll=function(){var e=!1,t=this;t.pollingFast=!0,t.polling.set(20,function r(){t.poll()||e?(t.pollingFast=!1,t.slowPoll()):(e=!0,t.polling.set(60,r))})},Gl.prototype.poll=function(){var e=this,t=this.cm,r=this.textarea,n=this.prevInput;if(this.contextMenuPending||!t.state.focused||He(r)&&!n&&!this.composing||t.isReadOnly()||t.options.disableInput||t.state.keySeq)return!1;var i=r.value;if(i==n&&!t.somethingSelected())return!1;if(l&&s>=9&&this.hasSelection===i||y&&/[\uf700-\uf7ff]/.test(i))return t.display.input.reset(),!1;if(t.doc.sel==t.display.selForContextMenu){var o=i.charCodeAt(0);if(8203!=o||n||(n="​"),8666==o)return this.reset(),this.cm.execCommand("undo")}for(var a=0,u=Math.min(n.length,i.length);a1e3||i.indexOf("\n")>-1?r.value=e.prevInput="":e.prevInput=i,e.composing&&(e.composing.range.clear(),e.composing.range=t.markText(e.composing.start,t.getCursor("to"),{className:"CodeMirror-composing"}))}),!0},Gl.prototype.ensurePolled=function(){this.pollingFast&&this.poll()&&(this.pollingFast=!1)},Gl.prototype.onKeyPress=function(){l&&s>=9&&(this.hasSelection=null),this.fastPoll()},Gl.prototype.onContextMenu=function(e){var t=this,r=t.cm,n=r.display,i=t.textarea;t.contextMenuPending&&t.contextMenuPending();var o=sn(r,e),u=n.scroller.scrollTop;if(o&&!h){r.options.resetSelectionOnContextMenu&&-1==r.doc.sel.contains(o)&&Zn(r,$i)(r.doc,xi(o),V);var c,f=i.style.cssText,d=t.wrapper.style.cssText,p=t.wrapper.offsetParent.getBoundingClientRect();if(t.wrapper.style.cssText="position: static",i.style.cssText="position: absolute; width: 30px; height: 30px;\n top: "+(e.clientY-p.top-5)+"px; left: "+(e.clientX-p.left-5)+"px;\n z-index: 1000; background: "+(l?"rgba(255, 255, 255, .05)":"transparent")+";\n outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);",a&&(c=window.scrollY),n.input.focus(),a&&window.scrollTo(null,c),n.input.reset(),r.somethingSelected()||(i.value=t.prevInput=" "),t.contextMenuPending=m,n.selForContextMenu=r.doc.sel,clearTimeout(n.detectingSelectAll),l&&s>=9&&v(),S){Se(e);var g=function(){pe(window,"mouseup",g),setTimeout(m,20)};fe(window,"mouseup",g)}else setTimeout(m,50)}function v(){if(null!=i.selectionStart){var e=r.somethingSelected(),o="​"+(e?i.value:"");i.value="⇚",i.value=o,t.prevInput=e?"":"​",i.selectionStart=1,i.selectionEnd=o.length,n.selForContextMenu=r.doc.sel}}function m(){if(t.contextMenuPending==m&&(t.contextMenuPending=!1,t.wrapper.style.cssText=d,i.style.cssText=f,l&&s<9&&n.scrollbars.setScrollTop(n.scroller.scrollTop=u),null!=i.selectionStart)){(!l||l&&s<9)&&v();var e=0,o=function(){n.selForContextMenu==r.doc.sel&&0==i.selectionStart&&i.selectionEnd>0&&"​"==t.prevInput?Zn(r,no)(r):e++<10?n.detectingSelectAll=setTimeout(o,500):(n.selForContextMenu=null,n.input.reset())};n.detectingSelectAll=setTimeout(o,200)}}},Gl.prototype.readOnlyChanged=function(e){e||this.reset(),this.textarea.disabled="nocursor"==e},Gl.prototype.setUneditable=function(){},Gl.prototype.needsContentAttribute=!1,function(e){var t=e.optionHandlers;function r(r,n,i,o){e.defaults[r]=n,i&&(t[r]=o?function(e,t,r){r!=yl&&i(e,t,r)}:i)}e.defineOption=r,e.Init=yl,r("value","",function(e,t){return e.setValue(t)},!0),r("mode",null,function(e,t){e.doc.modeOption=t,Ti(e)},!0),r("indentUnit",2,Ti,!0),r("indentWithTabs",!1),r("smartIndent",!0),r("tabSize",4,function(e){Mi(e),Rr(e),un(e)},!0),r("lineSeparator",null,function(e,t){if(e.doc.lineSep=t,t){var r=[],n=e.doc.first;e.doc.iter(function(e){for(var i=0;;){var o=e.text.indexOf(t,i);if(-1==o)break;i=o+t.length,r.push(et(n,o))}n++});for(var i=r.length-1;i>=0;i--)co(e.doc,t,r[i],et(r[i].line,r[i].ch+t.length))}}),r("specialChars",/[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff\ufff9-\ufffc]/g,function(e,t,r){e.state.specialChars=new RegExp(t.source+(t.test("\t")?"":"|\t"),"g"),r!=yl&&e.refresh()}),r("specialCharPlaceholder",Qt,function(e){return e.refresh()},!0),r("electricChars",!0),r("inputStyle",m?"contenteditable":"textarea",function(){throw new Error("inputStyle can not (yet) be changed in a running editor")},!0),r("spellcheck",!1,function(e,t){return e.getInputField().spellcheck=t},!0),r("autocorrect",!1,function(e,t){return e.getInputField().autocorrect=t},!0),r("autocapitalize",!1,function(e,t){return e.getInputField().autocapitalize=t},!0),r("rtlMoveVisually",!w),r("wholeLineUpdateBefore",!0),r("theme","default",function(e){ml(e),fi(e)},!0),r("keyMap","default",function(e,t,r){var n=Xo(t),i=r!=yl&&Xo(r);i&&i.detach&&i.detach(e,n),n.attach&&n.attach(e,i||null)}),r("extraKeys",null),r("configureMouse",null),r("lineWrapping",!1,Cl,!0),r("gutters",[],function(e,t){e.display.gutterSpecs=ci(t,e.options.lineNumbers),fi(e)},!0),r("fixedGutter",!0,function(e,t){e.display.gutters.style.left=t?nn(e.display)+"px":"0",e.refresh()},!0),r("coverGutterNextToScrollbar",!1,function(e){return Rn(e)},!0),r("scrollbarStyle","native",function(e){Un(e),Rn(e),e.display.scrollbars.setScrollTop(e.doc.scrollTop),e.display.scrollbars.setScrollLeft(e.doc.scrollLeft)},!0),r("lineNumbers",!1,function(e,t){e.display.gutterSpecs=ci(e.options.gutters,t),fi(e)},!0),r("firstLineNumber",1,fi,!0),r("lineNumberFormatter",function(e){return e},fi,!0),r("showCursorWhenSelecting",!1,pn,!0),r("resetSelectionOnContextMenu",!0),r("lineWiseCopyCut",!0),r("pasteLinesPerSelection",!0),r("selectionsMayTouch",!1),r("readOnly",!1,function(e,t){"nocursor"==t&&(Sn(e),e.display.input.blur()),e.display.input.readOnlyChanged(t)}),r("disableInput",!1,function(e,t){t||e.display.input.reset()},!0),r("dragDrop",!0,xl),r("allowDropFileTypes",null),r("cursorBlinkRate",530),r("cursorScrollMargin",0),r("cursorHeight",1,pn,!0),r("singleCursorHeightPerLine",!0,pn,!0),r("workTime",100),r("workDelay",100),r("flattenSpans",!0,Mi,!0),r("addModeClass",!1,Mi,!0),r("pollInterval",100),r("undoDepth",200,function(e,t){return e.doc.history.undoDepth=t}),r("historyEventDelay",1250),r("viewportMargin",10,function(e){return e.refresh()},!0),r("maxHighlightLength",1e4,Mi,!0),r("moveInputWithCursor",!0,function(e,t){t||e.display.input.resetPosition()}),r("tabindex",null,function(e,t){return e.display.input.getField().tabIndex=t||""}),r("autofocus",null),r("direction","ltr",function(e,t){return e.doc.setDirection(t)},!0),r("phrases",null)}(Sl),function(e){var t=e.optionHandlers,r=e.helpers={};e.prototype={constructor:e,focus:function(){window.focus(),this.display.input.focus()},setOption:function(e,r){var n=this.options,i=n[e];n[e]==r&&"mode"!=e||(n[e]=r,t.hasOwnProperty(e)&&Zn(this,t[e])(this,r,i),ge(this,"optionChange",this,e))},getOption:function(e){return this.options[e]},getDoc:function(){return this.doc},addKeyMap:function(e,t){this.state.keyMaps[t?"push":"unshift"](Xo(e))},removeKeyMap:function(e){for(var t=this.state.keyMaps,r=0;rr&&(kl(this,i.head.line,e,!0),r=i.head.line,n==this.doc.sel.primIndex&&On(this));else{var o=i.from(),l=i.to(),s=Math.max(r,o.line);r=Math.min(this.lastLine(),l.line-(l.ch?0:1))+1;for(var a=s;a0&&Xi(this.doc,n,new bi(o,u[n].to()),V)}}}),getTokenAt:function(e,t){return yt(this,e,t)},getLineTokens:function(e,t){return yt(this,et(e),t,!0)},getTokenTypeAt:function(e){e=st(this.doc,e);var t,r=ft(this,Xe(this.doc,e.line)),n=0,i=(r.length-1)/2,o=e.ch;if(0==o)t=r[2];else for(;;){var l=n+i>>1;if((l?r[2*l-1]:0)>=o)i=l;else{if(!(r[2*l+1]o&&(e=o,i=!0),n=Xe(this.doc,e)}else n=e;return Vr(this,n,{top:0,left:0},t||"page",r||i).top+(i?this.doc.height-Vt(n):0)},defaultTextHeight:function(){return en(this.display)},defaultCharWidth:function(){return tn(this.display)},getViewport:function(){return{from:this.display.viewFrom,to:this.display.viewTo}},addWidget:function(e,t,r,n,i){var o,l,s,a=this.display,u=(e=Xr(this,st(this.doc,e))).bottom,c=e.left;if(t.style.position="absolute",t.setAttribute("cm-ignore-events","true"),this.display.input.setUneditable(t),a.sizer.appendChild(t),"over"==n)u=e.top;else if("above"==n||"near"==n){var h=Math.max(a.wrapper.clientHeight,this.doc.height),f=Math.max(a.sizer.clientWidth,a.lineSpace.clientWidth);("above"==n||e.bottom+t.offsetHeight>h)&&e.top>t.offsetHeight?u=e.top-t.offsetHeight:e.bottom+t.offsetHeight<=h&&(u=e.bottom),c+t.offsetWidth>f&&(c=f-t.offsetWidth)}t.style.top=u+"px",t.style.left=t.style.right="","right"==i?(c=a.sizer.clientWidth-t.offsetWidth,t.style.right="0px"):("left"==i?c=0:"middle"==i&&(c=(a.sizer.clientWidth-t.offsetWidth)/2),t.style.left=c+"px"),r&&(o=this,l={left:c,top:u,right:c+t.offsetWidth,bottom:u+t.offsetHeight},null!=(s=Mn(o,l)).scrollTop&&Hn(o,s.scrollTop),null!=s.scrollLeft&&Pn(o,s.scrollLeft))},triggerOnKeyDown:Qn(ll),triggerOnKeyPress:Qn(al),triggerOnKeyUp:sl,triggerOnMouseDown:Qn(fl),execCommand:function(e){if(Zo.hasOwnProperty(e))return Zo[e].call(null,this)},triggerElectric:Qn(function(e){Al(this,e)}),findPosH:function(e,t,r,n){var i=1;t<0&&(i=-1,t=-t);for(var o=st(this.doc,e),l=0;l0&&l(t.charAt(r-1));)--r;for(;n.5)&&ln(this),ge(this,"refresh",this)}),swapDoc:Qn(function(e){var t=this.doc;return t.cm=null,this.state.selectingText&&this.state.selectingText(),Di(this,e),Rr(this),this.display.input.reset(),An(this,e.scrollLeft,e.scrollTop),this.curOp.forceScroll=!0,sr(this,"swapDoc",this,t),t}),phrase:function(e){var t=this.options.phrases;return t&&Object.prototype.hasOwnProperty.call(t,e)?t[e]:e},getInputField:function(){return this.display.input.getField()},getWrapperElement:function(){return this.display.wrapper},getScrollerElement:function(){return this.display.scroller},getGutterElement:function(){return this.display.gutters}},be(e),e.registerHelper=function(t,n,i){r.hasOwnProperty(t)||(r[t]=e[t]={_global:[]}),r[t][n]=i},e.registerGlobalHelper=function(t,n,i,o){e.registerHelper(t,n,o),r[t]._global.push({pred:i,val:o})}}(Sl);var Ul="iter insert remove copy getEditor constructor".split(" ");for(var Vl in Mo.prototype)Mo.prototype.hasOwnProperty(Vl)&&B(Ul,Vl)<0&&(Sl.prototype[Vl]=function(e){return function(){return e.apply(this.doc,arguments)}}(Mo.prototype[Vl]));return be(Mo),Sl.inputStyles={textarea:Gl,contenteditable:El},Sl.defineMode=function(e){Sl.defaults.mode||"null"==e||(Sl.defaults.mode=e),function(e,t){arguments.length>2&&(t.dependencies=Array.prototype.slice.call(arguments,2)),Ee[e]=t}.apply(this,arguments)},Sl.defineMIME=function(e,t){Ie[e]=t},Sl.defineMode("null",function(){return{token:function(e){return e.skipToEnd()}}}),Sl.defineMIME("text/plain","null"),Sl.defineExtension=function(e,t){Sl.prototype[e]=t},Sl.defineDocExtension=function(e,t){Mo.prototype[e]=t},Sl.fromTextArea=function(e,t){if((t=t?I(t):{}).value=e.value,!t.tabindex&&e.tabIndex&&(t.tabindex=e.tabIndex),!t.placeholder&&e.placeholder&&(t.placeholder=e.placeholder),null==t.autofocus){var r=W();t.autofocus=r==e||null!=e.getAttribute("autofocus")&&r==document.body}function n(){e.value=s.getValue()}var i;if(e.form&&(fe(e.form,"submit",n),!t.leaveSubmitMethodAlone)){var o=e.form;i=o.submit;try{var l=o.submit=function(){n(),o.submit=i,o.submit(),o.submit=l}}catch(e){}}t.finishInit=function(r){r.save=n,r.getTextArea=function(){return e},r.toTextArea=function(){r.toTextArea=isNaN,n(),e.parentNode.removeChild(r.getWrapperElement()),e.style.display="",e.form&&(pe(e.form,"submit",n),t.leaveSubmitMethodAlone||"function"!=typeof e.form.submit||(e.form.submit=i))}},e.style.display="none";var s=Sl(function(t){return e.parentNode.insertBefore(t,e.nextSibling)},t);return s},function(e){e.off=pe,e.on=fe,e.wheelEventPixels=vi,e.Doc=Mo,e.splitLines=We,e.countColumn=z,e.findColumn=X,e.isWordChar=ee,e.Pass=U,e.signal=ge,e.Line=Xt,e.changeEnd=Ci,e.scrollbarModel=Gn,e.Pos=et,e.cmpPos=tt,e.modes=Ee,e.mimeModes=Ie,e.resolveMode=ze,e.getMode=Re,e.modeExtensions=Be,e.extendMode=Ge,e.copyState=Ue,e.startState=Ke,e.innerMode=Ve,e.commands=Zo,e.keyMap=Ro,e.keyName=jo,e.isModifierKey=Vo,e.lookupKey=Uo,e.normalizeKeyMap=Go,e.StringStream=je,e.SharedTextMarker=So,e.TextMarker=xo,e.LineWidget=yo,e.e_preventDefault=we,e.e_stopPropagation=xe,e.e_stop=Se,e.addClass=H,e.contains=D,e.rmClass=T,e.keyNames=Po}(Sl),Sl.version="5.49.2",Sl}); \ No newline at end of file diff --git a/luci-app-adguardhome/root/www/luci-static/resources/codemirror/mode/yaml/yaml.js b/luci-app-adguardhome/root/www/luci-static/resources/codemirror/mode/yaml/yaml.js new file mode 100644 index 000000000..4a5e499bf --- /dev/null +++ b/luci-app-adguardhome/root/www/luci-static/resources/codemirror/mode/yaml/yaml.js @@ -0,0 +1 @@ +!function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(e){"use strict";e.defineMode("yaml",function(){var e=new RegExp("\\b(("+["true","false","on","off","yes","no"].join(")|(")+"))$","i");return{token:function(i,t){var r=i.peek(),n=t.escaped;if(t.escaped=!1,"#"==r&&(0==i.pos||/\s/.test(i.string.charAt(i.pos-1))))return i.skipToEnd(),"comment";if(i.match(/^('([^']|\\.)*'?|"([^"]|\\.)*"?)/))return"string";if(t.literal&&i.indentation()>t.keyCol)return i.skipToEnd(),"string";if(t.literal&&(t.literal=!1),i.sol()){if(t.keyCol=0,t.pair=!1,t.pairStart=!1,i.match(/---/))return"def";if(i.match(/\.\.\./))return"def";if(i.match(/\s*-\s+/))return"meta"}if(i.match(/^(\{|\}|\[|\])/))return"{"==r?t.inlinePairs++:"}"==r?t.inlinePairs--:"["==r?t.inlineList++:t.inlineList--,"meta";if(t.inlineList>0&&!n&&","==r)return i.next(),"meta";if(t.inlinePairs>0&&!n&&","==r)return t.keyCol=0,t.pair=!1,t.pairStart=!1,i.next(),"meta";if(t.pairStart){if(i.match(/^\s*(\||\>)\s*/))return t.literal=!0,"meta";if(i.match(/^\s*(\&|\*)[a-z0-9\._-]+\b/i))return"variable-2";if(0==t.inlinePairs&&i.match(/^\s*-?[0-9\.\,]+\s?$/))return"number";if(t.inlinePairs>0&&i.match(/^\s*-?[0-9\.\,]+\s?(?=(,|}))/))return"number";if(i.match(e))return"keyword"}return!t.pair&&i.match(/^\s*(?:[,\[\]{}&*!|>'"%@`][^\s'":]|[^,\[\]{}#&*!|>'"%@`])[^#]*?(?=\s*:($|\s))/)?(t.pair=!0,t.keyCol=i.indentation(),"atom"):t.pair&&i.match(/^:\s*/)?(t.pairStart=!0,"meta"):(t.pairStart=!1,t.escaped="\\"==r,i.next(),null)},startState:function(){return{pair:!1,pairStart:!1,keyCol:0,inlinePairs:0,inlineList:0,literal:!1,escaped:!1}},lineComment:"#",fold:"indent"}}),e.defineMIME("text/x-yaml","yaml"),e.defineMIME("text/yaml","yaml")}); \ No newline at end of file diff --git a/luci-app-adguardhome/root/www/luci-static/resources/codemirror/theme/dracula.css b/luci-app-adguardhome/root/www/luci-static/resources/codemirror/theme/dracula.css new file mode 100644 index 000000000..6c708c010 --- /dev/null +++ b/luci-app-adguardhome/root/www/luci-static/resources/codemirror/theme/dracula.css @@ -0,0 +1 @@ +.cm-s-dracula.CodeMirror,.cm-s-dracula .CodeMirror-gutters{background-color:#282a36 !important;color:#f8f8f2 !important;border:0}.cm-s-dracula .CodeMirror-gutters{color:#282a36}.cm-s-dracula .CodeMirror-cursor{border-left:solid thin #f8f8f0}.cm-s-dracula .CodeMirror-linenumber{color:#6d8a88}.cm-s-dracula .CodeMirror-selected{background:rgba(255,255,255,0.10)}.cm-s-dracula .CodeMirror-line::selection,.cm-s-dracula .CodeMirror-line>span::selection,.cm-s-dracula .CodeMirror-line>span>span::selection{background:rgba(255,255,255,0.10)}.cm-s-dracula .CodeMirror-line::-moz-selection,.cm-s-dracula .CodeMirror-line>span::-moz-selection,.cm-s-dracula .CodeMirror-line>span>span::-moz-selection{background:rgba(255,255,255,0.10)}.cm-s-dracula span.cm-comment{color:#6272a4}.cm-s-dracula span.cm-string,.cm-s-dracula span.cm-string-2{color:#f1fa8c}.cm-s-dracula span.cm-number{color:#bd93f9}.cm-s-dracula span.cm-variable{color:#50fa7b}.cm-s-dracula span.cm-variable-2{color:white}.cm-s-dracula span.cm-def{color:#50fa7b}.cm-s-dracula span.cm-operator{color:#ff79c6}.cm-s-dracula span.cm-keyword{color:#ff79c6}.cm-s-dracula span.cm-atom{color:#bd93f9}.cm-s-dracula span.cm-meta{color:#f8f8f2}.cm-s-dracula span.cm-tag{color:#ff79c6}.cm-s-dracula span.cm-attribute{color:#50fa7b}.cm-s-dracula span.cm-qualifier{color:#50fa7b}.cm-s-dracula span.cm-property{color:#66d9ef}.cm-s-dracula span.cm-builtin{color:#50fa7b}.cm-s-dracula span.cm-variable-3,.cm-s-dracula span.cm-type{color:#ffb86c}.cm-s-dracula .CodeMirror-activeline-background{background:rgba(255,255,255,0.1)}.cm-s-dracula .CodeMirror-matchingbracket{text-decoration:underline;color:white !important} diff --git a/luci-app-adguardhome/root/www/luci-static/resources/twin-bcrypt.min.js b/luci-app-adguardhome/root/www/luci-static/resources/twin-bcrypt.min.js new file mode 100644 index 000000000..6284357c6 --- /dev/null +++ b/luci-app-adguardhome/root/www/luci-static/resources/twin-bcrypt.min.js @@ -0,0 +1,7 @@ +/* @license + * Twin-Bcrypt 2.2.0 + * https://github.com/fpirsch/twin-bcrypt + * Licence: BSD-3-Clause + */ +!function(r,n){"use strict";function e(r){return y[g]=t.apply(n,r),g++}function t(r){var e=[].slice.call(arguments,1);return function(){"function"==typeof r?r.apply(n,e):new Function(""+r)()}}function o(r){if(m)setTimeout(t(o,r),0);else{var n=y[r];if(n){m=!0;try{n()}finally{a(r),m=!1}}}}function a(r){delete y[r]}function i(){p=function(){var r=e(arguments);return process.nextTick(t(o,r)),r}}function u(){if(r.postMessage&&!r.importScripts){var n=!0,e=r.onmessage;return r.onmessage=function(){n=!1},r.postMessage("","*"),r.onmessage=e,n}}function f(){var n="setImmediate$"+Math.random()+"$",t=function(e){e.source===r&&"string"==typeof e.data&&0===e.data.indexOf(n)&&o(+e.data.slice(n.length))};r.addEventListener?r.addEventListener("message",t,!1):r.attachEvent("onmessage",t),p=function(){var t=e(arguments);return r.postMessage(n+t,"*"),t}}function c(){var r=new MessageChannel;r.port1.onmessage=function(r){var n=r.data;o(n)},p=function(){var n=e(arguments);return r.port2.postMessage(n),n}}function s(){var r=v.documentElement;p=function(){var n=e(arguments),t=v.createElement("script");return t.onreadystatechange=function(){o(n),t.onreadystatechange=null,r.removeChild(t),t=null},r.appendChild(t),n}}function l(){p=function(){var r=e(arguments);return setTimeout(t(o,r),0),r}}if(!r.setImmediate){var p,g=1,y={},m=!1,v=r.document,d=Object.getPrototypeOf&&Object.getPrototypeOf(r);d=d&&d.setTimeout?d:r,"[object process]"==={}.toString.call(r.process)?i():u()?f():r.MessageChannel?c():v&&"onreadystatechange"in v.createElement("script")?s():l(),d.setImmediate=p,d.clearImmediate=a}}(new Function("return this")()),function(r){"object"==typeof exports?r(exports,require("crypto")):r(self.TwinBcrypt={},self.crypto||self.msCrypto)}(function(r,n){"use strict";function e(r){for(var n=unescape(encodeURIComponent(r)),e=n.length,t=new Array(e),o=0;e>o;o++)t[o]=n.charCodeAt(o);return t}function t(r){for(var n=r.length,e=new Array(n),t=0;n>t;t++)e[t]=r.charCodeAt(t);return e}function o(r,n){for(var e,t,o=0,a="";n>o;){if(e=255&r[o++],a+=B[e>>2],e=(3&e)<<4,o>=n){a+=B[e];break}if(t=255&r[o++],e|=t>>4,a+=B[e],e=(15&t)<<2,o>=n){a+=B[e];break}t=255&r[o++],e|=t>>6,a+=B[e],a+=B[63&t]}return a}function a(r){for(var n,e,t=new Array(16),o=0,a=0;;){if(n=D[r.charCodeAt(o++)-46],e=D[r.charCodeAt(o++)-46],t[a++]=255&(n<<2|e>>4),22===o)break;n=e<<4,e=D[r.charCodeAt(o++)-46],t[a++]=255&(n|e>>2),n=e<<6,e=D[r.charCodeAt(o++)-46],t[a++]=255&(n|e)}return t}function i(r){for(var n=r.length,e=new Array(72),t=0,o=0;72>o;)e[o++]=r[t++],t===n&&(t=0);return e}function u(r,n,e){for(var t=0,o=e>>2;tt;)r[n++]=e[t++]<<24|e[t++]<<16|e[t++]<<8|e[t++]}function c(r){function n(n){for(var e=r,t=G>>2,o=t|O,f=n>>2,c=e[f]^e[t],s=e[1|f];o>t;)s^=(e[c>>>24]+e[a|c>>>16&255]^e[i|c>>>8&255])+e[u|255&c]^e[++t],c^=(e[s>>>24]+e[a|s>>>16&255]^e[i|s>>>8&255])+e[u|255&s]^e[++t];e[f]=s^e[S>>2],e[1|f]=c}function e(n){var e;for(r[L>>2]=0,r[L+4>>2]=0,e=0;M>e;e++)r[G>>2|e]^=r[(n>>2)+e];var t,o,f,c,s,l=r;for(e=0;M>e;e+=2){for(t=G>>2,o=t|O,f=L>>2,c=l[f]^l[t],s=l[1|f];o>t;)s^=(l[c>>>24]+l[a|c>>>16&255]^l[i|c>>>8&255])+l[u|255&c]^l[++t],c^=(l[s>>>24]+l[a|s>>>16&255]^l[i|s>>>8&255])+l[u|255&s]^l[++t];l[f]=s^l[S>>2],l[1|f]=c,r[G>>2|e]=l[f],r[G>>2|e+1]=c}for(e=0;T>e;e+=2){for(t=G>>2,o=t|O,f=L>>2,c=l[f]^l[t],s=l[1|f];o>t;)s^=(l[c>>>24]+l[a|c>>>16&255]^l[i|c>>>8&255])+l[u|255&c]^l[++t],c^=(l[s>>>24]+l[a|s>>>16&255]^l[i|s>>>8&255])+l[u|255&s]^l[++t];l[f]=s^l[S>>2],l[1|f]=c,r[e]=l[f],r[1|e]=c}}function t(r,n,t){for(var o=0;t>=o&&!(r>n);o++)e(R),e(j),r++;return r}var o=k>>2,a=o+256|0,i=a+256|0,u=i+256|0;return{encrypt:n,expandLoop:t}}function s(stdlib, foreign, heap) {"use asm";var HEAP32=new stdlib.Uint32Array(heap);var BLOWFISH_NUM_ROUNDS=16;var S_offset=0x0000;var S1_offset=0x0400;var S2_offset=0x0800;var S3_offset=0x0C00;var P_offset=0x1000;var P_last_offset=0x1044;var crypt_ciphertext_offset=0x1048;var LR_offset=0x01060;var password_offset=0x1068;var salt_offset=0x10b0;var P_LEN=18;var S_LEN=1024;function encrypt(offset) {offset=offset|0;var i=0;var n=0;var L=0;var R=0;var imax=0;imax=P_offset|BLOWFISH_NUM_ROUNDS<<2;L=HEAP32[offset>>2]|0;R=HEAP32[offset+4>>2]|0;L=L^HEAP32[P_offset>>2];for (i=P_offset; (i|0)<(imax|0);) {i=(i+4)>>>0;R=R^(((HEAP32[(L>>>22)>>2]>>>0) +(HEAP32[(S1_offset|(L>>>14&0x3ff))>>2]>>>0) ^(HEAP32[(S2_offset|(L>>>6&0x3ff))>>2])) +(HEAP32[(S3_offset|(L<<2&0x3ff))>>2]>>>0))^HEAP32[i>>2];i=(i+4)>>>0;L=L^(((HEAP32[(R>>>22)>>2]>>>0) +(HEAP32[(S1_offset|(R>>>14&0x3ff))>>2]>>>0) ^(HEAP32[(S2_offset|(R>>>6&0x3ff))>>2])) +(HEAP32[(S3_offset|(R<<2&0x3ff))>>2]>>>0))^HEAP32[i>>2];}HEAP32[offset>>2]=R^HEAP32[P_last_offset>>2];HEAP32[(offset+4)>>2]=L;}function expandKey(offset) {offset=offset|0;var i=0;var off=0;off=P_offset|0;for (i=0; (i|0)<(P_LEN|0); i=(i+1)|0) {HEAP32[off>>2]=HEAP32[off>>2]^HEAP32[offset>>2];offset=(offset+4)|0;off=(off+4)|0;}HEAP32[LR_offset>>2]=0;HEAP32[LR_offset+4>>2]=0;off=P_offset;for (i=0; (i|0)<(P_LEN|0); i=(i+2)|0) {encrypt(LR_offset);HEAP32[off>>2]=HEAP32[LR_offset>>2];HEAP32[off+4>>2]=HEAP32[LR_offset+4>>2];off=(off+8)|0;}off=S_offset;for (i=0; (i|0)<(S_LEN|0); i=(i+2)|0) {encrypt(LR_offset);HEAP32[off>>2]=HEAP32[LR_offset>>2];HEAP32[off+4>>2]=HEAP32[LR_offset+4>>2];off=(off+8)|0;}}function expandLoop(i, counterEnd, maxIterations) {i=i|0;counterEnd=counterEnd|0;maxIterations=maxIterations|0;var j=0;for (j=0; (j|0) <= (maxIterations|0); j=(j+1)|0) {if ((i>>>0)>(counterEnd>>>0)) break;expandKey(password_offset);expandKey(salt_offset);i=(i+1)>>>0;}return i|0;}return {encrypt: encrypt,expandLoop: expandLoop};} +function l(r,n,e,t){var o,a,i,u=L>>2,f=u+1;for(t[u]=0,t[f]=0,a=0,o=0;M>o;o++)i=n[a++]<<24|n[a++]<<16|n[a++]<<8|n[a++],t[G>>2|o]^=i;for(a=0,o=0;M>o;o+=2)i=r[a++]<<24|r[a++]<<16|r[a++]<<8|r[a++],a&=65295,t[u]^=i,i=r[a++]<<24|r[a++]<<16|r[a++]<<8|r[a++],a&=65295,t[f]^=i,e.encrypt(L),t[G>>2|o]=t[u],t[G>>2|o+1]=t[f];var c=k>>2;for(o=0;T>o;o+=2)i=r[a++]<<24|r[a++]<<16|r[a++]<<8|r[a++],a&=65295,t[u]^=i,i=r[a++]<<24|r[a++]<<16|r[a++]<<8|r[a++],a&=65295,t[f]^=i,e.encrypt(L),t[c|o]=t[u],t[c|o+1]=t[f]}function p(r,n,e,t,o,a,i){for(var u=e;t>=u;){if(u=r.expandLoop(u,t,o),a){var f=a(u/(t+1));if(f===!1)return}if(u>t){if(i)return void setImmediate(g.bind(null,r,n,i));return}if(i)return void setImmediate(p.bind(null,r,n,u,t,o,a,i))}}function g(r,n,e){u(n,x,F);var t;for(t=0;64>t;t++)r.encrypt(F+0),r.encrypt(F+8),r.encrypt(F+16);var o,a=0,i=x.length,f=new Array(4*i);for(t=0;i>t;t++)o=n[(F>>2)+t],f[a++]=o>>24,f[a++]=o>>16&255,f[a++]=o>>8&255,f[a++]=255&o;return e&&e(f),f}function y(r,n){return r+o(n,23)}function m(n,o,m,v){var d,h=o.substr(0,29),w=+o.substr(4,2),A=o.substr(7,22);if("string"==typeof n)d=r.encodingMode===r.ENCODING_UTF8?e(n):t(n);else if(Array.isArray(n))d=n.map(function(r){return 255&r});else{if(!(n instanceof Uint8Array))throw new Error("Incorrect arguments");d=Array.prototype.slice.call(n)}d.push(0);var b,E,N=a(A,C),O=31>w?1<>2,N),f(b,R>>2,d),l(N,d,E,b),v?void p(E,b,0,M,T,m,function(r){v(y(h,r))}):(p(E,b,0,M,T,m),y(h,g(E,b)))}function v(r){if(!b)throw new Error("No cryptographically secure pseudorandom number generator available.");if(null==r&&(r=N),r=0|+r,isNaN(r)||4>r||r>31)throw new Error("Invalid cost parameter.");var n="$2y$";return 10>r&&(n+="0"),n+=r+"$",n+=o(b(C),C)}function d(r,n,e){if(n&&"number"!=typeof n){if("string"!=typeof n||!z.test(n))throw new Error("Invalid salt")}else n=v(n);return m(r,n,e)}function h(r,n,e,t){if(arguments.length<2)throw new Error("Incorrect arguments");if(2===arguments.length?(t=n,n=e=null):3===arguments.length&&(t=e,e=null,"function"==typeof n&&(e=n,n=null)),n&&"number"!=typeof n){if("string"!=typeof n||!z.test(n))throw new Error("Invalid salt")}else n=v(n);if(!t||"function"!=typeof t)throw new Error("No callback function was given.");m(r,n,e,t)}function w(r,n){if("string"!=typeof n||!Z.test(n))throw new Error("Incorrect arguments");var e=n.substr(0,n.length-31),t=d(r,e);return t===n}function A(r,n,e,t){if("string"!=typeof n||!Z.test(n))throw new Error("Incorrect arguments");if(t||(t=e,e=null),!t||"function"!=typeof t)throw new Error("No callback function was given.");var o=n.substr(0,n.length-31);h(r,o,e,function(r){t(r===n)})}var b,E="undefined"!=typeof InstallTrigger,I=E;n&&(b=n.randomBytes,n.getRandomValues&&(b=function(r){var e=new Uint8Array(r);return n.getRandomValues(e)}));var C=16,N=10,O=16,$=[608135816,2242054355,320440878,57701188,2752067618,698298832,137296536,3964562569,1160258022,953160567,3193202383,887688300,3232508343,3380367581,1065670069,3041331479,2450970073,2306472731],U=[3509652390,2564797868,805139163,3491422135,3101798381,1780907670,3128725573,4046225305,614570311,3012652279,134345442,2240740374,1667834072,1901547113,2757295779,4103290238,227898511,1921955416,1904987480,2182433518,2069144605,3260701109,2620446009,720527379,3318853667,677414384,3393288472,3101374703,2390351024,1614419982,1822297739,2954791486,3608508353,3174124327,2024746970,1432378464,3864339955,2857741204,1464375394,1676153920,1439316330,715854006,3033291828,289532110,2706671279,2087905683,3018724369,1668267050,732546397,1947742710,3462151702,2609353502,2950085171,1814351708,2050118529,680887927,999245976,1800124847,3300911131,1713906067,1641548236,4213287313,1216130144,1575780402,4018429277,3917837745,3693486850,3949271944,596196993,3549867205,258830323,2213823033,772490370,2760122372,1774776394,2652871518,566650946,4142492826,1728879713,2882767088,1783734482,3629395816,2517608232,2874225571,1861159788,326777828,3124490320,2130389656,2716951837,967770486,1724537150,2185432712,2364442137,1164943284,2105845187,998989502,3765401048,2244026483,1075463327,1455516326,1322494562,910128902,469688178,1117454909,936433444,3490320968,3675253459,1240580251,122909385,2157517691,634681816,4142456567,3825094682,3061402683,2540495037,79693498,3249098678,1084186820,1583128258,426386531,1761308591,1047286709,322548459,995290223,1845252383,2603652396,3431023940,2942221577,3202600964,3727903485,1712269319,422464435,3234572375,1170764815,3523960633,3117677531,1434042557,442511882,3600875718,1076654713,1738483198,4213154764,2393238008,3677496056,1014306527,4251020053,793779912,2902807211,842905082,4246964064,1395751752,1040244610,2656851899,3396308128,445077038,3742853595,3577915638,679411651,2892444358,2354009459,1767581616,3150600392,3791627101,3102740896,284835224,4246832056,1258075500,768725851,2589189241,3069724005,3532540348,1274779536,3789419226,2764799539,1660621633,3471099624,4011903706,913787905,3497959166,737222580,2514213453,2928710040,3937242737,1804850592,3499020752,2949064160,2386320175,2390070455,2415321851,4061277028,2290661394,2416832540,1336762016,1754252060,3520065937,3014181293,791618072,3188594551,3933548030,2332172193,3852520463,3043980520,413987798,3465142937,3030929376,4245938359,2093235073,3534596313,375366246,2157278981,2479649556,555357303,3870105701,2008414854,3344188149,4221384143,3956125452,2067696032,3594591187,2921233993,2428461,544322398,577241275,1471733935,610547355,4027169054,1432588573,1507829418,2025931657,3646575487,545086370,48609733,2200306550,1653985193,298326376,1316178497,3007786442,2064951626,458293330,2589141269,3591329599,3164325604,727753846,2179363840,146436021,1461446943,4069977195,705550613,3059967265,3887724982,4281599278,3313849956,1404054877,2845806497,146425753,1854211946,1266315497,3048417604,3681880366,3289982499,290971e4,1235738493,2632868024,2414719590,3970600049,1771706367,1449415276,3266420449,422970021,1963543593,2690192192,3826793022,1062508698,1531092325,1804592342,2583117782,2714934279,4024971509,1294809318,4028980673,1289560198,2221992742,1669523910,35572830,157838143,1052438473,1016535060,1802137761,1753167236,1386275462,3080475397,2857371447,1040679964,2145300060,2390574316,1461121720,2956646967,4031777805,4028374788,33600511,2920084762,1018524850,629373528,3691585981,3515945977,2091462646,2486323059,586499841,988145025,935516892,3367335476,2599673255,2839830854,265290510,3972581182,2759138881,3795373465,1005194799,847297441,406762289,1314163512,1332590856,1866599683,4127851711,750260880,613907577,1450815602,3165620655,3734664991,3650291728,3012275730,3704569646,1427272223,778793252,1343938022,2676280711,2052605720,1946737175,3164576444,3914038668,3967478842,3682934266,1661551462,3294938066,4011595847,840292616,3712170807,616741398,312560963,711312465,1351876610,322626781,1910503582,271666773,2175563734,1594956187,70604529,3617834859,1007753275,1495573769,4069517037,2549218298,2663038764,504708206,2263041392,3941167025,2249088522,1514023603,1998579484,1312622330,694541497,2582060303,2151582166,1382467621,776784248,2618340202,3323268794,2497899128,2784771155,503983604,4076293799,907881277,423175695,432175456,1378068232,4145222326,3954048622,3938656102,3820766613,2793130115,2977904593,26017576,3274890735,3194772133,1700274565,1756076034,4006520079,3677328699,720338349,1533947780,354530856,688349552,3973924725,1637815568,332179504,3949051286,53804574,2852348879,3044236432,1282449977,3583942155,3416972820,4006381244,1617046695,2628476075,3002303598,1686838959,431878346,2686675385,1700445008,1080580658,1009431731,832498133,3223435511,2605976345,2271191193,2516031870,1648197032,4164389018,2548247927,300782431,375919233,238389289,3353747414,2531188641,2019080857,1475708069,455242339,2609103871,448939670,3451063019,1395535956,2413381860,1841049896,1491858159,885456874,4264095073,4001119347,1565136089,3898914787,1108368660,540939232,1173283510,2745871338,3681308437,4207628240,3343053890,4016749493,1699691293,1103962373,3625875870,2256883143,3830138730,1031889488,3479347698,1535977030,4236805024,3251091107,2132092099,1774941330,1199868427,1452454533,157007616,2904115357,342012276,595725824,1480756522,206960106,497939518,591360097,863170706,2375253569,3596610801,1814182875,2094937945,3421402208,1082520231,3463918190,2785509508,435703966,3908032597,1641649973,2842273706,3305899714,1510255612,2148256476,2655287854,3276092548,4258621189,236887753,3681803219,274041037,1734335097,3815195456,3317970021,1899903192,1026095262,4050517792,356393447,2410691914,3873677099,3682840055,3913112168,2491498743,4132185628,2489919796,1091903735,1979897079,3170134830,3567386728,3557303409,857797738,1136121015,1342202287,507115054,2535736646,337727348,3213592640,1301675037,2528481711,1895095763,1721773893,3216771564,62756741,2142006736,835421444,2531993523,1442658625,3659876326,2882144922,676362277,1392781812,170690266,3921047035,1759253602,3611846912,1745797284,664899054,1329594018,3901205900,3045908486,2062866102,2865634940,3543621612,3464012697,1080764994,553557557,3656615353,3996768171,991055499,499776247,1265440854,648242737,3940784050,980351604,3713745714,1749149687,3396870395,4211799374,3640570775,1161844396,3125318951,1431517754,545492359,4268468663,3499529547,1437099964,2702547544,3433638243,2581715763,2787789398,1060185593,1593081372,2418618748,4260947970,69676912,2159744348,86519011,2512459080,3838209314,1220612927,3339683548,133810670,1090789135,1078426020,1569222167,845107691,3583754449,4072456591,1091646820,628848692,1613405280,3757631651,526609435,236106946,48312990,2942717905,3402727701,1797494240,859738849,992217954,4005476642,2243076622,3870952857,3732016268,765654824,3490871365,2511836413,1685915746,3888969200,1414112111,2273134842,3281911079,4080962846,172450625,2569994100,980381355,4109958455,2819808352,2716589560,2568741196,3681446669,3329971472,1835478071,660984891,3704678404,4045999559,3422617507,3040415634,1762651403,1719377915,3470491036,2693910283,3642056355,3138596744,1364962596,2073328063,1983633131,926494387,3423689081,2150032023,4096667949,1749200295,3328846651,309677260,2016342300,1779581495,3079819751,111262694,1274766160,443224088,298511866,1025883608,3806446537,1145181785,168956806,3641502830,3584813610,1689216846,3666258015,3200248200,1692713982,2646376535,4042768518,1618508792,1610833997,3523052358,4130873264,2001055236,3610705100,2202168115,4028541809,2961195399,1006657119,2006996926,3186142756,1430667929,3210227297,1314452623,4074634658,4101304120,2273951170,1399257539,3367210612,3027628629,1190975929,2062231137,2333990788,2221543033,2438960610,1181637006,548689776,2362791313,3372408396,3104550113,3145860560,296247880,1970579870,3078560182,3769228297,1714227617,3291629107,3898220290,166772364,1251581989,493813264,448347421,195405023,2709975567,677966185,3703036547,1463355134,2715995803,1338867538,1343315457,2802222074,2684532164,233230375,2599980071,2000651841,3277868038,1638401717,4028070440,3237316320,6314154,819756386,300326615,590932579,1405279636,3267499572,3150704214,2428286686,3959192993,3461946742,1862657033,1266418056,963775037,2089974820,2263052895,1917689273,448879540,3550394620,3981727096,150775221,3627908307,1303187396,508620638,2975983352,2726630617,1817252668,1876281319,1457606340,908771278,3720792119,3617206836,2455994898,1729034894,1080033504,976866871,3556439503,2881648439,1522871579,1555064734,1336096578,3548522304,2579274686,3574697629,3205460757,3593280638,3338716283,3079412587,564236357,2993598910,1781952180,1464380207,3163844217,3332601554,1699332808,1393555694,1183702653,3581086237,1288719814,691649499,2847557200,2895455976,3193889540,2717570544,1781354906,1676643554,2592534050,3230253752,1126444790,2770207658,2633158820,2210423226,2615765581,2414155088,3127139286,673620729,2805611233,1269405062,4015350505,3341807571,4149409754,1057255273,2012875353,2162469141,2276492801,2601117357,993977747,3918593370,2654263191,753973209,36408145,2530585658,25011837,3520020182,2088578344,530523599,2918365339,1524020338,1518925132,3760827505,3759777254,1202760957,3985898139,3906192525,674977740,4174734889,2031300136,2019492241,3983892565,4153806404,3822280332,352677332,2297720250,60907813,90501309,3286998549,1016092578,2535922412,2839152426,457141659,509813237,4120667899,652014361,1966332200,2975202805,55981186,2327461051,676427537,3255491064,2882294119,3433927263,1307055953,942726286,933058658,2468411793,3933900994,4215176142,1361170020,2001714738,2830558078,3274259782,1222529897,1679025792,2729314320,3714953764,1770335741,151462246,3013232138,1682292957,1483529935,471910574,1539241949,458788160,3436315007,1807016891,3718408830,978976581,1043663428,3165965781,1927990952,4200891579,2372276910,3208408903,3533431907,1412390302,2931980059,4132332400,1947078029,3881505623,4168226417,2941484381,1077988104,1320477388,886195818,18198404,3786409e3,2509781533,112762804,3463356488,1866414978,891333506,18488651,661792760,1628790961,3885187036,3141171499,876946877,2693282273,1372485963,791857591,2686433993,3759982718,3167212022,3472953795,2716379847,445679433,3561995674,3504004811,3574258232,54117162,3331405415,2381918588,3769707343,4154350007,1140177722,4074052095,668550556,3214352940,367459370,261225585,2610173221,4209349473,3468074219,3265815641,314222801,3066103646,3808782860,282218597,3406013506,3773591054,379116347,1285071038,846784868,2669647154,3771962079,3550491691,2305946142,453669953,1268987020,3317592352,3279303384,3744833421,2610507566,3859509063,266596637,3847019092,517658769,3462560207,3443424879,370717030,4247526661,2224018117,4143653529,4112773975,2788324899,2477274417,1456262402,2901442914,1517677493,1846949527,2295493580,3734397586,2176403920,1280348187,1908823572,3871786941,846861322,1172426758,3287448474,3383383037,1655181056,3139813346,901632758,1897031941,2986607138,3066810236,3447102507,1393639104,373351379,950779232,625454576,3124240540,4148612726,2007998917,544563296,2244738638,2330496472,2058025392,1291430526,424198748,50039436,29584100,3605783033,2429876329,2791104160,1057563949,3255363231,3075367218,3463963227,1469046755,985887462],M=$.length,T=U.length,x=[1332899944,1700884034,1701343084,1684370003,1668446532,1869963892],k=0,G=4096,S=4164,F=4168,L=4192,R=4200,j=4272,B="./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",D=[0,1,54,55,56,57,58,59,60,61,62,63,-1,-1,-1,-1,-1,-1,-1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,-1,-1,-1,-1,-1,-1,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,-1,-1,-1,-1,-1],z=/^\$2[ay]\$(0[4-9]|[12][0-9]|3[01])\$[.\/A-Za-z0-9]{21}[.Oeu]/,Z=/^\$2[ay]\$(0[4-9]|[12][0-9]|3[01])\$[.\/A-Za-z0-9]{21}[.Oeu][.\/A-Za-z0-9]{30}[.CGKOSWaeimquy26]$/;r.genSalt=v,r.hashSync=d,r.hash=h,r.compareSync=w,r.compare=A,r.ENCODING_UTF8=0,r.ENCODING_RAW=1,r.encodingMode=r.ENCODING_UTF8,r.cryptoRNG=!!b,r.randomBytes=b,r.defaultCost=N,r.version="2.2.0"}); \ No newline at end of file diff --git a/luci-app-cpufreq/Makefile b/luci-app-cpufreq/Makefile new file mode 100644 index 000000000..b85e91d22 --- /dev/null +++ b/luci-app-cpufreq/Makefile @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-3.0-only +# +# Copyright (C) 2021 ImmortalWrt.org + +include $(TOPDIR)/rules.mk + +LUCI_TITLE:=LuCI for CPU Freq Setting +LUCI_DEPENDS:=@(arm||aarch64) + +PKG_NAME:=luci-app-cpufreq +PKG_VERSION:=1 +PKG_RELEASE:=$(COMMITCOUNT) + +include $(TOPDIR)/feeds/luci/luci.mk + +# call BuildPackage - OpenWrt buildroot signature diff --git a/luci-app-cpufreq/luasrc/controller/cpufreq.lua b/luci-app-cpufreq/luasrc/controller/cpufreq.lua new file mode 100644 index 000000000..2bf7a5686 --- /dev/null +++ b/luci-app-cpufreq/luasrc/controller/cpufreq.lua @@ -0,0 +1,11 @@ +module("luci.controller.cpufreq", package.seeall) + +function index() + if not nixio.fs.access("/etc/config/cpufreq") then + return + end + + local page = entry({"admin", "system", "cpufreq"}, cbi("cpufreq"), _("CPU Freq"), 90) + page.dependent = false + page.acl_depends = { "luci-app-cpufreq" } +end diff --git a/luci-app-cpufreq/luasrc/model/cbi/cpufreq.lua b/luci-app-cpufreq/luasrc/model/cbi/cpufreq.lua new file mode 100644 index 000000000..febb7ad90 --- /dev/null +++ b/luci-app-cpufreq/luasrc/model/cbi/cpufreq.lua @@ -0,0 +1,68 @@ +local fs = require "nixio.fs" + +function string.split(input, delimiter) + input = tostring(input) + delimiter = tostring(delimiter) + if (delimiter=='') then return false end + local pos,arr = 0, {} + for st,sp in function() return string.find(input, delimiter, pos, true) end do + table.insert(arr, string.sub(input, pos, st - 1)) + pos = sp + 1 + end + table.insert(arr, string.sub(input, pos)) + return arr +end + +mp = Map("cpufreq", translate("CPU Freq Settings")) +mp.description = translate("Set CPU Scaling Governor to Max Performance or Balance Mode") + +s = mp:section(NamedSection, "cpufreq", "settings") +s.anonymouse = true + +local policy_nums = luci.sys.exec("echo -n $(find /sys/devices/system/cpu/cpufreq/policy* -maxdepth 0 | grep -Eo '[0-9]+')") +for _, policy_num in ipairs(string.split(policy_nums, " ")) do + if not fs.access("/sys/devices/system/cpu/cpufreq/policy" .. policy_num .. "/scaling_available_frequencies") then return end + + cpu_freqs = fs.readfile("/sys/devices/system/cpu/cpufreq/policy" .. policy_num .. "/scaling_available_frequencies") + cpu_freqs = string.sub(cpu_freqs, 1, -3) + + cpu_governors = fs.readfile("/sys/devices/system/cpu/cpufreq/policy" .. policy_num .. "/scaling_available_governors") + cpu_governors = string.sub(cpu_governors, 1, -3) + + + freq_array = string.split(cpu_freqs, " ") + governor_array = string.split(cpu_governors, " ") + + s:tab(policy_num, translate("Policy " .. policy_num)) + + governor = s:taboption(policy_num, ListValue, "governor" .. policy_num, translate("CPU Scaling Governor")) + for _, e in ipairs(governor_array) do + if e ~= "" then governor:value(e, translate(e, string.upper(e))) end + end + + minfreq = s:taboption(policy_num, ListValue, "minfreq" .. policy_num, translate("Min Idle CPU Freq")) + for _, e in ipairs(freq_array) do + if e ~= "" then minfreq:value(e) end + end + + maxfreq = s:taboption(policy_num, ListValue, "maxfreq" .. policy_num, translate("Max Turbo Boost CPU Freq")) + for _, e in ipairs(freq_array) do + if e ~= "" then maxfreq:value(e) end + end + + sdfactor = s:taboption(policy_num, Value, "sdfactor" .. policy_num, translate("CPU Switching Sampling rate")) + sdfactor.datatype="range(1,100000)" + sdfactor.description = translate("The sampling rate determines how frequently the governor checks to tune the CPU (ms)") + sdfactor.placeholder = 10 + sdfactor.default = 10 + sdfactor:depends("governor" .. policy_num, "ondemand") + + upthreshold = s:taboption(policy_num, Value, "upthreshold" .. policy_num, translate("CPU Switching Threshold")) + upthreshold.datatype="range(1,99)" + upthreshold.description = translate("Kernel make a decision on whether it should increase the frequency (%)") + upthreshold.placeholder = 50 + upthreshold.default = 50 + upthreshold:depends("governor" .. policy_num, "ondemand") +end + +return mp diff --git a/luci-app-cpufreq/po/zh-cn/cpufreq.po b/luci-app-cpufreq/po/zh-cn/cpufreq.po new file mode 100644 index 000000000..bd818d774 --- /dev/null +++ b/luci-app-cpufreq/po/zh-cn/cpufreq.po @@ -0,0 +1,32 @@ +msgid "CPU Freq" +msgstr "CPU 性能优化调节" + +msgid "CPU Freq Settings" +msgstr "CPU 性能优化调节设置" + +msgid "Set CPU Scaling Governor to Max Performance or Balance Mode" +msgstr "设置路由器的 CPU 性能模式(高性能/均衡省电)" + +msgid "CPU Scaling Governor" +msgstr "CPU 工作模式" + +msgid "CPU Freq from 48000 to 716000 (Khz)" +msgstr "CPU 频率范围为 48000 到 716000 (Khz)" + +msgid "Min Idle CPU Freq" +msgstr "待机 CPU 最小频率" + +msgid "Max Turbo Boost CPU Freq" +msgstr "最大 Turbo Boost CPU 频率" + +msgid "CPU Switching Sampling rate" +msgstr "CPU 切换周期" + +msgid "The sampling rate determines how frequently the governor checks to tune the CPU (ms)" +msgstr "CPU 检查切换的周期 (ms)。注意:过于频繁的切换频率会引起网络延迟抖动" + +msgid "CPU Switching Threshold" +msgstr "CPU 切换频率触发阈值" + +msgid "Kernel make a decision on whether it should increase the frequency (%)" +msgstr "当 CPU 占用率超过 (%) 的情况下触发内核切换频率" diff --git a/luci-app-cpufreq/root/etc/config/cpufreq b/luci-app-cpufreq/root/etc/config/cpufreq new file mode 100644 index 000000000..5c2c070e9 --- /dev/null +++ b/luci-app-cpufreq/root/etc/config/cpufreq @@ -0,0 +1,3 @@ + +config settings 'cpufreq' + diff --git a/luci-app-cpufreq/root/etc/init.d/cpufreq b/luci-app-cpufreq/root/etc/init.d/cpufreq new file mode 100644 index 000000000..4dda93bc7 --- /dev/null +++ b/luci-app-cpufreq/root/etc/init.d/cpufreq @@ -0,0 +1,27 @@ +#!/bin/sh /etc/rc.common +START=15 + +NAME="cpufreq" + +config_get_cpufreq() +{ + config_get "$NAME" "$1" +} + +start() +{ + config_load "$NAME" + + for i in $(find /sys/devices/system/cpu/cpufreq/policy* -maxdepth 0 | grep -Eo '[0-9]+') + do + [ -z "$(config_get_cpufreq "governor$i")" ] && return + + config_get_cpufreq "governor$i" > "/sys/devices/system/cpu/cpufreq/policy$i/scaling_governor" + config_get_cpufreq "minfreq$i" > "/sys/devices/system/cpu/cpufreq/policy$i/scaling_min_freq" + config_get_cpufreq "maxfreq$i" > "/sys/devices/system/cpu/cpufreq/policy$i/scaling_max_freq" + if [ "$(config_get_cpufreq "governor$i")" = "ondemand" ]; then + config_get_cpufreq "sdfactor$i" > "/sys/devices/system/cpu/cpufreq/ondemand/sampling_down_factor" + config_get_cpufreq "upthreshold$i" > "/sys/devices/system/cpu/cpufreq/ondemand/up_threshold" + fi + done +} diff --git a/luci-app-cpufreq/root/etc/uci-defaults/10-cpufreq b/luci-app-cpufreq/root/etc/uci-defaults/10-cpufreq new file mode 100644 index 000000000..4ad31dd5f --- /dev/null +++ b/luci-app-cpufreq/root/etc/uci-defaults/10-cpufreq @@ -0,0 +1,104 @@ +#!/bin/sh + +uci_write_config() { + uci -q set cpufreq.cpufreq.governor$1="$2" + uci -q set cpufreq.cpufreq.minfreq$1="$3" + uci -q set cpufreq.cpufreq.maxfreq$1="$4" + [ -n "$5" ] && uci -q set cpufreq.cpufreq.sdfactor$1="$5" + [ -n "$6" ] && uci -q set cpufreq.cpufreq.upthreshold$1="$6" + uci -q commit cpufreq +} + +CPU_FREQS="$(cat '/sys/devices/system/cpu/cpufreq/policy0/scaling_available_frequencies')" +CPU_POLICYS="$(find '/sys/devices/system/cpu/cpufreq/policy'* -maxdepth 0 | grep -Eo '[0-9]+')" +source "/etc/openwrt_release" +case "$DISTRIB_TARGET" in + "bcm27xx/bcm2710") + uci_write_config 0 ondemand 600000 1200000 10 50 + ;; + "bcm27xx/bcm2711") + uci_write_config 0 ondemand 600000 1500000 10 50 + ;; + "ipq40xx/generic") + uci_write_config 0 ondemand 200000 716000 10 50 + ;; + "ipq60xx/generic") + if echo "$CPU_FREQS" | grep -q "1800000"; then + # IPQ6010/18/28 + CPU_MAX_FREQ="1800000" + else + # IPQ6000 + CPU_MAX_FREQ="1200000" + fi + uci_write_config 0 ondemand 864000 $CPU_MAX_FREQ 10 50 + ;; + "ipq806x/generic") + if echo "$CPU_FREQS" | grep -q "1725000"; then + # IPQ8065 + CPU_MAX_FREQ="1725000" + elif echo "$CPU_FREQS" | grep -q "1400000"; then + # IPQ8064 + CPU_MAX_FREQ="1400000" + else + # IPQ8062 + CPU_MAX_FREQ="1000000" + fi + uci_write_config 0 ondemand 600000 $CPU_MAX_FREQ 10 50 + # IPQ8064/5 + echo "$CPU_POLICYS" | grep -q "1" && uci_write_config 1 ondemand 600000 1200000 10 50 + ;; + "ipq807x/generic") + if echo "$CPU_FREQS" | grep -q "2208000"; then + # IPQ8072/4/6/8A + CPU_MAX_FREQ="2208000" + else + # IPQ8071A + CPU_MAX_FREQ="1382400" + fi + uci_write_config 0 ondemand 1017600 $CPU_MAX_FREQ 10 50 + ;; + "mediatek/mt7622") + uci_write_config 0 ondemand 600000 1350000 10 50 + ;; + "meson/meson8b") + uci_write_config 0 schedutil 816000 1536000 + ;; + "rockchip/armv8") + if echo "$CPU_POLICYS" | grep -q "4"; then + # RK3399 + uci_write_config 0 schedutil 600000 1608000 + uci_write_config 4 schedutil 600000 2016000 + else + if echo "$CPU_FREQS" | grep -q "1992000"; then + # RK3568 + CPU_MAX_FREQ="1992000" + elif echo "$CPU_FREQS" | grep -q "1800000"; then + # RK3566 + CPU_MAX_FREQ="1800000" + else + # RK3328 + CPU_MAX_FREQ="1512000" + fi + uci_write_config 0 schedutil 816000 $CPU_MAX_FREQ + fi + ;; + "sunxi/cortexa53") + if echo "$CPU_FREQS" | grep -q "1800000"; then + # H6 + uci_write_config 0 schedutil 816000 1800000 + else + # H5 + uci_write_config 0 ondemand 648000 1200000 10 50 + fi + ;; +esac + +uci -q batch <<-EOF >/dev/null + delete ucitrack.@cpufreq[-1] + add ucitrack cpufreq + set ucitrack.@cpufreq[-1].init=cpufreq + commit ucitrack +EOF + +rm -f /tmp/luci-indexcache +exit 0 diff --git a/luci-app-cpufreq/root/usr/share/rpcd/acl.d/luci-app-cpufreq.json b/luci-app-cpufreq/root/usr/share/rpcd/acl.d/luci-app-cpufreq.json new file mode 100644 index 000000000..fae58ae40 --- /dev/null +++ b/luci-app-cpufreq/root/usr/share/rpcd/acl.d/luci-app-cpufreq.json @@ -0,0 +1,11 @@ +{ + "luci-app-cpufreq": { + "description": "Grant UCI access for luci-app-cpufreq", + "read": { + "uci": [ "cpufreq" ] + }, + "write": { + "uci": [ "cpufreq" ] + } + } +} diff --git a/luci-app-diskman/Makefile b/luci-app-diskman/Makefile new file mode 100644 index 000000000..fa6c46357 --- /dev/null +++ b/luci-app-diskman/Makefile @@ -0,0 +1,51 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=luci-app-diskman + +PKG_MAINTAINER:=lisaac +PKG_LICENSE:=AGPL-3.0 + +LUCI_TITLE:=Disk Manager interface for LuCI +LUCI_DEPENDS:=+blkid +e2fsprogs +parted +smartmontools \ + +PACKAGE_$(PKG_NAME)_INCLUDE_btrfs_progs:btrfs-progs \ + +PACKAGE_$(PKG_NAME)_INCLUDE_lsblk:lsblk \ + +PACKAGE_$(PKG_NAME)_INCLUDE_mdadm:mdadm \ + +PACKAGE_$(PKG_NAME)_INCLUDE_kmod_md_raid456:mdadm \ + +PACKAGE_$(PKG_NAME)_INCLUDE_kmod_md_raid456:kmod-md-raid456 \ + +PACKAGE_$(PKG_NAME)_INCLUDE_kmod_md_linears:mdadm \ + +PACKAGE_$(PKG_NAME)_INCLUDE_kmod_md_linears:kmod-md-linear + +include $(INCLUDE_DIR)/package.mk + +define Package/$(PKG_NAME)/config +config PACKAGE_$(PKG_NAME)_INCLUDE_btrfs_progs + bool "Include btrfs-progs" + default n + +config PACKAGE_$(PKG_NAME)_INCLUDE_lsblk + bool "Include lsblk" + default n + +config PACKAGE_$(PKG_NAME)_INCLUDE_mdadm + bool "Include mdadm" + default n + +config PACKAGE_$(PKG_NAME)_INCLUDE_kmod_md_raid456 + depends on PACKAGE_$(PKG_NAME)_INCLUDE_mdadm + bool "Include kmod-md-raid456" + default n + +config PACKAGE_$(PKG_NAME)_INCLUDE_kmod_md_linear + depends on PACKAGE_$(PKG_NAME)_INCLUDE_mdadm + bool "Include kmod-md-linear" + default n +endef + +define Package/$(PKG_NAME)/postinst +#!/bin/sh +rm -fr /tmp/luci-indexcache /tmp/luci-modulecache +endef + +include $(TOPDIR)/feeds/luci/luci.mk + +# call BuildPackage - OpenWrt buildroot signature diff --git a/luci-app-diskman/luasrc/controller/diskman.lua b/luci-app-diskman/luasrc/controller/diskman.lua new file mode 100644 index 000000000..258120430 --- /dev/null +++ b/luci-app-diskman/luasrc/controller/diskman.lua @@ -0,0 +1,155 @@ +--[[ +LuCI - Lua Configuration Interface +Copyright 2019 lisaac +]]-- + +require "luci.util" +module("luci.controller.diskman",package.seeall) + +function index() + -- check all used executables in disk management are existed + local CMD = {"parted", "blkid", "smartctl"} + local executables_all_existed = true + for _, cmd in ipairs(CMD) do + local command = luci.sys.exec("/usr/bin/which " .. cmd) + if not command:match(cmd) then + executables_all_existed = false + break + end + end + + if not executables_all_existed then return end + -- entry(path, target, title, order) + -- set leaf attr to true to pass argument throughe url (e.g. admin/system/disk/partition/sda) + entry({"admin", "system", "diskman"}, alias("admin", "system", "diskman", "disks"), _("Disk Man"), 55) + entry({"admin", "system", "diskman", "disks"}, form("diskman/disks"), nil).leaf = true + entry({"admin", "system", "diskman", "partition"}, form("diskman/partition"), nil).leaf = true + entry({"admin", "system", "diskman", "btrfs"}, form("diskman/btrfs"), nil).leaf = true + entry({"admin", "system", "diskman", "format_partition"}, call("format_partition"), nil).leaf = true + entry({"admin", "system", "diskman", "get_disk_info"}, call("get_disk_info"), nil).leaf = true + entry({"admin", "system", "diskman", "mk_p_table"}, call("mk_p_table"), nil).leaf = true + entry({"admin", "system", "diskman", "smartdetail"}, call("smart_detail"), nil).leaf = true + entry({"admin", "system", "diskman", "smartattr"}, call("smart_attr"), nil).leaf = true +end + +function format_partition() + local partation_name = luci.http.formvalue("partation_name") + local fs = luci.http.formvalue("file_system") + if not partation_name then + luci.http.status(500, "Partition NOT found!") + luci.http.write_json("Partition NOT found!") + return + elseif not nixio.fs.access("/dev/"..partation_name) then + luci.http.status(500, "Partition NOT found!") + luci.http.write_json("Partition NOT found!") + return + elseif not fs then + luci.http.status(500, "no file system") + luci.http.write_json("no file system") + return + end + local dm = require "luci.model.diskman" + code, msg = dm.format_partition(partation_name, fs) + luci.http.status(code, msg) + luci.http.write_json(msg) +end + +function get_disk_info(dev) + if not dev then + luci.http.status(500, "no device") + luci.http.write_json("no device") + return + elseif not nixio.fs.access("/dev/"..dev) then + luci.http.status(500, "no device") + luci.http.write_json("no device") + return + end + local dm = require "luci.model.diskman" + local device_info = dm.get_disk_info(dev) + luci.http.status(200, "ok") + luci.http.prepare_content("application/json") + luci.http.write_json(device_info) +end + +function mk_p_table() + local p_table = luci.http.formvalue("p_table") + local dev = luci.http.formvalue("dev") + if not dev then + luci.http.status(500, "no device") + luci.http.write_json("no device") + return + elseif not nixio.fs.access("/dev/"..dev) then + luci.http.status(500, "no device") + luci.http.write_json("no device") + return + end + local dm = require "luci.model.diskman" + if p_table == "GPT" or p_table == "MBR" then + p_table = p_table == "MBR" and "msdos" or "gpt" + local res = luci.sys.call(dm.command.parted .. " -s /dev/" .. dev .. " mktable ".. p_table) + if res == 0 then + luci.http.status(200, "ok") + else + luci.http.status(500, "command exec error") + end + luci.http.prepare_content("application/json") + luci.http.write_json({code=res}) + else + luci.http.status(404, "not support") + luci.http.prepare_content("application/json") + luci.http.write_json({code="1"}) + end +end + +function smart_detail(dev) + luci.template.render("diskman/smart_detail", {dev=dev}) +end + +function smart_attr(dev) + local dm = require "luci.model.diskman" + local cmd = io.popen(dm.command.smartctl .. " -H -A -i /dev/%s" % dev) + if cmd then + local attr = { } + if cmd:match("NVMe Version:")then + while true do + local ln = cmd:read("*l") + if not ln then + break + elseif ln:match("^(.-):%s+(.+)") then + local key, value = ln:match("^(.-):%s+(.+)") + attr[#attr+1]= { + key = key, + value = value + } + end + end + else + while true do + local ln = cmd:read("*l") + if not ln then + break + elseif ln:match("^.*%d+%s+.+%s+.+%s+.+%s+.+%s+.+%s+.+%s+.+%s+.+%s+.+") then + local id,attrbute,flag,value,worst,thresh,type,updated,raw = ln:match("^%s*(%d+)%s+([%a%p]+)%s+(%w+)%s+(%d+)%s+(%d+)%s+(%d+)%s+([%a%p]+)%s+(%a+)%s+[%w%p]+%s+(.+)") + id= "%x" % id + if not id:match("^%w%w") then + id = "0%s" % id + end + attr[#attr+1]= { + id = id:upper(), + attrbute = attrbute, + flag = flag, + value = value, + worst = worst, + thresh = thresh, + type = type, + updated = updated, + raw = raw + } + end + end + end + cmd:close() + luci.http.prepare_content("application/json") + luci.http.write_json(attr) + end +end diff --git a/luci-app-diskman/luasrc/model/cbi/diskman/btrfs.lua b/luci-app-diskman/luasrc/model/cbi/diskman/btrfs.lua new file mode 100644 index 000000000..006007853 --- /dev/null +++ b/luci-app-diskman/luasrc/model/cbi/diskman/btrfs.lua @@ -0,0 +1,210 @@ +--[[ +LuCI - Lua Configuration Interface +Copyright 2019 lisaac +]]-- + +require "luci.util" +require("luci.tools.webadmin") +local dm = require "luci.model.diskman" +local uuid = arg[1] + +if not uuid then luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman")) end + +-- mount subv=/ to tempfs +mount_point = "/tmp/.btrfs_tmp" +nixio.fs.mkdirr(mount_point) +luci.util.exec(dm.command.umount .. " "..mount_point .. " >/dev/null 2>&1") +luci.util.exec(dm.command.mount .. " -t btrfs -o subvol=/ UUID="..uuid.." "..mount_point) + +m = SimpleForm("btrfs", translate("Btrfs"), translate("Manage Btrfs")) +m.template = "diskman/cbi/xsimpleform" +m.redirect = luci.dispatcher.build_url("admin/system/diskman") +m.submit = false +m.reset = false + +-- info +local btrfs_info = dm.get_btrfs_info(mount_point) +local table_btrfs_info = m:section(Table, {btrfs_info}, translate("Btrfs Info")) +table_btrfs_info:option(DummyValue, "uuid", translate("UUID")) +table_btrfs_info:option(DummyValue, "members", translate("Members")) +table_btrfs_info:option(DummyValue, "data_raid_level", translate("Data")) +table_btrfs_info:option(DummyValue, "metadata_raid_lavel", translate("Metadata")) +table_btrfs_info:option(DummyValue, "size_formated", translate("Size")) +table_btrfs_info:option(DummyValue, "used_formated", translate("Used")) +table_btrfs_info:option(DummyValue, "free_formated", translate("Free Space")) +table_btrfs_info:option(DummyValue, "usage", translate("Usage")) +local v_btrfs_label = table_btrfs_info:option(Value, "label", translate("Label")) +local value_btrfs_label = "" +v_btrfs_label.write = function(self, section, value) + value_btrfs_label = value or "" +end +local btn_update_label = table_btrfs_info:option(Button, "_update_label") +btn_update_label.inputtitle = translate("Update") +btn_update_label.inputstyle = "edit" +btn_update_label.write = function(self, section, value) + local cmd = dm.command.btrfs .. " filesystem label " .. mount_point .. " " .. value_btrfs_label + local res = luci.util.exec(cmd) + luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman/btrfs/" .. uuid)) +end +-- subvolume +local subvolume_list = dm.get_btrfs_subv(mount_point) +subvolume_list["_"] = { ID = 0 } +table_subvolume = m:section(Table, subvolume_list, translate("SubVolumes")) +table_subvolume:option(DummyValue, "id", translate("ID")) +table_subvolume:option(DummyValue, "top_level", translate("Top Level")) +table_subvolume:option(DummyValue, "uuid", translate("UUID")) +table_subvolume:option(DummyValue, "otime", translate("Otime")) +table_subvolume:option(DummyValue, "snapshots", translate("Snapshots")) +local v_path = table_subvolume:option(Value, "path", translate("Path")) +v_path.forcewrite = true +v_path.render = function(self, section, scope) + if subvolume_list[section].ID == 0 then + self.template = "cbi/value" + self.placeholder = "/my_subvolume" + self.forcewrite = true + Value.render(self, section, scope) + else + self.template = "cbi/dvalue" + DummyValue.render(self, section, scope) + end +end +local value_path +v_path.write = function(self, section, value) + value_path = value +end +local btn_set_default = table_subvolume:option(Button, "_subv_set_default", translate("Set Default")) +btn_set_default.forcewrite = true +btn_set_default.inputstyle = "edit" +btn_set_default.template = "diskman/cbi/disabled_button" +btn_set_default.render = function(self, section, scope) + if subvolume_list[section].default_subvolume then + self.view_disabled = true + self.inputtitle = translate("Set Default") + elseif subvolume_list[section].ID == 0 then + self.template = "cbi/dvalue" + else + self.inputtitle = translate("Set Default") + self.view_disabled = false + end + Button.render(self, section, scope) +end +btn_set_default.write = function(self, section, value) + local cmd + if value == translate("Set Default") then + cmd = dm.command.btrfs .. " subvolume set-default " .. mount_point..subvolume_list[section].path + else + cmd = dm.command.btrfs .. " subvolume set-default " .. mount_point.."/" + end + local res = luci.util.exec(cmd.. " 2>&1") + if res and (res:match("ERR") or res:match("not enough arguments")) then + m.errmessage = res + else + luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman/btrfs/" .. uuid)) + end +end +local btn_remove = table_subvolume:option(Button, "_subv_remove") +btn_remove.template = "diskman/cbi/disabled_button" +btn_remove.forcewrite = true +btn_remove.render = function(self, section, scope) + if subvolume_list[section].ID == 0 then + btn_remove.inputtitle = translate("Create") + btn_remove.inputstyle = "add" + self.view_disabled = false + elseif subvolume_list[section].path == "/" or subvolume_list[section].default_subvolume then + btn_remove.inputtitle = translate("Delete") + btn_remove.inputstyle = "remove" + self.view_disabled = true + else + btn_remove.inputtitle = translate("Delete") + btn_remove.inputstyle = "remove" + self.view_disabled = false + end + Button.render(self, section, scope) +end + +btn_remove.write = function(self, section, value) + local cmd + if value == translate("Delete") then + cmd = dm.command.btrfs .. " subvolume delete " .. mount_point .. subvolume_list[section].path + elseif value == translate("Create") then + if value_path and value_path:match("^/") then + cmd = dm.command.btrfs .. " subvolume create " .. mount_point .. value_path + else + m.errmessage = translate("Please input Subvolume Path, Subvolume must start with '/'") + return + end + end + local res = luci.util.exec(cmd.. " 2>&1") + if res and (res:match("ERR") or res:match("not enough arguments")) then + m.errmessage = luci.util.pcdata(res) + else + luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman/btrfs/" .. uuid)) + end +end +-- snapshot +-- local snapshot_list = dm.get_btrfs_subv(mount_point, 1) +-- table_snapshot = m:section(Table, snapshot_list, translate("Snapshots")) +-- table_snapshot:option(DummyValue, "id", translate("ID")) +-- table_snapshot:option(DummyValue, "top_level", translate("Top Level")) +-- table_snapshot:option(DummyValue, "uuid", translate("UUID")) +-- table_snapshot:option(DummyValue, "otime", translate("Otime")) +-- table_snapshot:option(DummyValue, "path", translate("Path")) +-- local snp_remove = table_snapshot:option(Button, "_snp_remove") +-- snp_remove.inputtitle = translate("Delete") +-- snp_remove.inputstyle = "remove" +-- snp_remove.write = function(self, section, value) +-- local cmd = dm.command.btrfs .. " subvolume delete " .. mount_point .. snapshot_list[section].path +-- local res = luci.util.exec(cmd.. " 2>&1") +-- if res and (res:match("ERR") or res:match("not enough arguments")) then +-- m.errmessage = luci.util.pcdata(res) +-- else +-- luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman/btrfs/" .. uuid)) +-- end +-- end + +-- new snapshots +local s_snapshot = m:section(SimpleSection, translate("New Snapshot")) +local value_sorce, value_dest, value_readonly +local v_sorce = s_snapshot:option(Value, "_source", translate("Source Path"), translate("The source path for create the snapshot")) +v_sorce.placeholder = "/data" +v_sorce.forcewrite = true +v_sorce.write = function(self, section, value) + value_sorce = value +end + +local v_readonly = s_snapshot:option(Flag, "_readonly", translate("Readonly"), translate("The path where you want to store the snapshot")) +v_readonly.forcewrite = true +v_readonly.rmempty = false +v_readonly.disabled = 0 +v_readonly.enabled = 1 +v_readonly.default = 1 +v_readonly.write = function(self, section, value) + value_readonly = value +end +local v_dest = s_snapshot:option(Value, "_dest", translate("Destination Path (optional)")) +v_dest.forcewrite = true +v_dest.placeholder = "/.snapshot/202002051538" +v_dest.write = function(self, section, value) + value_dest = value +end +local btn_snp_create = s_snapshot:option(Button, "_snp_create") +btn_snp_create.title = " " +btn_snp_create.inputtitle = translate("New Snapshot") +btn_snp_create.inputstyle = "add" +btn_snp_create.write = function(self, section, value) + if value_sorce and value_sorce:match("^/") then + if not value_dest then value_dest = "/.snapshot"..value_sorce.."/"..os.date("%Y%m%d%H%M%S") end + nixio.fs.mkdirr(mount_point..value_dest:match("(.-)[^/]+$")) + local cmd = dm.command.btrfs .. " subvolume snapshot" .. (value_readonly == 1 and " -r " or " ") .. mount_point..value_sorce .. " " .. mount_point..value_dest + local res = luci.util.exec(cmd .. " 2>&1") + if res and (res:match("ERR") or res:match("not enough arguments")) then + m.errmessage = luci.util.pcdata(res) + else + luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman/btrfs/" .. uuid)) + end + else + m.errmessage = translate("Please input Source Path of snapshot, Source Path must start with '/'") + end +end + +return m diff --git a/luci-app-diskman/luasrc/model/cbi/diskman/disks.lua b/luci-app-diskman/luasrc/model/cbi/diskman/disks.lua new file mode 100644 index 000000000..c209df0aa --- /dev/null +++ b/luci-app-diskman/luasrc/model/cbi/diskman/disks.lua @@ -0,0 +1,327 @@ +--[[ +LuCI - Lua Configuration Interface +Copyright 2019 lisaac +]]-- + +require "luci.util" +require("luci.tools.webadmin") +local dm = require "luci.model.diskman" + +-- Use (non-UCI) SimpleForm since we have no related config file +m = SimpleForm("diskman", translate("DiskMan"), translate("Manage Disks over LuCI.")) +m.template = "diskman/cbi/xsimpleform" +m:append(Template("diskman/disk_info")) +-- disable submit and reset button +m.submit = false +m.reset = false +-- rescan disks +rescan = m:section(SimpleSection) +rescan_button = rescan:option(Button, "_rescan") +rescan_button.inputtitle= translate("Rescan Disks") +rescan_button.template = "diskman/cbi/inlinebutton" +rescan_button.inputstyle = "add" +rescan_button.forcewrite = true +rescan_button.write = function(self, section, value) + luci.util.exec("echo '- - -' | tee /sys/class/scsi_host/host*/scan > /dev/null") + if dm.command.mdadm then + luci.util.exec(dm.command.mdadm .. " --assemble --scan") + end + luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman")) +end + +-- disks +local disks = dm.list_devices() +d = m:section(Table, disks, translate("Disks")) +d.config = "disk" +-- option(type, id(key of table), text) +d:option(DummyValue, "path", translate("Path")) +d:option(DummyValue, "model", translate("Model")) +d:option(DummyValue, "sn", translate("Serial Number")) +d:option(DummyValue, "size_formated", translate("Size")) +d:option(DummyValue, "temp", translate("Temp")) +-- d:option(DummyValue, "sec_size", translate("Sector Size ")) +d:option(DummyValue, "p_table", translate("Partition Table")) +d:option(DummyValue, "sata_ver", translate("SATA Version")) +-- d:option(DummyValue, "rota_rate", translate("Rotation Rate")) +d:option(DummyValue, "health", translate("Health")) +d:option(DummyValue, "status", translate("Status")) + +d.extedit = luci.dispatcher.build_url("admin/system/diskman/partition/%s") + +-- raid devices +if dm.command.mdadm then + local raid_devices = dm.list_raid_devices() + -- raid_devices = diskmanager.getRAIDdevices() + if next(raid_devices) ~= nil then + local r = m:section(Table, raid_devices, translate("RAID Devices")) + r.config = "_raid" + r:option(DummyValue, "path", translate("Path")) + r:option(DummyValue, "level", translate("RAID mode")) + r:option(DummyValue, "size_formated", translate("Size")) + r:option(DummyValue, "p_table", translate("Partition Table")) + r:option(DummyValue, "status", translate("Status")) + r:option(DummyValue, "members_str", translate("Members")) + r:option(DummyValue, "active", translate("Active")) + r.extedit = luci.dispatcher.build_url("admin/system/diskman/partition/%s") + end +end + +-- btrfs devices +if dm.command.btrfs then + btrfs_devices = dm.list_btrfs_devices() + if next(btrfs_devices) ~= nil then + local table_btrfs = m:section(Table, btrfs_devices, translate("Btrfs")) + table_btrfs:option(DummyValue, "uuid", translate("UUID")) + table_btrfs:option(DummyValue, "label", translate("Label")) + table_btrfs:option(DummyValue, "members", translate("Members")) + -- sieze is error, since there is RAID + -- table_btrfs:option(DummyValue, "size_formated", translate("Size")) + table_btrfs:option(DummyValue, "used_formated", translate("Usage")) + table_btrfs.extedit = luci.dispatcher.build_url("admin/system/diskman/btrfs/%s") + end +end + +-- mount point +local mount_point = dm.get_mount_points() +local _mount_point = {} +table.insert( mount_point, { device = 0 } ) +local table_mp = m:section(Table, mount_point, translate("Mount Point")) +local v_device = table_mp:option(Value, "device", translate("Device")) +v_device.render = function(self, section, scope) + if mount_point[section].device == 0 then + self.template = "cbi/value" + self.forcewrite = true + for dev, info in pairs(disks) do + for i, v in ipairs(info.partitions) do + self:value("/dev/".. v.name, "/dev/".. v.name .. " ".. v.size_formated) + end + end + Value.render(self, section, scope) + else + self.template = "cbi/dvalue" + DummyValue.render(self, section, scope) + end +end +v_device.write = function(self, section, value) + _mount_point.device = value and value:gsub("%s+", "") or "" +end +local v_fs = table_mp:option(Value, "fs", translate("File System")) +v_fs.render = function(self, section, scope) + if mount_point[section].device == 0 then + self.template = "cbi/value" + self:value("auto", "auto") + self.default = "auto" + self.forcewrite = true + Value.render(self, section, scope) + else + self.template = "cbi/dvalue" + DummyValue.render(self, section, scope) + end +end +v_fs.write = function(self, section, value) + _mount_point.fs = value and value:gsub("%s+", "") or "" +end +local v_mount_option = table_mp:option(Value, "mount_options", translate("Mount Options")) +v_mount_option.render = function(self, section, scope) + if mount_point[section].device == 0 then + self.template = "cbi/value" + self.placeholder = "rw,noauto" + self.forcewrite = true + Value.render(self, section, scope) + else + self.template = "cbi/dvalue" + local mp = mount_point[section].mount_options + mount_point[section].mount_options = nil + local length = 0 + for k in mp:gmatch("([^,]+)") do + mount_point[section].mount_options = mount_point[section].mount_options and (mount_point[section].mount_options .. ",") or "" + if length > 20 then + mount_point[section].mount_options = mount_point[section].mount_options.. "
" + length = 0 + end + mount_point[section].mount_options = mount_point[section].mount_options .. k + length = length + #k + end + self.rawhtml = true + -- mount_point[section].mount_options = #mount_point[section].mount_options > 50 and mount_point[section].mount_options:sub(1,50) .. "..." or mount_point[section].mount_options + DummyValue.render(self, section, scope) + end +end +v_mount_option.write = function(self, section, value) + _mount_point.mount_options = value and value:gsub("%s+", "") or "" +end +local v_mount_point = table_mp:option(Value, "mount_point", translate("Mount Point")) +v_mount_point.render = function(self, section, scope) + if mount_point[section].device == 0 then + self.template = "cbi/value" + self.placeholder = "/media/diskX" + self.forcewrite = true + Value.render(self, section, scope) + else + self.template = "cbi/dvalue" + local new_mp = "" + local v_mp_d + for v_mp_d in self["section"]["data"][section]["mount_point"]:gmatch('[^/]+') do + if #v_mp_d > 12 then + new_mp = new_mp .. "/" .. v_mp_d:sub(1,7) .. ".." .. v_mp_d:sub(-4) + else + new_mp = new_mp .."/".. v_mp_d + end + end + self["section"]["data"][section]["mount_point"] = ''..new_mp..'' + self.rawhtml = true + DummyValue.render(self, section, scope) + end +end +v_mount_point.write = function(self, section, value) + _mount_point.mount_point = value +end +local btn_umount = table_mp:option(Button, "_mount", translate("Mount")) +btn_umount.forcewrite = true +btn_umount.render = function(self, section, scope) + if mount_point[section].device == 0 then + self.inputtitle = translate("Mount") + btn_umount.inputstyle = "add" + else + self.inputtitle = translate("Umount") + btn_umount.inputstyle = "remove" + end + Button.render(self, section, scope) +end +btn_umount.write = function(self, section, value) + local res + if value == translate("Mount") then + if not _mount_point.mount_point or not _mount_point.device then return end + luci.util.exec("mkdir -p ".. _mount_point.mount_point) + res = luci.util.exec(dm.command.mount .. " ".. _mount_point.device .. (_mount_point.fs and (" -t ".. _mount_point.fs )or "") .. (_mount_point.mount_options and (" -o " .. _mount_point.mount_options.. " ") or " ").._mount_point.mount_point .. " 2>&1") + elseif value == translate("Umount") then + res = luci.util.exec(dm.command.umount .. " "..mount_point[section].mount_point .. " 2>&1") + end + if res:match("^mount:") or res:match("^umount:") then + m.errmessage = luci.util.pcdata(res) + else + luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman")) + end +end + +if dm.command.mdadm or dm.command.btrfs then +local creation_section = m:section(TypedSection, "_creation") +creation_section.cfgsections=function() + return {translate("Creation")} +end +creation_section:tab("raid", translate("RAID"), translate("RAID Creation")) +creation_section:tab("btrfs", translate("Btrfs"), translate("Multiple Devices Btrfs Creation")) + +-- raid functions +if dm.command.mdadm then + + local rname, rmembers, rlevel + local r_name = creation_section:taboption("raid", Value, "_rname", translate("Raid Name")) + r_name.placeholder = "/dev/md0" + r_name.write = function(self, section, value) + rname = value + end + local r_level = creation_section:taboption("raid", ListValue, "_rlevel", translate("Raid Level")) + local valid_raid = luci.util.exec("lsmod | grep md_mod") + if valid_raid:match("linear") then + r_level:value("linear", "Linear") + end + if valid_raid:match("raid456") then + r_level:value("5", "Raid 5") + r_level:value("6", "Raid 6") + end + if valid_raid:match("raid1") then + r_level:value("1", "Raid 1") + end + if valid_raid:match("raid0") then + r_level:value("0", "Raid 0") + end + if valid_raid:match("raid10") then + r_level:value("10", "Raid 10") + end + r_level.write = function(self, section, value) + rlevel = value + end + local r_member = creation_section:taboption("raid", DynamicList, "_rmember", translate("Raid Member")) + for dev, info in pairs(disks) do + if not info.inuse and #info.partitions == 0 then + r_member:value(info.path, info.path.. " ".. info.size_formated) + end + for i, v in ipairs(info.partitions) do + if not v.inuse then + r_member:value("/dev/".. v.name, "/dev/".. v.name .. " ".. v.size_formated) + end + end + end + r_member.write = function(self, section, value) + rmembers = value + end + local r_create = creation_section:taboption("raid", Button, "_rcreate") + r_create.render = function(self, section, scope) + self.title = " " + self.inputtitle = translate("Create Raid") + self.inputstyle = "add" + Button.render(self, section, scope) + end + r_create.write = function(self, section, value) + -- mdadm --create --verbose /dev/md0 --level=stripe --raid-devices=2 /dev/sdb6 /dev/sdc5 + local res = dm.create_raid(rname, rlevel, rmembers) + if res and res:match("^ERR") then + m.errmessage = luci.util.pcdata(res) + return + end + dm.gen_mdadm_config() + luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman")) + end +end + +-- btrfs +if dm.command.btrfs then + local blabel, bmembers, blevel + local btrfs_label = creation_section:taboption("btrfs", Value, "_blabel", translate("Btrfs Label")) + btrfs_label.write = function(self, section, value) + blabel = value + end + local btrfs_level = creation_section:taboption("btrfs", ListValue, "_blevel", translate("Btrfs Raid Level")) + btrfs_level:value("single", "Single") + btrfs_level:value("raid0", "Raid 0") + btrfs_level:value("raid1", "Raid 1") + btrfs_level:value("raid10", "Raid 10") + btrfs_level.write = function(self, section, value) + blevel = value + end + + local btrfs_member = creation_section:taboption("btrfs", DynamicList, "_bmember", translate("Btrfs Member")) + for dev, info in pairs(disks) do + if not info.inuse and #info.partitions == 0 then + btrfs_member:value(info.path, info.path.. " ".. info.size_formated) + end + for i, v in ipairs(info.partitions) do + if not v.inuse then + btrfs_member:value("/dev/".. v.name, "/dev/".. v.name .. " ".. v.size_formated) + end + end + end + btrfs_member.write = function(self, section, value) + bmembers = value + end + local btrfs_create = creation_section:taboption("btrfs", Button, "_bcreate") + btrfs_create.render = function(self, section, scope) + self.title = " " + self.inputtitle = translate("Create Btrfs") + self.inputstyle = "add" + Button.render(self, section, scope) + end + btrfs_create.write = function(self, section, value) + -- mkfs.btrfs -L label -d blevel /dev/sda /dev/sdb + local res = dm.create_btrfs(blabel, blevel, bmembers) + if res and res:match("^ERR") then + m.errmessage = luci.util.pcdata(res) + return + end + luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman")) + end +end +end + +return m diff --git a/luci-app-diskman/luasrc/model/cbi/diskman/partition.lua b/luci-app-diskman/luasrc/model/cbi/diskman/partition.lua new file mode 100644 index 000000000..1428eb6b2 --- /dev/null +++ b/luci-app-diskman/luasrc/model/cbi/diskman/partition.lua @@ -0,0 +1,366 @@ +--[[ +LuCI - Lua Configuration Interface +Copyright 2019 lisaac +]]-- + +require "luci.util" +require("luci.tools.webadmin") +local dm = require "luci.model.diskman" +local dev = arg[1] + +if not dev then + luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman")) +elseif not nixio.fs.access("/dev/"..dev) then + luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman")) +end + +m = SimpleForm("partition", translate("Partition Management"), translate("Partition Disk over LuCI.")) +m.template = "diskman/cbi/xsimpleform" +m.redirect = luci.dispatcher.build_url("admin/system/diskman") +m:append(Template("diskman/partition_info")) +-- disable submit and reset button +m.submit = false +m.reset = false + +local disk_info = dm.get_disk_info(dev, true) +local format_cmd = dm.get_format_cmd() + +s = m:section(Table, {disk_info}, translate("Device Info")) +-- s:option(DummyValue, "key") +-- s:option(DummyValue, "value") +s:option(DummyValue, "path", translate("Path")) +s:option(DummyValue, "model", translate("Model")) +s:option(DummyValue, "sn", translate("Serial Number")) +s:option(DummyValue, "size_formated", translate("Size")) +s:option(DummyValue, "sec_size", translate("Sector Size")) +local dv_p_table = s:option(ListValue, "p_table", translate("Partition Table")) +dv_p_table.render = function(self, section, scope) + -- create table only if not used by raid and no partitions on disk + if not disk_info.p_table:match("Raid") and (#disk_info.partitions == 0 or (#disk_info.partitions == 1 and disk_info.partitions[1].number == -1) or (disk_info.p_table:match("LOOP") and not disk_info.partitions[1].inuse)) then + self:value(disk_info.p_table, disk_info.p_table) + self:value("GPT", "GPT") + self:value("MBR", "MBR") + self.default = disk_info.p_table + ListValue.render(self, section, scope) + else + self.template = "cbi/dvalue" + DummyValue.render(self, section, scope) + end +end +if disk_info.type:match("md") then + s:option(DummyValue, "level", translate("Level")) + s:option(DummyValue, "members_str", translate("Members")) +else + s:option(DummyValue, "temp", translate("Temp")) + s:option(DummyValue, "sata_ver", translate("SATA Version")) + s:option(DummyValue, "rota_rate", translate("Rotation Rate")) +end +s:option(DummyValue, "status", translate("Status")) +local btn_health = s:option(Button, "health", translate("Health")) +btn_health.render = function(self, section, scope) + if disk_info.health then + self.inputtitle = disk_info.health + if disk_info.health == "PASSED" then + self.inputstyle = "add" + else + self.inputstyle = "remove" + end + Button.render(self, section, scope) + else + self.template = "cbi/dvalue" + DummyValue.render(self, section, scope) + end +end + +local btn_eject = s:option(Button, "_eject") +btn_eject.template = "diskman/cbi/disabled_button" +btn_eject.inputstyle = "remove" +btn_eject.render = function(self, section, scope) + for i, p in ipairs(disk_info.partitions) do + if p.mount_point ~= "-" then + self.view_disabled = true + break + end + end + if disk_info.p_table:match("Raid") then + self.view_disabled = true + end + if disk_info.type:match("md") then + btn_eject.inputtitle = translate("Remove") + else + btn_eject.inputtitle = translate("Eject") + end + Button.render(self, section, scope) +end +btn_eject.forcewrite = true +btn_eject.write = function(self, section, value) + for i, p in ipairs(disk_info.partitions) do + if p.mount_point ~= "-" then + m.errmessage = p.name .. translate("is in use! please unmount it first!") + return + end + end + if disk_info.type:match("md") then + luci.util.exec(dm.command.mdadm .. " --stop /dev/" .. dev) + luci.util.exec(dm.command.mdadm .. " --remove /dev/" .. dev) + for _, disk in ipairs(disk_info.members) do + luci.util.exec(dm.command.mdadm .. " --zero-superblock " .. disk) + end + dm.gen_mdadm_config() + else + luci.util.exec("echo 1 > /sys/block/" .. dev .. "/device/delete") + end + luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman")) +end +-- eject: echo 1 > /sys/block/(device)/device/delete +-- rescan: echo '- - -' | tee /sys/class/scsi_host/host*/scan > /dev/null + + +-- partitions info +if not disk_info.p_table:match("Raid") then + s_partition_table = m:section(Table, disk_info.partitions, translate("Partitions Info"), translate("Default 2048 sector alignment, support +size{b,k,m,g,t} in End Sector")) + + -- s_partition_table:option(DummyValue, "number", translate("Number")) + s_partition_table:option(DummyValue, "name", translate("Name")) + local val_sec_start = s_partition_table:option(Value, "sec_start", translate("Start Sector")) + val_sec_start.render = function(self, section, scope) + -- could create new partition + if disk_info.partitions[section].number == -1 and disk_info.partitions[section].size > 1 * 1024 * 1024 then + self.template = "cbi/value" + Value.render(self, section, scope) + else + self.template = "cbi/dvalue" + DummyValue.render(self, section, scope) + end + end + local val_sec_end = s_partition_table:option(Value, "sec_end", translate("End Sector")) + val_sec_end.render = function(self, section, scope) + -- could create new partition + if disk_info.partitions[section].number == -1 and disk_info.partitions[section].size > 1 * 1024 * 1024 then + self.template = "cbi/value" + Value.render(self, section, scope) + else + self.template = "cbi/dvalue" + DummyValue.render(self, section, scope) + end + end + val_sec_start.forcewrite = true + val_sec_start.write = function(self, section, value) + disk_info.partitions[section]._sec_start = value + end + val_sec_end.forcewrite = true + val_sec_end.write = function(self, section, value) + disk_info.partitions[section]._sec_end = value + end + s_partition_table:option(DummyValue, "size_formated", translate("Size")) + if disk_info.p_table == "MBR" then + s_partition_table:option(DummyValue, "type", translate("Type")) + end + s_partition_table:option(DummyValue, "used_formated", translate("Used")) + s_partition_table:option(DummyValue, "free_formated", translate("Free Space")) + s_partition_table:option(DummyValue, "usage", translate("Usage")) + local dv_mount_point = s_partition_table:option(DummyValue, "mount_point", translate("Mount Point")) + dv_mount_point.rawhtml = true + dv_mount_point.render = function(self, section, scope) + local new_mp = "" + local v_mp_d + for line in self["section"]["data"][section]["mount_point"]:gmatch("[^%s]+") do + if line == '-' then + new_mp = line + break + end + for v_mp_d in line:gmatch('[^/]+') do + if #v_mp_d > 12 then + new_mp = new_mp .. "/" .. v_mp_d:sub(1,7) .. ".." .. v_mp_d:sub(-4) + else + new_mp = new_mp .."/".. v_mp_d + end + end + new_mp = '' ..new_mp ..'' .. "
" + end + self["section"]["data"][section]["mount_point"] = new_mp + DummyValue.render(self, section, scope) + end + local val_fs = s_partition_table:option(Value, "fs", translate("File System")) + val_fs.forcewrite = true + val_fs.partitions = disk_info.partitions + for k, v in pairs(format_cmd) do + val_fs.format_cmd = val_fs.format_cmd and (val_fs.format_cmd .. "," .. k) or k + end + + val_fs.write = function(self, section, value) + disk_info.partitions[section]._fs = value + end + val_fs.render = function(self, section, scope) + -- use listvalue when partition not mounted + if disk_info.partitions[section].mount_point == "-" and disk_info.partitions[section].number ~= -1 and disk_info.partitions[section].type ~= "extended" then + self.template = "diskman/cbi/format_button" + self.inputstyle = "reset" + self.inputtitle = disk_info.partitions[section].fs == "raw" and translate("Format") or disk_info.partitions[section].fs + Button.render(self, section, scope) + -- self:reset_values() + -- self.keylist = {} + -- self.vallist = {} + -- for k, v in pairs(format_cmd) do + -- self:value(k,k) + -- end + -- self.default = disk_info.partitions[section].fs + else + -- self:reset_values() + -- self.keylist = {} + -- self.vallist = {} + self.template = "cbi/dvalue" + DummyValue.render(self, section, scope) + end + end + -- btn_format = s_partition_table:option(Button, "_format") + -- btn_format.template = "diskman/cbi/format_button" + -- btn_format.partitions = disk_info.partitions + -- btn_format.render = function(self, section, scope) + -- if disk_info.partitions[section].mount_point == "-" and disk_info.partitions[section].number ~= -1 and disk_info.partitions[section].type ~= "extended" then + -- self.inputtitle = translate("Format") + -- self.template = "diskman/cbi/disabled_button" + -- self.view_disabled = false + -- self.inputstyle = "reset" + -- for k, v in pairs(format_cmd) do + -- self:depends("val_fs", "k") + -- end + -- -- elseif disk_info.partitions[section].mount_point ~= "-" and disk_info.partitions[section].number ~= -1 then + -- -- self.inputtitle = "Format" + -- -- self.template = "diskman/cbi/disabled_button" + -- -- self.view_disabled = true + -- -- self.inputstyle = "reset" + -- else + -- self.inputtitle = "" + -- self.template = "cbi/dvalue" + -- end + -- Button.render(self, section, scope) + -- end + -- btn_format.forcewrite = true + -- btn_format.write = function(self, section, value) + -- local partition_name = "/dev/".. disk_info.partitions[section].name + -- if not nixio.fs.access(partition_name) then + -- m.errmessage = translate("Partition NOT found!") + -- return + -- end + -- local fs = disk_info.partitions[section]._fs + -- if not format_cmd[fs] then + -- m.errmessage = translate("Filesystem NOT support!") + -- return + -- end + -- local cmd = format_cmd[fs].cmd .. " " .. format_cmd[fs].option .. " " .. partition_name + -- local res = luci.util.exec(cmd .. " 2>&1") + -- if res and res:lower():match("error+") then + -- m.errmessage = luci.util.pcdata(res) + -- else + -- luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman/partition/" .. dev)) + -- end + -- end + + local btn_action = s_partition_table:option(Button, "_action") + btn_action.forcewrite = true + btn_action.template = "diskman/cbi/disabled_button" + btn_action.render = function(self, section, scope) + -- if partition is mounted or the size < 1mb, then disable the add action + if disk_info.partitions[section].mount_point ~= "-" or (disk_info.partitions[section].type ~= "extended" and disk_info.partitions[section].number == -1 and disk_info.partitions[section].size <= 1 * 1024 * 1024) then + self.view_disabled = true + -- self.inputtitle = "" + -- self.template = "cbi/dvalue" + elseif disk_info.partitions[section].type == "extended" and next(disk_info.partitions[section]["logicals"]) ~= nil then + self.view_disabled = true + else + -- self.template = "diskman/cbi/disabled_button" + self.view_disabled = false + end + if disk_info.partitions[section].number ~= -1 then + self.inputtitle = translate("Remove") + self.inputstyle = "remove" + else + self.inputtitle = translate("New") + self.inputstyle = "add" + end + Button.render(self, section, scope) + end + btn_action.write = function(self, section, value) + if value == translate("New") then + local start_sec = disk_info.partitions[section]._sec_start and tonumber(disk_info.partitions[section]._sec_start) or tonumber(disk_info.partitions[section].sec_start) + local end_sec = disk_info.partitions[section]._sec_end + + if start_sec then + -- for sector alignment + local align = tonumber(disk_info.phy_sec) / tonumber(disk_info.logic_sec) + align = (align < 2048) and 2048 + if start_sec < 2048 then + start_sec = "2048" .. "s" + elseif math.fmod( start_sec, align ) ~= 0 then + start_sec = tostring(start_sec + align - math.fmod( start_sec, align )) .. "s" + else + start_sec = start_sec .. "s" + end + else + m.errmessage = translate("Invalid Start Sector!") + return + end + -- support +size format for End sector + local end_size, end_unit = end_sec:match("^+(%d-)([bkmgtsBKMGTS])$") + if tonumber(end_size) and end_unit then + local unit ={ + B=1, + S=512, + K=1024, + M=1048576, + G=1073741824, + T=1099511627776 + } + end_unit = end_unit:upper() + end_sec = tostring(tonumber(end_size) * unit[end_unit] / unit["S"] + tonumber(start_sec:sub(1,-2)) - 1 ) .. "s" + elseif tonumber(end_sec) then + end_sec = end_sec .. "s" + else + m.errmessage = translate("Invalid End Sector!") + return + end + local part_type = "primary" + + if disk_info.p_table == "MBR" and disk_info["extended_partition_index"] then + if tonumber(disk_info.partitions[disk_info["extended_partition_index"]].sec_start) <= tonumber(start_sec:sub(1,-2)) and tonumber(disk_info.partitions[disk_info["extended_partition_index"]].sec_end) >= tonumber(end_sec:sub(1,-2)) then + part_type = "logical" + if tonumber(start_sec:sub(1,-2)) - tonumber(disk_info.partitions[section].sec_start) < 2048 then + start_sec = tonumber(start_sec:sub(1,-2)) + 2048 + start_sec = start_sec .."s" + end + end + elseif disk_info.p_table == "GPT" then + -- AUTOMATIC FIX GPT PARTITION TABLE + -- Not all of the space available to /dev/sdb appears to be used, you can fix the GPT to use all of the space (an extra 16123870 blocks) or continue with the current setting? + local cmd = ' printf "ok\nfix\n" | parted ---pretend-input-tty /dev/'.. dev ..' print' + luci.util.exec(cmd .. " 2>&1") + end + + -- partiton + local cmd = dm.command.parted .. " -s -a optimal /dev/" .. dev .. " mkpart " .. part_type .." " .. start_sec .. " " .. end_sec + local res = luci.util.exec(cmd .. " 2>&1") + if res and res:lower():match("error+") then + m.errmessage = luci.util.pcdata(res) + else + luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman/partition/" .. dev)) + end + elseif value == translate("Remove") then + -- remove partition + local number = tostring(disk_info.partitions[section].number) + if (not number) or (number == "") then + m.errmessage = translate("Partition not exists!") + return + end + local cmd = dm.command.parted .. " -s /dev/" .. dev .. " rm " .. number + local res = luci.util.exec(cmd .. " 2>&1") + if res and res:lower():match("error+") then + m.errmessage = luci.util.pcdata(res) + else + luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman/partition/" .. dev)) + end + end + end +end + +return m diff --git a/luci-app-diskman/luasrc/model/diskman.lua b/luci-app-diskman/luasrc/model/diskman.lua new file mode 100644 index 000000000..b29308c31 --- /dev/null +++ b/luci-app-diskman/luasrc/model/diskman.lua @@ -0,0 +1,738 @@ +--[[ +LuCI - Lua Configuration Interface +Copyright 2019 lisaac +]]-- + +require "luci.util" +local ver = require "luci.version" + +local CMD = {"parted", "mdadm", "blkid", "smartctl", "df", "btrfs", "lsblk"} + +local d = {command ={}} +for _, cmd in ipairs(CMD) do + local command = luci.sys.exec("/usr/bin/which " .. cmd) + d.command[cmd] = command:match("^.+"..cmd) or nil +end + +d.command.mount = nixio.fs.access("/usr/bin/mount") and "/usr/bin/mount" or "/bin/mount" +d.command.umount = nixio.fs.access("/usr/bin/umount") and "/usr/bin/umount" or "/bin/umount" + +local proc_mounts = nixio.fs.readfile("/proc/mounts") or "" +local mounts = luci.util.exec(d.command.mount .. " 2>/dev/null") or "" +local swaps = nixio.fs.readfile("/proc/swaps") or "" +local df = luci.sys.exec(d.command.df .. " 2>/dev/null") or "" + +function byte_format(byte) + local suff = {"B", "KB", "MB", "GB", "TB"} + for i=1, 5 do + if byte > 1024 and i < 5 then + byte = byte / 1024 + else + return string.format("%.2f %s", byte, suff[i]) + end + end +end + +local get_smart_info = function(device) + local section + local smart_info = {} + for _, line in ipairs(luci.util.execl(d.command.smartctl .. " -H -A -i -n standby -f brief /dev/" .. device)) do + local attrib, val + if section == 1 then + attrib, val = line:match "^(.-):%s+(.+)" + elseif section == 2 and smart_info.nvme_ver then + attrib, val = line:match("^(.-):%s+(.+)") + if not smart_info.health then smart_info.health = line:match(".-overall%-health.-: (.+)") end + elseif section == 2 then + attrib, val = line:match("^([0-9 ]+)%s+[^ ]+%s+[POSRCK-]+%s+[0-9-]+%s+[0-9-]+%s+[0-9-]+%s+[0-9-]+%s+([0-9-]+)") + if not smart_info.health then smart_info.health = line:match(".-overall%-health.-: (.+)") end + else + attrib = line:match "^=== START OF (.*) SECTION ===" + if attrib and attrib:match("INFORMATION") then + section = 1 + elseif attrib and attrib:match("SMART DATA") then + section = 2 + elseif not smart_info.status then + val = line:match "^Device is in (.*) mode" + if val then smart_info.status = val end + end + end + + if not attrib then + if section ~= 2 then section = 0 end + elseif (attrib == "Power mode is") or + (attrib == "Power mode was") then + smart_info.status = val:match("(%S+)") + -- elseif attrib == "Sector Sizes" then + -- -- 512 bytes logical, 4096 bytes physical + -- smart_info.phy_sec = val:match "([0-9]*) bytes physical" + -- smart_info.logic_sec = val:match "([0-9]*) bytes logical" + -- elseif attrib == "Sector Size" then + -- -- 512 bytes logical/physical + -- smart_info.phy_sec = val:match "([0-9]*)" + -- smart_info.logic_sec = smart_info.phy_sec + elseif attrib == "Serial Number" then + smart_info.sn = val + elseif attrib == "194" or attrib == "Temperature" then + smart_info.temp = val:match("(%d+)") .. "°C" + elseif attrib == "Rotation Rate" then + smart_info.rota_rate = val + elseif attrib == "SATA Version is" then + smart_info.sata_ver = val + elseif attrib == "NVMe Version" then + smart_info.nvme_ver = val + end + end + return smart_info +end + +local parse_parted_info = function(keys, line) + -- parse the output of parted command (machine parseable format) + -- /dev/sda:5860533168s:scsi:512:4096:gpt:ATA ST3000DM001-1ER1:; + -- 1:34s:2047s:2014s:free; + -- 1:2048s:1073743872s:1073741825s:ext4:primary:; + local result = {} + local values = {} + + for value in line:gmatch("(.-)[:;]") do table.insert(values, value) end + for i = 1,#keys do + result[keys[i]] = values[i] or "" + end + return result +end + +local is_raid_member = function(partition) + -- check if inuse as raid member + if nixio.fs.access("/proc/mdstat") then + for _, result in ipairs(luci.util.execl("grep md /proc/mdstat | sed 's/[][]//g'")) do + local md, buf + md, buf = result:match("(md.-):(.+)") + if buf:match(partition) then + return "Raid Member: ".. md + end + end + end + return nil +end + +local get_mount_point = function(partition) + local mount_point + for m in mounts:gmatch("/dev/"..partition.." on ([^ ]*)") do + mount_point = (mount_point and (mount_point .. " ") or "") .. m + end + if mount_point then return mount_point end + -- result = luci.sys.exec('cat /proc/mounts | awk \'{if($1=="/dev/'.. partition ..'") print $2}\'') + -- if result ~= "" then return result end + + if swaps:match("\n/dev/" .. partition .."%s") then return "swap" end + -- result = luci.sys.exec("cat /proc/swaps | grep /dev/" .. partition) + -- if result ~= "" then return "swap" end + + return is_raid_member(partition) + +end + +-- return used, free, usage +local get_partition_usage = function(partition) + if not nixio.fs.access("/dev/"..partition) then return false end + local used, free, usage = df:match("\n/dev/" .. partition .. "%s+%d+%s+(%d+)%s+(%d+)%s+(%d+)%%%s-") + + usage = usage and (usage .. "%") or "-" + used = used and (tonumber(used) * 1024) or 0 + free = free and (tonumber(free) * 1024) or 0 + + return used, free, usage +end + +local get_parted_info = function(device) + if not device then return end + local result = {partitions={}} + local DEVICE_INFO_KEYS = { "path", "size", "type", "logic_sec", "phy_sec", "p_table", "model", "flags" } + local PARTITION_INFO_KEYS = { "number", "sec_start", "sec_end", "size", "fs", "tag_name", "flags" } + local partition_temp + local partitions_temp = {} + local disk_temp + + for line in luci.util.execi(d.command.parted .. " -s -m /dev/" .. device .. " unit s print free", "r") do + if line:find("^/dev/"..device..":.+") then + disk_temp = parse_parted_info(DEVICE_INFO_KEYS, line) + disk_temp.partitions = {} + if disk_temp["size"] then + local length = disk_temp["size"]:gsub("^(%d+)s$", "%1") + local newsize = tostring(tonumber(length)*tonumber(disk_temp["logic_sec"])) + disk_temp["size"] = newsize + end + if disk_temp["p_table"] == "msdos" then + disk_temp["p_table"] = "MBR" + else + disk_temp["p_table"] = disk_temp["p_table"]:upper() + end + elseif line:find("^%d-:.+") then + partition_temp = parse_parted_info(PARTITION_INFO_KEYS, line) + -- use human-readable form instead of sector number + if partition_temp["size"] then + local length = partition_temp["size"]:gsub("^(%d+)s$", "%1") + local newsize = (tonumber(length) * tonumber(disk_temp["logic_sec"])) + partition_temp["size"] = newsize + partition_temp["size_formated"] = byte_format(newsize) + end + partition_temp["number"] = tonumber(partition_temp["number"]) or -1 + if partition_temp["fs"] == "free" then + partition_temp["number"] = -1 + partition_temp["fs"] = "Free Space" + partition_temp["name"] = "-" + elseif device:match("sd") or device:match("sata") then + partition_temp["name"] = device..partition_temp["number"] + elseif device:match("mmcblk") or device:match("md") or device:match("nvme") then + partition_temp["name"] = device.."p"..partition_temp["number"] + end + if partition_temp["number"] > 0 and partition_temp["fs"] == "" and d.command.lsblk then + partition_temp["fs"] = luci.util.exec(d.command.lsblk .. " /dev/"..device.. tostring(partition_temp["number"]) .. " -no fstype"):match("([^%s]+)") or "" + end + partition_temp["fs"] = partition_temp["fs"] == "" and "raw" or partition_temp["fs"] + partition_temp["sec_start"] = partition_temp["sec_start"] and partition_temp["sec_start"]:sub(1,-2) + partition_temp["sec_end"] = partition_temp["sec_end"] and partition_temp["sec_end"]:sub(1,-2) + partition_temp["mount_point"] = partition_temp["name"]~="-" and get_mount_point(partition_temp["name"]) or "-" + if partition_temp["mount_point"]~="-" then + partition_temp["used"], partition_temp["free"], partition_temp["usage"] = get_partition_usage(partition_temp["name"]) + partition_temp["used_formated"] = partition_temp["used"] and byte_format(partition_temp["used"]) or "-" + partition_temp["free_formated"] = partition_temp["free"] and byte_format(partition_temp["free"]) or "-" + else + partition_temp["used"], partition_temp["free"], partition_temp["usage"] = 0,0,"-" + partition_temp["used_formated"] = "-" + partition_temp["free_formated"] = "-" + end + -- if disk_temp["p_table"] == "MBR" and (partition_temp["number"] < 4) and (partition_temp["number"] > 0) then + -- local real_size_sec = tonumber(nixio.fs.readfile("/sys/block/"..device.."/"..partition_temp["name"].."/size")) * tonumber(disk_temp.phy_sec) + -- if real_size_sec ~= partition_temp["size"] then + -- disk_temp["extended_partition_index"] = partition_temp["number"] + -- partition_temp["type"] = "extended" + -- partition_temp["size"] = real_size_sec + -- partition_temp["fs"] = "-" + -- partition_temp["logicals"] = {} + -- else + -- partition_temp["type"] = "primary" + -- end + -- end + + table.insert(partitions_temp, partition_temp) + end + end + if disk_temp and disk_temp["p_table"] == "MBR" then + for i, p in ipairs(partitions_temp) do + if disk_temp["extended_partition_index"] and p["number"] > 4 then + if tonumber(p["sec_end"]) <= tonumber(partitions_temp[disk_temp["extended_partition_index"]]["sec_end"]) and tonumber(p["sec_start"]) >= tonumber(partitions_temp[disk_temp["extended_partition_index"]]["sec_start"]) then + p["type"] = "logical" + table.insert(partitions_temp[disk_temp["extended_partition_index"]]["logicals"], i) + end + elseif (p["number"] < 4) and (p["number"] > 0) then + local s = nixio.fs.readfile("/sys/block/"..device.."/"..p["name"].."/size") + if s then + local real_size_sec = tonumber(s) * tonumber(disk_temp.phy_sec) + -- if size not equal, it's an extended + if real_size_sec ~= p["size"] then + disk_temp["extended_partition_index"] = i + p["type"] = "extended" + p["size"] = real_size_sec + p["fs"] = "-" + p["logicals"] = {} + else + p["type"] = "primary" + end + else + -- if not found in "/sys/block" + p["type"] = "primary" + end + end + end + end + result = disk_temp + result.partitions = partitions_temp + + return result +end + +local mddetail = function(mdpath) + local detail = {} + local path = mdpath:match("^/dev/md%d+$") + if path then + local mdadm = io.popen(d.command.mdadm .. " --detail "..path, "r") + for line in mdadm:lines() do + local key, value = line:match("^%s*(.+) : (.+)") + if key then + detail[key] = value + end + end + mdadm:close() + end + return detail +end + +-- return {{device="", mount_points="", fs="", mount_options="", dump="", pass=""}..} +d.get_mount_points = function() + local mount + local res = {} + local h ={"device", "mount_point", "fs", "mount_options", "dump", "pass"} + for mount in proc_mounts:gmatch("[^\n]+") do + local device = mount:match("^([^%s]+)%s+.+") + -- only show /dev/xxx device + if device and device:match("/dev/") then + res[#res+1] = {} + local i = 0 + for v in mount:gmatch("[^%s]+") do + i = i + 1 + res[#res][h[i]] = v + end + end + end + return res +end + +d.get_disk_info = function(device, wakeup) + --[[ return: + { + path, model, sn, size, size_mounted, flags, type, temp, p_table, logic_sec, phy_sec, sec_size, sata_ver, rota_rate, status, health, + partitions = { + 1 = { number, name, sec_start, sec_end, size, size_mounted, fs, tag_name, type, flags, mount_point, usage, used, free, used_formated, free_formated}, + 2 = { number, name, sec_start, sec_end, size, size_mounted, fs, tag_name, type, flags, mount_point, usage, used, free, used_formated, free_formated}, + ... + } + --raid devices only + level, members, members_str + } + --]] + if not device then return end + local disk_info + local smart_info = get_smart_info(device) + + -- check if divice is the member of raid + smart_info["p_table"] = is_raid_member(device..'0') + -- if status is not active(standby), only check smart_info. + -- if only weakup == true, weakup the disk and check parted_info. + if smart_info.status ~= "STANDBY" or wakeup or (smart_info["p_table"] and not smart_info["p_table"]:match("Raid")) or device:match("^md") then + disk_info = get_parted_info(device) + disk_info["sec_size"] = disk_info["logic_sec"] .. "/" .. disk_info["phy_sec"] + disk_info["size_formated"] = byte_format(tonumber(disk_info["size"])) + -- if status is standby, after get part info, the disk is weakuped, then get smart_info again for more informations + if smart_info.status ~= "ACTIVE" then smart_info = get_smart_info(device) end + else + disk_info = {} + end + + for k, v in pairs(smart_info) do + disk_info[k] = v + end + + if disk_info.type and disk_info.type:match("md") then + local raid_info = d.list_raid_devices()[disk_info["path"]:match("/dev/(.+)")] + for k, v in pairs(raid_info) do + disk_info[k] = v + end + end + return disk_info +end + +d.list_raid_devices = function() + local fs = require "nixio.fs" + + local raid_devices = {} + if not fs.access("/proc/mdstat") then return raid_devices end + local mdstat = io.open("/proc/mdstat", "r") + for line in mdstat:lines() do + + -- md1 : active raid1 sdb2[1] sda2[0] + -- md127 : active raid5 sdh1[6] sdg1[4] sdf1[3] sde1[2] sdd1[1] sdc1[0] + local device_info = {} + local mdpath, list = line:match("^(md%d+) : (.+)") + if mdpath then + local members = {} + for member in string.gmatch(list, "%S+") do + member_path = member:match("^(%S+)%[%d+%]") + if member_path then + member = '/dev/'..member_path + end + table.insert(members, member) + end + local active = table.remove(members, 1) + local level = "-" + if active == "active" then + level = table.remove(members, 1) + end + + local size = tonumber(fs.readfile(string.format("/sys/class/block/%s/size", mdpath))) + local ss = tonumber(fs.readfile(string.format("/sys/class/block/%s/queue/logical_block_size", mdpath))) + + device_info["path"] = "/dev/"..mdpath + device_info["size"] = size*ss + device_info["size_formated"] = byte_format(size*ss) + device_info["active"] = active:upper() + device_info["level"] = level + device_info["members"] = members + device_info["members_str"] = table.concat(members, ", ") + + -- Get more info from output of mdadm --detail + local detail = mddetail(device_info["path"]) + device_info["status"] = detail["State"]:upper() + + raid_devices[mdpath] = device_info + end + end + mdstat:close() + + return raid_devices +end + +-- Collect Devices information + --[[ return: + { + sda={ + path, model, inuse, size_formated, + partitions={ + { name, inuse, size_formated } + ... + } + } + .. + } + --]] +d.list_devices = function() + local fs = require "nixio.fs" + + -- get all device names (sdX and mmcblkX) + local target_devnames = {} + for dev in fs.dir("/dev") do + if dev:match("^sd[a-z]$") + or dev:match("^mmcblk%d+$") + or dev:match("^sata[a-z]$") + or dev:match("^nvme%d+n%d+$") + then + table.insert(target_devnames, dev) + end + end + + local devices = {} + for i, bname in pairs(target_devnames) do + local device_info = {} + local device = "/dev/" .. bname + local size = tonumber(fs.readfile(string.format("/sys/class/block/%s/size", bname)) or "0") + local ss = tonumber(fs.readfile(string.format("/sys/class/block/%s/queue/logical_block_size", bname)) or "0") + local model = fs.readfile(string.format("/sys/class/block/%s/device/model", bname)) + local partitions = {} + for part in nixio.fs.glob("/sys/block/" .. bname .."/" .. bname .. "*") do + local pname = nixio.fs.basename(part) + local psize = byte_format(tonumber(nixio.fs.readfile(part .. "/size"))*ss) + local mount_point = get_mount_point(pname) + if mount_point then device_info["inuse"] = true end + table.insert(partitions, {name = pname, size_formated = psize, inuse = mount_point}) + end + + device_info["path"] = device + device_info["size_formated"] = byte_format(size*ss) + device_info["model"] = model + device_info["partitions"] = partitions + -- true or false + device_info["inuse"] = device_info["inuse"] or get_mount_point(bname) + + local udevinfo = {} + if luci.sys.exec("which udevadm") ~= "" then + local udevadm = io.popen("udevadm info --query=property --name="..device) + for attr in udevadm:lines() do + local k, v = attr:match("(%S+)=(%S+)") + udevinfo[k] = v + end + udevadm:close() + + device_info["info"] = udevinfo + if udevinfo["ID_MODEL"] then device_info["model"] = udevinfo["ID_MODEL"] end + end + devices[bname] = device_info + end + -- luci.util.perror(luci.util.serialize_json(devices)) + return devices +end + +-- get formart cmd +d.get_format_cmd = function() + local AVAILABLE_FMTS = { + ext2 = { cmd = "mkfs.ext2", option = "-F -E lazy_itable_init=1" }, + ext3 = { cmd = "mkfs.ext3", option = "-F -E lazy_itable_init=1" }, + ext4 = { cmd = "mkfs.ext4", option = "-F -E lazy_itable_init=1" }, + fat32 = { cmd = "mkfs.vfat", option = "-F" }, + exfat = { cmd = "mkexfat", option = "-f" }, + hfsplus = { cmd = "mkhfs", option = "-f" }, + ntfs = { cmd = "mkntfs", option = "-f" }, + swap = { cmd = "mkswap", option = "" }, + btrfs = { cmd = "mkfs.btrfs", option = "-f" } + } + result = {} + for fmt, obj in pairs(AVAILABLE_FMTS) do + local cmd = luci.sys.exec("/usr/bin/which " .. obj["cmd"]) + if cmd:match(obj["cmd"]) then + result[fmt] = { cmd = cmd:match("^.+"..obj["cmd"]) ,option = obj["option"] } + end + end + return result +end + +d.create_raid = function(rname, rlevel, rmembers) + local mb = {} + for _, v in ipairs(rmembers) do + mb[v]=v + end + rmembers = {} + for _, v in pairs(mb) do + table.insert(rmembers, v) + end + if type(rname) == "string" then + if rname:match("^md%d-%s+") then + rname = "/dev/"..rname:match("^(md%d-)%s+") + elseif rname:match("^/dev/md%d-%s+") then + rname = "/dev/"..rname:match("^(/dev/md%d-)%s+") + elseif not rname:match("/") then + rname = "/dev/md/".. rname + else + return "ERR: Invalid raid name" + end + else + local mdnum = 0 + for num=1,127 do + local md = io.open("/dev/md"..tostring(num), "r") + if md == nil then + mdnum = num + break + else + io.close(md) + end + end + if mdnum == 0 then return "ERR: Cannot find proper md number" end + rname = "/dev/md"..mdnum + end + + if rlevel == "5" or rlevel == "6" then + if #rmembers < 3 then return "ERR: Not enough members" end + end + if rlevel == "10" then + if #rmembers < 4 then return "ERR: Not enough members" end + end + if #rmembers < 2 then return "ERR: Not enough members" end + local cmd = d.command.mdadm .. " --create "..rname.." --run --assume-clean --homehost=any --level=" .. rlevel .. " --raid-devices=" .. #rmembers .. " " .. table.concat(rmembers, " ") + local res = luci.util.exec(cmd) + return res +end + +d.gen_mdadm_config = function() + if not nixio.fs.access("/etc/config/mdadm") then return end + local uci = require "luci.model.uci" + local x = uci.cursor() + -- delete all array sections + x:foreach("mdadm", "array", function(s) x:delete("mdadm",s[".name"]) end) + local cmd = d.command.mdadm .. " -D -s" + --ARRAY /dev/md1 metadata=1.2 name=any:1 UUID=f998ae14:37621b27:5c49e850:051f6813 + --ARRAY /dev/md3 metadata=1.2 name=any:3 UUID=c068c141:4b4232ca:f48cbf96:67d42feb + for _, v in ipairs(luci.util.execl(cmd)) do + local device, uuid = v:match("^ARRAY%s-([^%s]+)%s-[^%s]-%s-[^%s]-%s-UUID=([^%s]+)%s-") + if device and uuid then + local section_name = x:add("mdadm", "array") + x:set("mdadm", section_name, "device", device) + x:set("mdadm", section_name, "uuid", uuid) + end + end + x:commit("mdadm") + -- enable mdadm + luci.util.exec("/etc/init.d/mdadm enable") +end + +-- list btrfs filesystem device +-- {uuid={uuid, label, members, size, used}...} +d.list_btrfs_devices = function() + local btrfs_device = {} + if not d.command.btrfs then return btrfs_device end + local line, _uuid + for _, line in ipairs(luci.util.execl(d.command.btrfs .. " filesystem show -d --raw")) + do + local label, uuid = line:match("^Label:%s+([^%s]+)%s+uuid:%s+([^%s]+)") + if label and uuid then + _uuid = uuid + local _label = label:match("^'([^']+)'") + btrfs_device[_uuid] = {label = _label or label, uuid = uuid} + -- table.insert(btrfs_device, {label = label, uuid = uuid}) + end + local used = line:match("Total devices[%w%s]+used%s+(%d+)$") + if used then + btrfs_device[_uuid]["used"] = tonumber(used) + btrfs_device[_uuid]["used_formated"] = byte_format(tonumber(used)) + end + local size, device = line:match("devid[%w.%s]+size%s+(%d+)[%w.%s]+path%s+([^%s]+)$") + if size and device then + btrfs_device[_uuid]["size"] = btrfs_device[_uuid]["size"] and btrfs_device[_uuid]["size"] + tonumber(size) or tonumber(size) + btrfs_device[_uuid]["size_formated"] = byte_format(btrfs_device[_uuid]["size"]) + btrfs_device[_uuid]["members"] = btrfs_device[_uuid]["members"] and btrfs_device[_uuid]["members"]..", "..device or device + end + end + return btrfs_device +end + +d.create_btrfs = function(blabel, blevel, bmembers) + -- mkfs.btrfs -L label -d blevel /dev/sda /dev/sdb + if not d.command.btrfs or type(bmembers) ~= "table" or next(bmembers) == nil then return "ERR no btrfs support or no members" end + local label = blabel and " -L " .. blabel or "" + local cmd = "mkfs.btrfs -f " .. label .. " -d " .. blevel .. " " .. table.concat(bmembers, " ") + return luci.util.exec(cmd) +end + +-- get btrfs info +-- {uuid, label, members, data_raid_level,metadata_raid_lavel, size, used, size_formated, used_formated, free, free_formated, usage} +d.get_btrfs_info = function(m_point) + local btrfs_info = {} + if not m_point or not d.command.btrfs then return btrfs_info end + local cmd = d.command.btrfs .. " filesystem show --raw " .. m_point + local _, line, uuid, _label, members + for _, line in ipairs(luci.util.execl(cmd)) do + if not uuid and not _label then + _label, uuid = line:match("^Label:%s+([^%s]+)%s+uuid:%s+([^s]+)") + else + local mb = line:match("%s+devid.+path%s+([^%s]+)") + if mb then + members = members and (members .. ", ".. mb) or mb + end + end + end + + if not _label or not uuid then return btrfs_info end + local label = _label:match("^'([^']+)'") + cmd = d.command.btrfs .. " filesystem usage -b " .. m_point + local used, free, data_raid_level, metadata_raid_lavel + for _, line in ipairs(luci.util.execl(cmd)) do + if not used then + used = line:match("^%s+Used:%s+(%d+)") + elseif not free then + free = line:match("^%s+Free %(estimated%):%s+(%d+)") + elseif not data_raid_level then + data_raid_level = line:match("^Data,%s-(%w+)") + elseif not metadata_raid_lavel then + metadata_raid_lavel = line:match("^Metadata,%s-(%w+)") + end + end + if used and free and data_raid_level and metadata_raid_lavel then + used = tonumber(used) + free = tonumber(free) + btrfs_info = { + uuid = uuid, + label = label, + data_raid_level = data_raid_level, + metadata_raid_lavel = metadata_raid_lavel, + used = used, + free = free, + size = used + free, + size_formated = byte_format(used + free), + used_formated = byte_format(used), + free_formated = byte_format(free), + members = members, + usage = string.format("%.2f",(used / (free+used) * 100)) .. "%" + } + end + return btrfs_info +end + +-- get btrfs subvolume +-- {id={id, gen, top_level, path, snapshots, otime, default_subvolume}...} +d.get_btrfs_subv = function(m_point, snapshot) +local subvolume = {} +if not m_point or not d.command.btrfs then return subvolume end + +-- get default subvolume +local cmd = d.command.btrfs .. " subvolume get-default " .. m_point +local res = luci.util.exec(cmd) +local default_subvolume_id = res:match("^ID%s+([^%s]+)") + +-- get the root subvolume +if not snapshot then + local _, line, section_snap, _uuid, _otime, _id, _snap + cmd = d.command.btrfs .. " subvolume show ".. m_point + for _, line in ipairs(luci.util.execl(cmd)) do + if not section_snap then + if not _uuid then + _uuid = line:match("^%s-UUID:%s+([^%s]+)") + elseif not _otime then + _otime = line:match("^%s+Creation time:%s+(.+)") + elseif not _id then + _id = line:match("^%s+Subvolume ID:%s+([^%s]+)") + elseif line:match("^%s+(Snapshot%(s%):)") then + section_snap = true + end + else + local snapshot = line:match("^%s+(.+)") + if snapshot then + _snap = _snap and (_snap ..", /".. snapshot) or ("/"..snapshot) + end + end + end + if _uuid and _otime and _id then + subvolume["0".._id] = {id = _id , uuid = _uuid, otime = _otime, snapshots = _snap, path = "/"} + if default_subvolume_id == _id then + subvolume["0".._id].default_subvolume = 1 + end + end +end + +-- get subvolume of btrfs +cmd = d.command.btrfs .. " subvolume list -gcu" .. (snapshot and "s " or " ") .. m_point +for _, line in ipairs(luci.util.execl(cmd)) do + -- ID 259 gen 11 top level 258 uuid 26ae0c59-199a-cc4d-bd58-644eb4f65d33 path 1a/2b' + local id, gen, top_level, uuid, path, otime, otime2 + if snapshot then + id, gen, top_level, otime, otime2, uuid, path = line:match("^ID%s+([^%s]+)%s+gen%s+([^%s]+)%s+cgen.-top level%s+([^%s]+)%s+otime%s+([^%s]+)%s+([^%s]+)%s+uuid%s+([^%s]+)%s+path%s+([^%s]+)%s-$") + else + id, gen, top_level, uuid, path = line:match("^ID%s+([^%s]+)%s+gen%s+([^%s]+)%s+cgen.-top level%s+([^%s]+)%s+uuid%s+([^%s]+)%s+path%s+([^%s]+)%s-$") + end + if id and gen and top_level and uuid and path then + subvolume[id] = {id = id, gen = gen, top_level = top_level, otime = (otime and otime or "") .." ".. (otime2 and otime2 or ""), uuid = uuid, path = '/'.. path} + if not snapshot then + -- use btrfs subv show to get snapshots + local show_cmd = d.command.btrfs .. " subvolume show "..m_point.."/"..path + local __, line_show, section_snap + for __, line_show in ipairs(luci.util.execl(show_cmd)) do + if not section_snap then + local create_time = line_show:match("^%s+Creation time:%s+(.+)") + if create_time then + subvolume[id]["otime"] = create_time + elseif line_show:match("^%s+(Snapshot%(s%):)") then + section_snap = "true" + end + else + local snapshot = line_show:match("^%s+(.+)") + subvolume[id]["snapshots"] = subvolume[id]["snapshots"] and (subvolume[id]["snapshots"] .. ", /".. snapshot) or ("/"..snapshot) + end + end + end + end +end +if subvolume[default_subvolume_id] then + subvolume[default_subvolume_id].default_subvolume = 1 +end +-- if m_point == "/tmp/.btrfs_tmp" then +-- luci.util.exec("umount " .. m_point) +-- end +return subvolume +end + +d.format_partition = function(partition, fs) + local partition_name = "/dev/".. partition + if not nixio.fs.access(partition_name) then + return 500, "Partition NOT found!" + end + + local format_cmd = d.get_format_cmd() + if not format_cmd[fs] then + return 500, "Filesystem NOT support!" + end + local cmd = format_cmd[fs].cmd .. " " .. format_cmd[fs].option .. " " .. partition_name + local res = luci.util.exec(cmd .. " 2>&1") + if res and res:lower():match("error+") then + return 500, res + else + return 200, "OK" + end +end + +return d diff --git a/luci-app-diskman/luasrc/view/diskman/cbi/disabled_button.htm b/luci-app-diskman/luasrc/view/diskman/cbi/disabled_button.htm new file mode 100644 index 000000000..1ad4eca3b --- /dev/null +++ b/luci-app-diskman/luasrc/view/diskman/cbi/disabled_button.htm @@ -0,0 +1,7 @@ +<%+cbi/valueheader%> + <% if self:cfgvalue(section) ~= false then %> + " type="submit"<%= attr("name", cbid) .. attr("id", cbid) .. attr("value", self.inputtitle or self.title)%> <% if self.view_disabled then %> disabled <% end %>/> + <% else %> + - + <% end %> +<%+cbi/valuefooter%> \ No newline at end of file diff --git a/luci-app-diskman/luasrc/view/diskman/cbi/format_button.htm b/luci-app-diskman/luasrc/view/diskman/cbi/format_button.htm new file mode 100644 index 000000000..18e306e27 --- /dev/null +++ b/luci-app-diskman/luasrc/view/diskman/cbi/format_button.htm @@ -0,0 +1,7 @@ +<%+cbi/valueheader%> + <% if self:cfgvalue(section) ~= false then %> + " onclick="event.preventDefault();partition_format('<%=self.partitions[section].name%>', '<%=self.format_cmd%>', '<%=self.inputtitle%>');" type="submit"<%= attr("name", cbid) .. attr("id", cbid) .. attr("value", self.inputtitle or self.title)%> <% if self.view_disabled then %> disabled <% end %>/> + <% else %> + - + <% end %> +<%+cbi/valuefooter%> diff --git a/luci-app-diskman/luasrc/view/diskman/cbi/inlinebutton.htm b/luci-app-diskman/luasrc/view/diskman/cbi/inlinebutton.htm new file mode 100644 index 000000000..b1b193257 --- /dev/null +++ b/luci-app-diskman/luasrc/view/diskman/cbi/inlinebutton.htm @@ -0,0 +1,7 @@ +
+ <% if self:cfgvalue(section) ~= false then %> + " type="submit"" <% if self.disable then %>disabled <% end %><%= attr("name", cbid) .. attr("id", cbid) .. attr("value", self.inputtitle or self.title)%> /> + <% else %> + - + <% end %> +
diff --git a/luci-app-diskman/luasrc/view/diskman/cbi/xnullsection.htm b/luci-app-diskman/luasrc/view/diskman/cbi/xnullsection.htm new file mode 100644 index 000000000..69aa65e00 --- /dev/null +++ b/luci-app-diskman/luasrc/view/diskman/cbi/xnullsection.htm @@ -0,0 +1,37 @@ +
+ <% if self.title and #self.title > 0 then -%> + <%=self.title%> + <%- end %> + <% if self.description and #self.description > 0 then -%> +
<%=self.description%>
+ <%- end %> +
+
+ <% self:render_children(1, scope or {}) %> +
+ <% if self.error and self.error[1] then -%> +
+
    <% for _, e in ipairs(self.error[1]) do -%> +
  • + <%- if e == "invalid" then -%> + <%:One or more fields contain invalid values!%> + <%- elseif e == "missing" then -%> + <%:One or more required fields have no value!%> + <%- else -%> + <%=pcdata(e)%> + <%- end -%> +
  • + <%- end %>
+
+ <%- end %> +
+
+<%- + if type(self.hidden) == "table" then + for k, v in pairs(self.hidden) do +-%> + +<%- + end + end +%> \ No newline at end of file diff --git a/luci-app-diskman/luasrc/view/diskman/cbi/xsimpleform.htm b/luci-app-diskman/luasrc/view/diskman/cbi/xsimpleform.htm new file mode 100644 index 000000000..a831bfc77 --- /dev/null +++ b/luci-app-diskman/luasrc/view/diskman/cbi/xsimpleform.htm @@ -0,0 +1,88 @@ +<% if not self.embedded then %> +
> + + + <% + end + + %>
<% + + if self.title and #self.title > 0 then + %>

<%=self.title%>

<% + end + + if self.description and #self.description > 0 then + %>
<%=self.description%>
<% + end + + self:render_children() + + %>
<% + + if self.message then + %>
<%=self.message%>
<% + end + + if self.errmessage then + %>
<%=self.errmessage%>
<% + end + + if not self.embedded then + if type(self.hidden) == "table" then + local k, v + for k, v in pairs(self.hidden) do + %><% + end + end + + local display_back = (self.redirect) + local display_cancel = (self.cancel ~= false and self.on_cancel) + local display_skip = (self.flow and self.flow.skip) + local display_submit = (self.submit ~= false) + local display_reset = (self.reset ~= false) + + if display_back or display_cancel or display_skip or display_submit or display_reset then + %>
<% + + if display_back then + %> <% + end + + if display_cancel then + local label = pcdata(self.cancel or translate("Cancel")) + %> <% + end + + if display_skip then + %> <% + end + + if display_submit then + local label = pcdata(self.submit or translate("Submit")) + %> <% + end + + if display_reset then + local label = pcdata(self.reset or translate("Reset")) + %> <% + end + + %>
<% + end + + %>
<% + end +%> + + diff --git a/luci-app-diskman/luasrc/view/diskman/disk_info.htm b/luci-app-diskman/luasrc/view/diskman/disk_info.htm new file mode 100644 index 000000000..118acd50d --- /dev/null +++ b/luci-app-diskman/luasrc/view/diskman/disk_info.htm @@ -0,0 +1,108 @@ + diff --git a/luci-app-diskman/luasrc/view/diskman/partition_info.htm b/luci-app-diskman/luasrc/view/diskman/partition_info.htm new file mode 100644 index 000000000..78f5c1bd7 --- /dev/null +++ b/luci-app-diskman/luasrc/view/diskman/partition_info.htm @@ -0,0 +1,129 @@ + + \ No newline at end of file diff --git a/luci-app-diskman/luasrc/view/diskman/smart_detail.htm b/luci-app-diskman/luasrc/view/diskman/smart_detail.htm new file mode 100644 index 000000000..56a9139f0 --- /dev/null +++ b/luci-app-diskman/luasrc/view/diskman/smart_detail.htm @@ -0,0 +1,79 @@ + + + S.M.A.R.T detail of <%=dev%> + + + + +
+
+ <%:S.M.A.R.T Attrbutes%>: /dev/<%=dev%> + + + <% if dev:match("nvme") then %> + + <% else %> + + + + + + + + + + <% end %> + + + + +
<%:ID%><%:Attrbute%><%:Flag%><%:Value%><%:Worst%><%:Thresh%><%:Type%><%:Updated%><%:Raw%>

<%:Collecting data...%>
+
+
+ + \ No newline at end of file diff --git a/luci-app-diskman/po/zh-cn/diskman.po b/luci-app-diskman/po/zh-cn/diskman.po new file mode 100644 index 000000000..f380fc586 --- /dev/null +++ b/luci-app-diskman/po/zh-cn/diskman.po @@ -0,0 +1,239 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8\n" + +msgid "DiskMan" +msgstr "DiskMan 磁盘管理" + +msgid "Manage Disks over LuCI." +msgstr "通过 LuCI 管理磁盘" + +msgid "Rescan Disks" +msgstr "重新扫描磁盘" + +msgid "Disks" +msgstr "磁盘" + +msgid "Path" +msgstr "路径" + +msgid "Serial Number" +msgstr "序列号" + +msgid "Temp" +msgstr "温度" + +msgid "Partition Table" +msgstr "分区表" + +msgid "SATA Version" +msgstr "SATA 版本" + +msgid "Health" +msgstr "健康" + +msgid "File System" +msgstr "文件系统" + +msgid "Mount Options" +msgstr "挂载选项" + +msgid "Mount" +msgstr "挂载" + +msgid "Umount" +msgstr "卸载" + +msgid "Eject" +msgstr "弹出" + +msgid "New" +msgstr "创建" + +msgid "Remove" +msgstr "移除" + +msgid "Format" +msgstr "格式化" + +msgid "Start Sector" +msgstr "起始扇区" + +msgid "End Sector" +msgstr "中止扇区" + +msgid "Usage" +msgstr "用量" + +msgid "Used" +msgstr "已使用" + +msgid "Free Space" +msgstr "空闲空间" + +msgid "Model" +msgstr "型号" + +msgid "Size" +msgstr "容量" + +msgid "Status" +msgstr "状态" + +msgid "Mount Point" +msgstr "挂载点" + +msgid "Sector Size" +msgstr "扇区/物理扇区大小" + +msgid "Rotation Rate" +msgstr "转速" + +msgid "RAID Devices" +msgstr "RAID 设备" + +msgid "RAID mode" +msgstr "RAID 模式" + +msgid "Members" +msgstr "成员" + +msgid "Active" +msgstr "活动" + +msgid "RAID Creation" +msgstr "RAID 创建" + +msgid "Raid Name" +msgstr "RAID 名称" + +msgid "Raid Level" +msgstr "RAID 级别" + +msgid "Raid Member" +msgstr "磁盘阵列成员" + +msgid "Create Raid" +msgstr "创建 RAID" + +msgid "Partition Management" +msgstr "分区管理" + +msgid "Partition Disk over LuCI." +msgstr "通过LuCI分区磁盘。" + +msgid "Device Info" +msgstr "设备信息" + +msgid "Disk Man" +msgstr "磁盘管理" + +msgid "Partitions Info" +msgstr "分区信息" + +msgid "Default 2048 sector alignment, support +size{b,k,m,g,t} in End Sector" +msgstr "默认2048扇区对齐,【中止扇区】支持 +容量{b,k,m,g,t} 格式,例:+500m +10g +1t" + +msgid "Multiple Devices Btrfs Creation" +msgstr "Btrfs 阵列创建" + +msgid "Label" +msgstr "卷标" + +msgid "Btrfs Label" +msgstr "Btrfs 卷标" + +msgid "Btrfs Raid Level" +msgstr "Btrfs Raid 级别" + +msgid "Btrfs Member" +msgstr "Btrfs 整列成员" + +msgid "Create Btrfs" +msgstr "创建 Btrfs" + +msgid "New Snapshot" +msgstr "新建快照" + +msgid "SubVolumes" +msgstr "子卷" + +msgid "Top Level" +msgstr "父ID" + +msgid "Manage Btrfs" +msgstr "Btrfs 管理" + +msgid "Otime" +msgstr "创建时间" + +msgid "Snapshots" +msgstr "快照" + +msgid "Set Default" +msgstr "默认子卷" + +msgid "Source Path" +msgstr "源目录" + +msgid "Readonly" +msgstr "只读" + +msgid "Delete" +msgstr "删除" + +msgid "Create" +msgstr "创建" + +msgid "Destination Path (optional)" +msgstr "目标目录(可选)" + +msgid "Metadata" +msgstr "元数据" + +msgid "Data" +msgstr "数据" + +msgid "Btrfs Info" +msgstr "Btrfs 信息" + +msgid "The source path for create the snapshot" +msgstr "创建快照的源数据目录" + +msgid "The path where you want to store the snapshot" +msgstr "存放快照数据目录" + +msgid "Please input Source Path of snapshot, Source Path must start with '/'" +msgstr "请输入快照源路径,源路径必须以'/'开头" + +msgid "Please input Subvolume Path, Subvolume must start with '/'" +msgstr "请输入子卷路径,子卷路径必须以'/'开头" + +msgid "is in use! please unmount it first!" +msgstr "正在被使用!请先卸载!" + +msgid "Partition NOT found!" +msgstr "分区未找到!" + +msgid "Filesystem NOT support!" +msgstr "文件系统不支持!" + +msgid "Invalid Start Sector!" +msgstr "无效的起始扇区!" + +msgid "Invalid End Sector" +msgstr "无效的终止扇区!" + +msgid "Partition not exists!" +msgstr "分区不存在!" + +msgid "Creation" +msgstr "创建" + +msgid "Please select file system!" +msgstr "请选择文件系统!" + +msgid "Format partation:" +msgstr "格式化分区:" + +msgid "Warnning !! \nTHIS WILL OVERWRITE EXISTING PARTITIONS!! \nModify the partition table?" +msgstr "警告!!\n此操作会覆盖现有分区\n确定修改分区表?" diff --git a/luci-app-diskman/po/zh_Hans b/luci-app-diskman/po/zh_Hans new file mode 100644 index 000000000..41451e4a1 --- /dev/null +++ b/luci-app-diskman/po/zh_Hans @@ -0,0 +1 @@ +zh-cn \ No newline at end of file diff --git a/luci-app-dockerman/Makefile b/luci-app-dockerman/Makefile new file mode 100644 index 000000000..51dfa5c09 --- /dev/null +++ b/luci-app-dockerman/Makefile @@ -0,0 +1,21 @@ +include $(TOPDIR)/rules.mk + +LUCI_TITLE:=LuCI Support for docker +LUCI_DEPENDS:=@(aarch64||arm||x86_64) \ + +luci-compat \ + +luci-lib-docker \ + +luci-lib-ip \ + +docker \ + +dockerd \ + +ttyd +LUCI_PKGARCH:=all + +PKG_LICENSE:=AGPL-3.0 +PKG_MAINTAINER:=lisaac \ + Florian Eckert + +PKG_VERSION:=v0.5.25 + +include $(TOPDIR)/feeds/luci/luci.mk + +# call BuildPackage - OpenWrt buildroot signature diff --git a/luci-app-dockerman/depends.lst b/luci-app-dockerman/depends.lst new file mode 100644 index 000000000..8a62f6a74 --- /dev/null +++ b/luci-app-dockerman/depends.lst @@ -0,0 +1 @@ +ttyd docker-cli \ No newline at end of file diff --git a/luci-app-dockerman/htdocs/luci-static/resources/dockerman/containers.svg b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/containers.svg new file mode 100644 index 000000000..4165f90bd --- /dev/null +++ b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/containers.svg @@ -0,0 +1,7 @@ + + + + + Docker icon + + diff --git a/luci-app-dockerman/htdocs/luci-static/resources/dockerman/file-icon.png b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/file-icon.png new file mode 100644 index 000000000..f156dc1c7 Binary files /dev/null and b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/file-icon.png differ diff --git a/luci-app-dockerman/htdocs/luci-static/resources/dockerman/file-manager.css b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/file-manager.css new file mode 100644 index 000000000..911693b62 --- /dev/null +++ b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/file-manager.css @@ -0,0 +1,91 @@ +.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; + } +} + +.cbi-section-table { + width: 100%; +} + +.cbi-section-table-cell { + text-align: right; +} + +.cbi-button-install { +border-color: #c44; + color: #c44; + margin-left: 3px; +} + +.cbi-value-field { + padding: 10px 0; +} + +.parent-icon { + height: 1.8rem; + padding: 10px 0; +} \ No newline at end of file diff --git a/luci-app-dockerman/htdocs/luci-static/resources/dockerman/folder-icon.png b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/folder-icon.png new file mode 100644 index 000000000..1370df3ad Binary files /dev/null and b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/folder-icon.png differ diff --git a/luci-app-dockerman/htdocs/luci-static/resources/dockerman/images.svg b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/images.svg new file mode 100644 index 000000000..90ca5a1c7 --- /dev/null +++ b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/images.svg @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/luci-app-dockerman/htdocs/luci-static/resources/dockerman/link-icon.png b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/link-icon.png new file mode 100644 index 000000000..03cc82cdf Binary files /dev/null and b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/link-icon.png differ diff --git a/luci-app-dockerman/htdocs/luci-static/resources/dockerman/networks.svg b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/networks.svg new file mode 100644 index 000000000..3eb12a393 --- /dev/null +++ b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/networks.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/luci-app-dockerman/htdocs/luci-static/resources/dockerman/tar.min.js b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/tar.min.js new file mode 100644 index 000000000..d9c06667f --- /dev/null +++ b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/tar.min.js @@ -0,0 +1,185 @@ +// https://github.com/thiscouldbebetter/TarFileExplorer +class TarFileTypeFlag +{constructor(value,name) +{this.value=value;this.id="_"+this.value;this.name=name;} +static _instances;static Instances() +{if(TarFileTypeFlag._instances==null) +{TarFileTypeFlag._instances=new TarFileTypeFlag_Instances();} +return TarFileTypeFlag._instances;}} +class TarFileTypeFlag_Instances +{constructor() +{this.Normal=new TarFileTypeFlag("0","Normal");this.HardLink=new TarFileTypeFlag("1","Hard Link");this.SymbolicLink=new TarFileTypeFlag("2","Symbolic Link");this.CharacterSpecial=new TarFileTypeFlag("3","Character Special");this.BlockSpecial=new TarFileTypeFlag("4","Block Special");this.Directory=new TarFileTypeFlag("5","Directory");this.FIFO=new TarFileTypeFlag("6","FIFO");this.ContiguousFile=new TarFileTypeFlag("7","Contiguous File");this.LongFilePath=new TarFileTypeFlag("L","././@LongLink");this._All=[this.Normal,this.HardLink,this.SymbolicLink,this.CharacterSpecial,this.BlockSpecial,this.Directory,this.FIFO,this.ContiguousFile,this.LongFilePath,];for(var i=0;ia+=String.fromCharCode(b),"");entryNext.header.fileName=entryNext.header.fileName.replace(/\0/g,"");entries.splice(i,1);i--;}}} +downloadAs(fileNameToSaveAs) +{return FileHelper.saveBytesAsFile +(this.toBytes(),fileNameToSaveAs)} +entriesForDirectories() +{return this.entries.filter(x=>x.header.typeFlag.name==TarFileTypeFlag.Instances().Directory);} +toBytes() +{this.toBytes_PrependLongPathEntriesAsNeeded();var fileAsBytes=[];var entriesAsByteArrays=this.entries.map(x=>x.toBytes());this.consolidateLongPathEntries();for(var i=0;imaxLength) +{var entryFileNameAsBytes=entryFileName.split("").map(x=>x.charCodeAt(0));var entryContainingLongPathToPrepend=TarFileEntry.fileNew +(typeFlagLongPath.name,entryFileNameAsBytes);entryContainingLongPathToPrepend.header.typeFlag=typeFlagLongPath;entryContainingLongPathToPrepend.header.timeModifiedInUnixFormat=entryHeader.timeModifiedInUnixFormat;entryContainingLongPathToPrepend.header.checksumCalculate();entryHeader.fileName=entryFileName.substr(0,maxLength)+String.fromCharCode(0);entries.splice(i,0,entryContainingLongPathToPrepend);i++;}}} +toString() +{var newline="\n";var returnValue="[TarFile]"+newline;for(var i=0;i{var fileLoadedAsBinaryString=fileLoadedEvent.target.result;var fileLoadedAsBytes=ByteHelper.stringUTF8ToBytes(fileLoadedAsBinaryString);callback(fileToLoad.name,fileLoadedAsBytes);} +fileReader.readAsBinaryString(fileToLoad);} +static loadFileAsText(fileToLoad,callback) +{var fileReader=new FileReader();fileReader.onload=(fileLoadedEvent)=>{var textFromFileLoaded=fileLoadedEvent.target.result;callback(fileToLoad.name,textFromFileLoaded);};fileReader.readAsText(fileToLoad);} +static saveBytesAsFile(bytesToWrite,fileNameToSaveAs) +{var bytesToWriteAsArrayBuffer=new ArrayBuffer(bytesToWrite.length);var bytesToWriteAsUIntArray=new Uint8Array(bytesToWriteAsArrayBuffer);for(var i=0;i + + + + + diff --git a/luci-app-dockerman/luasrc/controller/dockerman.lua b/luci-app-dockerman/luasrc/controller/dockerman.lua new file mode 100644 index 000000000..7aeed56e1 --- /dev/null +++ b/luci-app-dockerman/luasrc/controller/dockerman.lua @@ -0,0 +1,614 @@ +--[[ +LuCI - Lua Configuration Interface +Copyright 2019 lisaac +]]-- + +local docker = require "luci.model.docker" +-- local uci = (require "luci.model.uci").cursor() + +module("luci.controller.dockerman",package.seeall) + +function index() + entry({"admin", "docker"}, + alias("admin", "docker", "config"), + _("Docker"), + 40).acl_depends = { "luci-app-dockerman" } + + entry({"admin", "docker", "config"},cbi("dockerman/configuration"),_("Configuration"), 8).leaf=true + + -- local uci = (require "luci.model.uci").cursor() + -- if uci:get_bool("dockerd", "dockerman", "remote_endpoint") then + -- local host = uci:get("dockerd", "dockerman", "remote_host") + -- local port = uci:get("dockerd", "dockerman", "remote_port") + -- if not host or not port then + -- return + -- end + -- else + -- local socket = uci:get("dockerd", "dockerman", "socket_path") or "/var/run/docker.sock" + -- if socket and not nixio.fs.access(socket) then + -- return + -- end + -- end + + -- if (require "luci.model.docker").new():_ping().code ~= 200 then + -- return + -- end + + entry({"admin", "docker", "overview"}, form("dockerman/overview"),_("Overview"), 2).leaf=true + entry({"admin", "docker", "containers"}, form("dockerman/containers"), _("Containers"), 3).leaf=true + entry({"admin", "docker", "images"}, form("dockerman/images"), _("Images"), 4).leaf=true + entry({"admin", "docker", "networks"}, form("dockerman/networks"), _("Networks"), 5).leaf=true + entry({"admin", "docker", "volumes"}, form("dockerman/volumes"), _("Volumes"), 6).leaf=true + entry({"admin", "docker", "events"}, call("action_events"), _("Events"), 7) + + entry({"admin", "docker", "newcontainer"}, form("dockerman/newcontainer")).leaf=true + entry({"admin", "docker", "newnetwork"}, form("dockerman/newnetwork")).leaf=true + entry({"admin", "docker", "container"}, form("dockerman/container")).leaf=true + + entry({"admin", "docker", "container_stats"}, call("action_get_container_stats")).leaf=true + entry({"admin", "docker", "containers_stats"}, call("action_get_containers_stats")).leaf=true + entry({"admin", "docker", "get_system_df"}, call("action_get_system_df")).leaf=true + entry({"admin", "docker", "container_get_archive"}, call("download_archive")).leaf=true + entry({"admin", "docker", "container_put_archive"}, call("upload_archive")).leaf=true + entry({"admin", "docker", "container_list_file"}, call("list_file")).leaf=true + entry({"admin", "docker", "container_remove_file"}, call("remove_file")).leaf=true + entry({"admin", "docker", "container_rename_file"}, call("rename_file")).leaf=true + entry({"admin", "docker", "container_export"}, call("export_container")).leaf=true + entry({"admin", "docker", "images_save"}, call("save_images")).leaf=true + entry({"admin", "docker", "images_load"}, call("load_images")).leaf=true + entry({"admin", "docker", "images_import"}, call("import_images")).leaf=true + entry({"admin", "docker", "images_get_tags"}, call("get_image_tags")).leaf=true + entry({"admin", "docker", "images_tag"}, call("tag_image")).leaf=true + entry({"admin", "docker", "images_untag"}, call("untag_image")).leaf=true + entry({"admin", "docker", "confirm"}, call("action_confirm")).leaf=true +end + +function action_get_system_df() + local res = docker.new():df() + luci.http.status(res.code, res.message) + luci.http.prepare_content("application/json") + luci.http.write_json(res.body) +end + +function scandir(id, directory) + local cmd_docker = luci.util.exec("command -v docker"):match("^.+docker") or nil + if not cmd_docker or cmd_docker:match("^%s+$") then + return + end + local i, t, popen = 0, {}, io.popen + local uci = (require "luci.model.uci").cursor() + local remote = uci:get_bool("dockerd", "dockerman", "remote_endpoint") + local socket_path = not remote and uci:get("dockerd", "dockerman", "socket_path") or nil + local host = remote and uci:get("dockerd", "dockerman", "remote_host") or nil + local port = remote and uci:get("dockerd", "dockerman", "remote_port") or nil + if remote and host and port then + hosts = "tcp://" .. host .. ':'.. port + elseif socket_path then + hosts = "unix://" .. socket_path + else + return + end + local pfile = popen(cmd_docker .. ' -H "'.. hosts ..'" exec ' ..id .." ls -lh \""..directory.."\" | egrep -v '^total'") + for fileinfo in pfile:lines() do + i = i + 1 + t[i] = fileinfo + end + pfile:close() + return t +end + +function list_response(id, path, success) + luci.http.prepare_content("application/json") + local result + if success then + local rv = scandir(id, path) + result = { + ec = 0, + data = rv + } + else + result = { + ec = 1 + } + end + luci.http.write_json(result) +end + +function list_file(id) + local path = luci.http.formvalue("path") + list_response(id, path, true) +end + +function rename_file(id) + local filepath = luci.http.formvalue("filepath") + local newpath = luci.http.formvalue("newpath") + local cmd_docker = luci.util.exec("command -v docker"):match("^.+docker") or nil + if not cmd_docker or cmd_docker:match("^%s+$") then + return + end + local uci = (require "luci.model.uci").cursor() + local remote = uci:get_bool("dockerd", "dockerman", "remote_endpoint") + local socket_path = not remote and uci:get("dockerd", "dockerman", "socket_path") or nil + local host = remote and uci:get("dockerd", "dockerman", "remote_host") or nil + local port = remote and uci:get("dockerd", "dockerman", "remote_port") or nil + if remote and host and port then + hosts = "tcp://" .. host .. ':'.. port + elseif socket_path then + hosts = "unix://" .. socket_path + else + return + end + local success = os.execute(cmd_docker .. ' -H "'.. hosts ..'" exec '.. id ..' mv "'..filepath..'" "'..newpath..'"') + list_response(nixio.fs.dirname(filepath), success) +end + +function remove_file(id) + local path = luci.http.formvalue("path") + local isdir = luci.http.formvalue("isdir") + local cmd_docker = luci.util.exec("command -v docker"):match("^.+docker") or nil + if not cmd_docker or cmd_docker:match("^%s+$") then + return + end + local uci = (require "luci.model.uci").cursor() + local remote = uci:get_bool("dockerd", "dockerman", "remote_endpoint") + local socket_path = not remote and uci:get("dockerd", "dockerman", "socket_path") or nil + local host = remote and uci:get("dockerd", "dockerman", "remote_host") or nil + local port = remote and uci:get("dockerd", "dockerman", "remote_port") or nil + if remote and host and port then + hosts = "tcp://" .. host .. ':'.. port + elseif socket_path then + hosts = "unix://" .. socket_path + else + return + end + path = path:gsub("<>", "/") + path = path:gsub(" ", "\ ") + local success + if isdir then + success = os.execute(cmd_docker .. ' -H "'.. hosts ..'" exec '.. id ..' rm -r "'..path..'"') + else + success = os.remove(path) + end + list_response(nixio.fs.dirname(path), success) +end + +function action_events() + local logs = "" + local query ={} + + local dk = docker.new() + query["until"] = os.time() + local events = dk:events({query = query}) + + if events.code == 200 then + for _, v in ipairs(events.body) do + local date = "unknown" + if v and v.time then + date = os.date("%Y-%m-%d %H:%M:%S", v.time) + end + + local name = v.Actor.Attributes.name or "unknown" + local action = v.Action or "unknown" + + if v and v.Type == "container" then + local id = v.Actor.ID or "unknown" + logs = logs .. string.format("[%s] %s %s Container ID: %s Container Name: %s\n", date, v.Type, action, id, name) + elseif v.Type == "network" then + local container = v.Actor.Attributes.container or "unknown" + local network = v.Actor.Attributes.type or "unknown" + logs = logs .. string.format("[%s] %s %s Container ID: %s Network Name: %s Network type: %s\n", date, v.Type, action, container, name, network) + elseif v.Type == "image" then + local id = v.Actor.ID or "unknown" + logs = logs .. string.format("[%s] %s %s Image: %s Image name: %s\n", date, v.Type, action, id, name) + end + end + end + + luci.template.render("dockerman/logs", {self={syslog = logs, title="Events"}}) +end + +local calculate_cpu_percent = function(d) + if type(d) ~= "table" then + return + end + + local cpu_count = tonumber(d["cpu_stats"]["online_cpus"]) + local cpu_percent = 0.0 + local cpu_delta = tonumber(d["cpu_stats"]["cpu_usage"]["total_usage"]) - tonumber(d["precpu_stats"]["cpu_usage"]["total_usage"]) + local system_delta = tonumber(d["cpu_stats"]["system_cpu_usage"]) -- tonumber(d["precpu_stats"]["system_cpu_usage"]) + if system_delta > 0.0 then + cpu_percent = string.format("%.2f", cpu_delta / system_delta * 100.0 * cpu_count) + end + + return cpu_percent +end + +local get_memory = function(d) + if type(d) ~= "table" then + return + end + + -- local limit = string.format("%.2f", tonumber(d["memory_stats"]["limit"]) / 1024 / 1024) + -- local usage = string.format("%.2f", (tonumber(d["memory_stats"]["usage"]) - tonumber(d["memory_stats"]["stats"]["total_cache"])) / 1024 / 1024) + -- return usage .. "MB / " .. limit.. "MB" + + local limit =tonumber(d["memory_stats"]["limit"]) + local usage = tonumber(d["memory_stats"]["usage"]) + -- - tonumber(d["memory_stats"]["stats"]["total_cache"]) + + return usage, limit +end + +local get_rx_tx = function(d) + if type(d) ~="table" then + return + end + + local data = {} + if type(d["networks"]) == "table" then + for e, v in pairs(d["networks"]) do + data[e] = { + bw_tx = tonumber(v.tx_bytes), + bw_rx = tonumber(v.rx_bytes) + } + end + end + + return data +end + +local function get_stat(container_id) + if container_id then + local dk = docker.new() + local response = dk.containers:inspect({id = container_id}) + if response.code == 200 and response.body.State.Running then + response = dk.containers:stats({id = container_id, query = {stream = false, ["one-shot"] = true}}) + if response.code == 200 then + local container_stats = response.body + local cpu_percent = calculate_cpu_percent(container_stats) + local mem_useage, mem_limit = get_memory(container_stats) + local bw_rxtx = get_rx_tx(container_stats) + return response.code, response.body.message, { + cpu_percent = cpu_percent, + memory = { + mem_useage = mem_useage, + mem_limit = mem_limit + }, + bw_rxtx = bw_rxtx + } + else + return response.code, response.body.message + end + else + if response.code == 200 then + return 500, "container "..container_id.." not running" + else + return response.code, response.body.message + end + end + else + return 404, "No container name or id" + end +end +function action_get_container_stats(container_id) + local code, msg, res = get_stat(container_id) + luci.http.status(code, msg) + luci.http.prepare_content("application/json") + luci.http.write_json(res) +end + +function action_get_containers_stats() + local res = luci.http.formvalue(containers) or "" + local stats = {} + res = luci.jsonc.parse(res.containers) + if res and type(res) == "table" then + for i, v in ipairs(res) do + _,_,stats[v] = get_stat(v) + end + end + luci.http.status(200, "OK") + luci.http.prepare_content("application/json") + luci.http.write_json(stats) +end + +function action_confirm() + local data = docker:read_status() + if data then + data = data:gsub("\n","
"):gsub(" "," ") + code = 202 + msg = data + else + code = 200 + msg = "finish" + data = "finish" + end + + luci.http.status(code, msg) + luci.http.prepare_content("application/json") + luci.http.write_json({info = data}) +end + +function export_container(id) + local dk = docker.new() + local first + + local cb = function(res, chunk) + if res.code == 200 then + if not first then + first = true + luci.http.header('Content-Disposition', 'inline; filename="'.. id ..'.tar"') + luci.http.header('Content-Type', 'application\/x-tar') + end + luci.ltn12.pump.all(chunk, luci.http.write) + else + if not first then + first = true + luci.http.prepare_content("text/plain") + end + luci.ltn12.pump.all(chunk, luci.http.write) + end + end + + local res = dk.containers:export({id = id}, cb) +end + +function download_archive() + local id = luci.http.formvalue("id") + local path = luci.http.formvalue("path") + local filename = luci.http.formvalue("filename") or "archive" + local dk = docker.new() + local first + + local cb = function(res, chunk) + if res and res.code and res.code == 200 then + if not first then + first = true + luci.http.header('Content-Disposition', 'inline; filename="'.. filename .. '.tar"') + luci.http.header('Content-Type', 'application\/x-tar') + end + luci.ltn12.pump.all(chunk, luci.http.write) + else + if not first then + first = true + luci.http.status(res and res.code or 500, msg or "unknow") + luci.http.prepare_content("text/plain") + end + luci.ltn12.pump.all(chunk, luci.http.write) + end + end + + local res = dk.containers:get_archive({ + id = id, + query = { + path = luci.http.urlencode(path) + } + }, cb) +end + +function upload_archive(container_id) + local path = luci.http.formvalue("upload-path") + local dk = docker.new() + local ltn12 = require "luci.ltn12" + + local rec_send = function(sinkout) + luci.http.setfilehandler(function (meta, chunk, eof) + if chunk then + ltn12.pump.step(ltn12.source.string(chunk), sinkout) + end + end) + end + + local res = dk.containers:put_archive({ + id = container_id, + query = { + path = luci.http.urlencode(path) + }, + body = rec_send + }) + + local msg = res and res.message or res.body and res.body.message or nil + luci.http.status(res and res.code or 500, msg or "unknow") + luci.http.prepare_content("application/json") + luci.http.write_json({message = msg or "unknow"}) +end + +-- function save_images() +-- local names = luci.http.formvalue("names") +-- local dk = docker.new() +-- local first + +-- local cb = function(res, chunk) +-- if res.code == 200 then +-- if not first then +-- first = true +-- luci.http.status(res.code, res.message) +-- luci.http.header('Content-Disposition', 'inline; filename="'.. "images" ..'.tar"') +-- luci.http.header('Content-Type', 'application\/x-tar') +-- end +-- luci.ltn12.pump.all(chunk, luci.http.write) +-- else +-- if not first then +-- first = true +-- luci.http.prepare_content("text/plain") +-- end +-- luci.ltn12.pump.all(chunk, luci.http.write) +-- end +-- end + +-- docker:write_status("Images: saving" .. " " .. names .. "...") +-- local res = dk.images:get({ +-- query = { +-- names = luci.http.urlencode(names) +-- } +-- }, cb) +-- docker:clear_status() + +-- local msg = res and res.body and res.body.message or nil +-- luci.http.status(res.code, msg) +-- luci.http.prepare_content("application/json") +-- luci.http.write_json({message = msg}) +-- end + +function load_images() + local archive = luci.http.formvalue("upload-archive") + local dk = docker.new() + local ltn12 = require "luci.ltn12" + + local rec_send = function(sinkout) + luci.http.setfilehandler(function (meta, chunk, eof) + if chunk then + ltn12.pump.step(ltn12.source.string(chunk), sinkout) + end + end) + end + + docker:write_status("Images: loading...") + local res = dk.images:load({body = rec_send}) + local msg = res and res.body and ( res.body.message or res.body.stream or res.body.error ) or nil + if res and res.code == 200 and msg and msg:match("Loaded image ID") then + docker:clear_status() + else + docker:append_status("code:" .. (res and res.code or "500") .." ".. (msg or "unknow")) + end + + luci.http.status(res and res.code or 500, msg or "unknow") + luci.http.prepare_content("application/json") + luci.http.write_json({message = msg or "unknow"}) +end + +function import_images() + local src = luci.http.formvalue("src") + local itag = luci.http.formvalue("tag") + local dk = docker.new() + local ltn12 = require "luci.ltn12" + + local rec_send = function(sinkout) + luci.http.setfilehandler(function (meta, chunk, eof) + if chunk then + ltn12.pump.step(ltn12.source.string(chunk), sinkout) + end + end) + end + + docker:write_status("Images: importing".. " ".. itag .."...\n") + local repo = itag and itag:match("^([^:]+)") + local tag = itag and itag:match("^[^:]-:([^:]+)") + local res = dk.images:create({ + query = { + fromSrc = luci.http.urlencode(src or "-"), + repo = repo or nil, + tag = tag or nil + }, + body = not src and rec_send or nil + }, docker.import_image_show_status_cb) + + local msg = res and res.body and ( res.body.message )or nil + if not msg and #res.body == 0 then + msg = res.body.status or res.body.error + elseif not msg and #res.body >= 1 then + msg = res.body[#res.body].status or res.body[#res.body].error + end + + if res.code == 200 and msg and msg:match("sha256:") then + docker:clear_status() + else + docker:append_status("code:" .. (res and res.code or "500") .." ".. (msg or "unknow")) + end + + luci.http.status(res and res.code or 500, msg or "unknow") + luci.http.prepare_content("application/json") + luci.http.write_json({message = msg or "unknow"}) +end + +function get_image_tags(image_id) + if not image_id then + luci.http.status(400, "no image id") + luci.http.prepare_content("application/json") + luci.http.write_json({message = "no image id"}) + return + end + + local dk = docker.new() + local res = dk.images:inspect({ + id = image_id + }) + local msg = res and res.body and res.body.message or nil + luci.http.status(res and res.code or 500, msg or "unknow") + luci.http.prepare_content("application/json") + + if res.code == 200 then + local tags = res.body.RepoTags + luci.http.write_json({tags = tags}) + else + local msg = res and res.body and res.body.message or nil + luci.http.write_json({message = msg or "unknow"}) + end +end + +function tag_image(image_id) + local src = luci.http.formvalue("tag") + local image_id = image_id or luci.http.formvalue("id") + + if type(src) ~= "string" or not image_id then + luci.http.status(400, "no image id or tag") + luci.http.prepare_content("application/json") + luci.http.write_json({message = "no image id or tag"}) + return + end + + local repo = src:match("^([^:]+)") + local tag = src:match("^[^:]-:([^:]+)") + local dk = docker.new() + local res = dk.images:tag({ + id = image_id, + query={ + repo=repo, + tag=tag + } + }) + local msg = res and res.body and res.body.message or nil + luci.http.status(res and res.code or 500, msg or "unknow") + luci.http.prepare_content("application/json") + + if res.code == 201 then + local tags = res.body.RepoTags + luci.http.write_json({tags = tags}) + else + local msg = res and res.body and res.body.message or nil + luci.http.write_json({message = msg or "unknow"}) + end +end + +function untag_image(tag) + local tag = tag or luci.http.formvalue("tag") + + if not tag then + luci.http.status(400, "no tag name") + luci.http.prepare_content("application/json") + luci.http.write_json({message = "no tag name"}) + return + end + + local dk = docker.new() + local res = dk.images:inspect({name = tag}) + + if res.code == 200 then + local tags = res.body.RepoTags + if #tags > 1 then + local r = dk.images:remove({name = tag}) + local msg = r and r.body and r.body.message or nil + luci.http.status(r.code, msg) + luci.http.prepare_content("application/json") + luci.http.write_json({message = msg}) + else + luci.http.status(500, "Cannot remove the last tag") + luci.http.prepare_content("application/json") + luci.http.write_json({message = "Cannot remove the last tag"}) + end + else + local msg = res and res.body and res.body.message or nil + luci.http.status(res and res.code or 500, msg or "unknow") + luci.http.prepare_content("application/json") + luci.http.write_json({message = msg or "unknow"}) + end +end diff --git a/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua b/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua new file mode 100644 index 000000000..f62650fe5 --- /dev/null +++ b/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua @@ -0,0 +1,152 @@ +--[[ +LuCI - Lua Configuration Interface +Copyright 2021 Florian Eckert +Copyright 2021 lisaac +]]-- + +local uci = (require "luci.model.uci").cursor() + +local m, s, o + +m = Map("dockerd", + translate("Docker - Configuration"), + translate("DockerMan is a simple docker manager client for LuCI")) + +if nixio.fs.access("/usr/bin/dockerd") and not m.uci:get_bool("dockerd", "dockerman", "remote_endpoint") then + s = m:section(NamedSection, "globals", "section", translate("Docker Daemon settings")) + + o = s:option(Flag, "auto_start", translate("Auto start")) + o.rmempty = false + o.write = function(self, section, value) + if value == "1" then + luci.util.exec("/etc/init.d/dockerd enable") + else + luci.util.exec("/etc/init.d/dockerd disable") + end + m.uci:set("dockerd", "globals", "auto_start", value) + end + + o = s:option(Value, "data_root", + translate("Docker Root Dir")) + o.placeholder = "/opt/docker/" + o:depends("remote_endpoint", 0) + + o = s:option(Value, "bip", + translate("Default bridge"), + translate("Configure the default bridge network")) + o.placeholder = "172.17.0.1/16" + o.datatype = "ipaddr" + o:depends("remote_endpoint", 0) + + o = s:option(DynamicList, "registry_mirrors", + translate("Registry Mirrors"), + translate("It replaces the daemon registry mirrors with a new set of registry mirrors")) + o:value("https://hub-mirror.c.163.com", "https://hub-mirror.c.163.com") + o:depends("remote_endpoint", 0) + o.forcewrite = true + + o = s:option(ListValue, "log_level", + translate("Log Level"), + translate('Set the logging level')) + o:value("debug", translate("Debug")) + o:value("", translate("Info")) -- This is the default debug level from the deamon is optin is not set + o:value("warn", translate("Warning")) + o:value("error", translate("Error")) + o:value("fatal", translate("Fatal")) + o.rmempty = true + o:depends("remote_endpoint", 0) + + o = s:option(DynamicList, "hosts", + translate("Client connection"), + translate('Specifies where the Docker daemon will listen for client connections (default: unix:///var/run/docker.sock)')) + o:value("unix:///var/run/docker.sock", "unix:///var/run/docker.sock") + o:value("tcp://0.0.0.0:2375", "tcp://0.0.0.0:2375") + o.rmempty = true + o:depends("remote_endpoint", 0) +end + +s = m:section(NamedSection, "dockerman", "section", translate("DockerMan settings")) +s:tab("ac", translate("Access Control")) +s:tab("dockerman", translate("DockerMan")) + +o = s:taboption("dockerman", Flag, "remote_endpoint", + translate("Remote Endpoint"), + translate("Connect to remote docker endpoint")) +o.rmempty = false +o.validate = function(self, value, sid) + local res = luci.http.formvaluetable("cbid.dockerd") + if res["dockerman.remote_endpoint"] == "1" then + if res["dockerman.remote_port"] and res["dockerman.remote_port"] ~= "" and res["dockerman.remote_host"] and res["dockerman.remote_host"] ~= "" then + return 1 + else + return nil, translate("Please input the PORT or HOST IP of remote docker instance!") + end + else + if not res["dockerman.socket_path"] then + return nil, translate("Please input the SOCKET PATH of docker daemon!") + end + end + return 0 +end + +o = s:taboption("dockerman", Value, "socket_path", + translate("Docker Socket Path")) +o.default = "/var/run/docker.sock" +o.placeholder = "/var/run/docker.sock" +o:depends("remote_endpoint", 0) + +o = s:taboption("dockerman", Value, "remote_host", + translate("Remote Host"), + translate("Host or IP Address for the connection to a remote docker instance")) +o.datatype = "host" +o.placeholder = "10.1.1.2" +o:depends("remote_endpoint", 1) + +o = s:taboption("dockerman", Value, "remote_port", + translate("Remote Port")) +o.placeholder = "2375" +o.datatype = "port" +o:depends("remote_endpoint", 1) + +-- o = s:taboption("dockerman", Value, "status_path", translate("Action Status Tempfile Path"), translate("Where you want to save the docker status file")) +-- o = s:taboption("dockerman", Flag, "debug", translate("Enable Debug"), translate("For debug, It shows all docker API actions of luci-app-dockerman in Debug Tempfile Path")) +-- o.enabled="true" +-- o.disabled="false" +-- o = s:taboption("dockerman", Value, "debug_path", translate("Debug Tempfile Path"), translate("Where you want to save the debug tempfile")) + +if nixio.fs.access("/usr/bin/dockerd") and not m.uci:get_bool("dockerd", "dockerman", "remote_endpoint") then + o = s:taboption("ac", DynamicList, "ac_allowed_interface", translate("Allowed access interfaces"), translate("Which interface(s) can access containers under the bridge network, fill-in Interface Name")) + local interfaces = luci.sys and luci.sys.net and luci.sys.net.devices() or {} + for i, v in ipairs(interfaces) do + o:value(v, v) + end + o = s:taboption("ac", DynamicList, "ac_allowed_ports", translate("Ports allowed to be accessed"), translate("Which Port(s) can be accessed, it's not restricted by the Allowed Access interfaces configuration. Use this configuration with caution!")) + o.placeholder = "8080/tcp" + local docker = require "luci.model.docker" + local containers, res, lost_state + local dk = docker.new() + if dk:_ping().code ~= 200 then + lost_state = true + else + lost_state = false + res = dk.containers:list() + if res and res.code and res.code < 300 then + containers = res.body + end + end + + -- allowed_container.placeholder = "container name_or_id" + if containers then + for i, v in ipairs(containers) do + if v.State == "running" and v.Ports then + for _, port in ipairs(v.Ports) do + if port.PublicPort and port.IP and not string.find(port.IP,":") then + o:value(port.PublicPort.."/"..port.Type, v.Names[1]:sub(2) .. " | " .. port.PublicPort .. " | " .. port.Type) + end + end + end + end + end +end + +return m diff --git a/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua b/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua new file mode 100644 index 000000000..20220ad8f --- /dev/null +++ b/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua @@ -0,0 +1,810 @@ +--[[ +LuCI - Lua Configuration Interface +Copyright 2019 lisaac +]]-- + +require "luci.util" + +local docker = require "luci.model.docker" +local dk = docker.new() + +container_id = arg[1] +local action = arg[2] or "info" + +local m, s, o +local images, networks, container_info, res + +if not container_id then + return +end + +res = dk.containers:inspect({id = container_id}) +if res.code < 300 then + container_info = res.body +else + return +end + +local get_ports = function(d) + local data + + if d.HostConfig and d.HostConfig.PortBindings then + for inter, out in pairs(d.HostConfig.PortBindings) do + data = (data and (data .. "
") or "") .. out[1]["HostPort"] .. ":" .. inter + end + end + + return data +end + +local get_env = function(d) + local data + + if d.Config and d.Config.Env then + for _,v in ipairs(d.Config.Env) do + data = (data and (data .. "
") or "") .. v + end + end + + return data +end + +local get_command = function(d) + local data + + if d.Config and d.Config.Cmd then + for _,v in ipairs(d.Config.Cmd) do + data = (data and (data .. " ") or "") .. v + end + end + + return data +end + +local get_mounts = function(d) + local data + + if d.Mounts then + for _,v in ipairs(d.Mounts) do + local v_sorce_d, v_dest_d + local v_sorce = "" + local v_dest = "" + for v_sorce_d in v["Source"]:gmatch('[^/]+') do + if v_sorce_d and #v_sorce_d > 12 then + v_sorce = v_sorce .. "/" .. v_sorce_d:sub(1,12) .. "..." + else + v_sorce = v_sorce .."/".. v_sorce_d + end + end + for v_dest_d in v["Destination"]:gmatch('[^/]+') do + if v_dest_d and #v_dest_d > 12 then + v_dest = v_dest .. "/" .. v_dest_d:sub(1,12) .. "..." + else + v_dest = v_dest .."/".. v_dest_d + end + end + data = (data and (data .. "
") or "") .. v_sorce .. ":" .. v["Destination"] .. (v["Mode"] ~= "" and (":" .. v["Mode"]) or "") + end + end + + return data +end + +local get_device = function(d) + local data + + if d.HostConfig and d.HostConfig.Devices then + for _,v in ipairs(d.HostConfig.Devices) do + data = (data and (data .. "
") or "") .. v["PathOnHost"] .. ":" .. v["PathInContainer"] .. (v["CgroupPermissions"] ~= "" and (":" .. v["CgroupPermissions"]) or "") + end + end + + return data +end + +local get_links = function(d) + local data + + if d.HostConfig and d.HostConfig.Links then + for _,v in ipairs(d.HostConfig.Links) do + data = (data and (data .. "
") or "") .. v + end + end + + return data +end + +local get_tmpfs = function(d) + local data + + if d.HostConfig and d.HostConfig.Tmpfs then + for k, v in pairs(d.HostConfig.Tmpfs) do + data = (data and (data .. "
") or "") .. k .. (v~="" and ":" or "")..v + end + end + + return data +end + +local get_dns = function(d) + local data + + if d.HostConfig and d.HostConfig.Dns then + for _, v in ipairs(d.HostConfig.Dns) do + data = (data and (data .. "
") or "") .. v + end + end + + return data +end + +local get_sysctl = function(d) + local data + + if d.HostConfig and d.HostConfig.Sysctls then + for k, v in pairs(d.HostConfig.Sysctls) do + data = (data and (data .. "
") or "") .. k..":"..v + end + end + + return data +end + +local get_networks = function(d) + local data={} + + if d.NetworkSettings and d.NetworkSettings.Networks and type(d.NetworkSettings.Networks) == "table" then + for k,v in pairs(d.NetworkSettings.Networks) do + data[k] = v.IPAddress or "" + end + end + + return data +end + + +local start_stop_remove = function(m, cmd) + local res + + docker:clear_status() + docker:append_status("Containers: " .. cmd .. " " .. container_id .. "...") + + if cmd ~= "upgrade" then + res = dk.containers[cmd](dk, {id = container_id}) + else + res = dk.containers_upgrade(dk, {id = container_id}) + end + + if res and res.code >= 300 then + docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message)) + luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id)) + else + docker:clear_status() + if cmd ~= "remove" and cmd ~= "upgrade" then + luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id)) + else + luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers")) + end + end +end + +m=SimpleForm("docker", + translatef("Docker - Container (%s)", container_info.Name:sub(2)), + translate("On this page, the selected container can be managed.")) +m.redirect = luci.dispatcher.build_url("admin/docker/containers") + +s = m:section(SimpleSection) +s.template = "dockerman/apply_widget" +s.err=docker:read_status() +s.err=s.err and s.err:gsub("\n","
"):gsub(" "," ") +if s.err then + docker:clear_status() +end + +s = m:section(Table,{{}}) +s.notitle=true +s.rowcolors=false +s.template = "cbi/nullsection" + +o = s:option(Button, "_start") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Start") +o.inputstyle = "apply" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"start") +end + +o = s:option(Button, "_restart") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Restart") +o.inputstyle = "reload" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"restart") +end + +o = s:option(Button, "_stop") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Stop") +o.inputstyle = "reset" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"stop") +end + +o = s:option(Button, "_kill") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Kill") +o.inputstyle = "reset" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"kill") +end + +o = s:option(Button, "_export") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Export") +o.inputstyle = "apply" +o.forcewrite = true +o.write = function(self, section) + luci.http.redirect(luci.dispatcher.build_url("admin/docker/container_export/"..container_id)) +end + +o = s:option(Button, "_upgrade") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Upgrade") +o.inputstyle = "reload" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"upgrade") +end + +o = s:option(Button, "_duplicate") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Duplicate/Edit") +o.inputstyle = "add" +o.forcewrite = true +o.write = function(self, section) + luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer/duplicate/"..container_id)) +end + +o = s:option(Button, "_remove") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Remove") +o.inputstyle = "remove" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"remove") +end + +s = m:section(SimpleSection) +s.template = "dockerman/container" + +if action == "info" then + res = dk.networks:list() + if res.code < 300 then + networks = res.body + else + return + end + m.submit = false + m.reset = false + table_info = { + ["01name"] = { + _key = translate("Name"), + _value = container_info.Name:sub(2) or "-", + _button=translate("Update") + }, + ["02id"] = { + _key = translate("ID"), + _value = container_info.Id or "-" + }, + ["03image"] = { + _key = translate("Image"), + _value = container_info.Config.Image .. "
" .. container_info.Image + }, + ["04status"] = { + _key = translate("Status"), + _value = container_info.State and container_info.State.Status or "-" + }, + ["05created"] = { + _key = translate("Created"), + _value = container_info.Created or "-" + }, + } + + if container_info.State.Status == "running" then + table_info["06start"] = { + _key = translate("Start Time"), + _value = container_info.State and container_info.State.StartedAt or "-" + } + else + table_info["06start"] = { + _key = translate("Finish Time"), + _value = container_info.State and container_info.State.FinishedAt or "-" + } + end + + table_info["07healthy"] = { + _key = translate("Healthy"), + _value = container_info.State and container_info.State.Health and container_info.State.Health.Status or "-" + } + table_info["08restart"] = { + _key = translate("Restart Policy"), + _value = container_info.HostConfig and container_info.HostConfig.RestartPolicy and container_info.HostConfig.RestartPolicy.Name or "-", + _button=translate("Update") + } + table_info["081user"] = { + _key = translate("User"), + _value = container_info.Config and (container_info.Config.User ~="" and container_info.Config.User or "-") or "-" + } + table_info["09mount"] = { + _key = translate("Mount/Volume"), + _value = get_mounts(container_info) or "-" + } + table_info["10cmd"] = { + _key = translate("Command"), + _value = get_command(container_info) or "-" + } + table_info["11env"] = { + _key = translate("Env"), + _value = get_env(container_info) or "-" + } + table_info["12ports"] = { + _key = translate("Ports"), + _value = get_ports(container_info) or "-" + } + table_info["13links"] = { + _key = translate("Links"), + _value = get_links(container_info) or "-" + } + table_info["14device"] = { + _key = translate("Device"), + _value = get_device(container_info) or "-" + } + table_info["15tmpfs"] = { + _key = translate("Tmpfs"), + _value = get_tmpfs(container_info) or "-" + } + table_info["16dns"] = { + _key = translate("DNS"), + _value = get_dns(container_info) or "-" + } + table_info["17sysctl"] = { + _key = translate("Sysctl"), + _value = get_sysctl(container_info) or "-" + } + + info_networks = get_networks(container_info) + list_networks = {} + for _, v in ipairs (networks) do + if v and v.Name then + local parent = v.Options and v.Options.parent or nil + local ip = v.IPAM and v.IPAM.Config and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil + ipv6 = v.IPAM and v.IPAM.Config and v.IPAM.Config[2] and v.IPAM.Config[2].Subnet or nil + local network_name = v.Name .. " | " .. v.Driver .. (parent and (" | " .. parent) or "") .. (ip and (" | " .. ip) or "").. (ipv6 and (" | " .. ipv6) or "") + list_networks[v.Name] = network_name + end + end + + if type(info_networks)== "table" then + for k,v in pairs(info_networks) do + table_info["14network"..k] = { + _key = translate("Network"), + _value = k.. (v~="" and (" | ".. v) or ""), + _button=translate("Disconnect") + } + list_networks[k]=nil + end + end + + table_info["15connect"] = { + _key = translate("Connect Network"), + _value = list_networks ,_opts = "", + _button=translate("Connect") + } + + s = m:section(Table,table_info) + s.nodescr=true + s.formvalue=function(self, section) + return table_info + end + + o = s:option(DummyValue, "_key", translate("Info")) + o.width = "20%" + + o = s:option(ListValue, "_value") + o.render = function(self, section, scope) + if table_info[section]._key == translate("Name") then + self:reset_values() + self.template = "cbi/value" + self.size = 30 + self.keylist = {} + self.vallist = {} + self.default=table_info[section]._value + Value.render(self, section, scope) + elseif table_info[section]._key == translate("Restart Policy") then + self.template = "cbi/lvalue" + self:reset_values() + self.size = nil + self:value("no", "No") + self:value("unless-stopped", "Unless stopped") + self:value("always", "Always") + self:value("on-failure", "On failure") + self.default=table_info[section]._value + ListValue.render(self, section, scope) + elseif table_info[section]._key == translate("Connect Network") then + self.template = "cbi/lvalue" + self:reset_values() + self.size = nil + for k,v in pairs(list_networks) do + if k ~= "host" then + self:value(k,v) + end + end + self.default=table_info[section]._value + ListValue.render(self, section, scope) + else + self:reset_values() + self.rawhtml=true + self.template = "cbi/dvalue" + self.default=table_info[section]._value + DummyValue.render(self, section, scope) + end + end + o.forcewrite = true + o.write = function(self, section, value) + table_info[section]._value=value + end + o.validate = function(self, value) + return value + end + + o = s:option(Value, "_opts") + o.forcewrite = true + o.write = function(self, section, value) + table_info[section]._opts=value + end + o.validate = function(self, value) + return value + end + o.render = function(self, section, scope) + if table_info[section]._key==translate("Connect Network") then + self.template = "cbi/value" + self.keylist = {} + self.vallist = {} + self.placeholder = "10.1.1.254" + self.datatype = "ip4addr" + self.default=table_info[section]._opts + Value.render(self, section, scope) + else + self.rawhtml=true + self.template = "cbi/dvalue" + self.default=table_info[section]._opts + DummyValue.render(self, section, scope) + end + end + + o = s:option(Button, "_button") + o.forcewrite = true + o.render = function(self, section, scope) + if table_info[section]._button and table_info[section]._value ~= nil then + self.inputtitle=table_info[section]._button + self.template = "cbi/button" + self.inputstyle = "edit" + Button.render(self, section, scope) + else + self.template = "cbi/dvalue" + self.default="" + DummyValue.render(self, section, scope) + end + end + o.write = function(self, section, value) + local res + + docker:clear_status() + + if section == "01name" then + docker:append_status("Containers: rename " .. container_id .. "...") + local new_name = table_info[section]._value + res = dk.containers:rename({ + id = container_id, + query = { + name=new_name + } + }) + elseif section == "08restart" then + docker:append_status("Containers: update " .. container_id .. "...") + local new_restart = table_info[section]._value + res = dk.containers:update({ + id = container_id, + body = { + RestartPolicy = { + Name = new_restart + } + } + }) + elseif table_info[section]._key == translate("Network") then + local _,_,leave_network + + _, _, leave_network = table_info[section]._value:find("(.-) | .+") + leave_network = leave_network or table_info[section]._value + docker:append_status("Network: disconnect " .. leave_network .. container_id .. "...") + res = dk.networks:disconnect({ + name = leave_network, + body = { + Container = container_id + } + }) + elseif section == "15connect" then + local connect_network = table_info[section]._value + local network_opiton + if connect_network ~= "none" + and connect_network ~= "bridge" + and connect_network ~= "host" then + + network_opiton = table_info[section]._opts ~= "" and { + IPAMConfig={ + IPv4Address=table_info[section]._opts + } + } or nil + end + docker:append_status("Network: connect " .. connect_network .. container_id .. "...") + res = dk.networks:connect({ + name = connect_network, + body = { + Container = container_id, + EndpointConfig= network_opiton + } + }) + end + + if res and res.code > 300 then + docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message)) + else + docker:clear_status() + end + luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id.."/info")) + end +elseif action == "resources" then + s = m:section(SimpleSection) + o = s:option( Value, "cpus", + translate("CPUs"), + translate("Number of CPUs. Number is a fractional number. 0.000 means no limit.")) + o.placeholder = "1.5" + o.rmempty = true + o.datatype="ufloat" + o.default = container_info.HostConfig.NanoCpus / (10^9) + + o = s:option(Value, "cpushares", + translate("CPU Shares Weight"), + translate("CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024.")) + o.placeholder = "1024" + o.rmempty = true + o.datatype="uinteger" + o.default = container_info.HostConfig.CpuShares + + o = s:option(Value, "memory", + translate("Memory"), + translate("Memory limit (format: []). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M.")) + o.placeholder = "128m" + o.rmempty = true + o.default = container_info.HostConfig.Memory ~=0 and ((container_info.HostConfig.Memory / 1024 /1024) .. "M") or 0 + + o = s:option(Value, "blkioweight", + translate("Block IO Weight"), + translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000.")) + o.placeholder = "500" + o.rmempty = true + o.datatype="uinteger" + o.default = container_info.HostConfig.BlkioWeight + + m.handle = function(self, state, data) + if state == FORM_VALID then + local memory = data.memory + if memory and memory ~= 0 then + _,_,n,unit = memory:find("([%d%.]+)([%l%u]+)") + if n then + unit = unit and unit:sub(1,1):upper() or "B" + if unit == "M" then + memory = tonumber(n) * 1024 * 1024 + elseif unit == "G" then + memory = tonumber(n) * 1024 * 1024 * 1024 + elseif unit == "K" then + memory = tonumber(n) * 1024 + else + memory = tonumber(n) + end + end + end + + request_body = { + BlkioWeight = tonumber(data.blkioweight), + NanoCPUs = tonumber(data.cpus)*10^9, + Memory = tonumber(memory), + CpuShares = tonumber(data.cpushares) + } + + docker:write_status("Containers: update " .. container_id .. "...") + local res = dk.containers:update({id = container_id, body = request_body}) + if res and res.code >= 300 then + docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message)) + else + docker:clear_status() + end + luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id.."/resources")) + end + end + +elseif action == "file" then + m.submit = false + m.reset = false + s= m:section(SimpleSection) + s.template = "dockerman/container_file_manager" + s.container = container_id + m.redirect = nil +elseif action == "inspect" then + s = m:section(SimpleSection) + s.syslog = luci.jsonc.stringify(container_info, true) + s.title = translate("Container Inspect") + s.template = "dockerman/logs" + m.submit = false + m.reset = false +elseif action == "logs" then + local logs = "" + local query ={ + stdout = 1, + stderr = 1, + tail = 1000 + } + + s = m:section(SimpleSection) + + logs = dk.containers:logs({id = container_id, query = query}) + if logs.code == 200 then + s.syslog=logs.body + else + s.syslog="Get Logs ERROR\n"..logs.code..": "..logs.body + end + + s.title=translate("Container Logs") + s.template = "dockerman/logs" + m.submit = false + m.reset = false +elseif action == "console" then + m.submit = false + m.reset = false + local cmd_docker = luci.util.exec("command -v docker"):match("^.+docker") or nil + local cmd_ttyd = luci.util.exec("command -v ttyd"):match("^.+ttyd") or nil + + if cmd_docker and cmd_ttyd and container_info.State.Status == "running" then + local cmd = "/bin/sh" + local uid + + s = m:section(SimpleSection) + + o = s:option(Value, "command", translate("Command")) + o:value("/bin/sh", "/bin/sh") + o:value("/bin/ash", "/bin/ash") + o:value("/bin/bash", "/bin/bash") + o.default = "/bin/sh" + o.forcewrite = true + o.write = function(self, section, value) + cmd = value + end + + o = s:option(Value, "uid", translate("UID")) + o.forcewrite = true + o.write = function(self, section, value) + uid = value + end + + o = s:option(Button, "connect") + o.render = function(self, section, scope) + self.inputstyle = "add" + self.title = " " + self.inputtitle = translate("Connect") + Button.render(self, section, scope) + end + o.write = function(self, section) + local cmd_docker = luci.util.exec("command -v docker"):match("^.+docker") or nil + local cmd_ttyd = luci.util.exec("command -v ttyd"):match("^.+ttyd") or nil + + if not cmd_docker or not cmd_ttyd or cmd_docker:match("^%s+$") or cmd_ttyd:match("^%s+$") then + return + end + local uci = (require "luci.model.uci").cursor() + + local ttyd_ssl = uci:get("ttyd", "@ttyd[0]", "ssl") + local ttyd_ssl_key = uci:get("ttyd", "@ttyd[0]", "ssl_key") + local ttyd_ssl_cert = uci:get("ttyd", "@ttyd[0]", "ssl_cert") + + if ttyd_ssl == "1" and ttyd_ssl_cert and ttyd_ssl_key then + cmd_ttyd = string.format('%s -S -C %s -K %s', cmd_ttyd, ttyd_ssl_cert, ttyd_ssl_key) + end + + local pid = luci.util.trim(luci.util.exec("netstat -lnpt | grep :7682 | grep ttyd | tr -s ' ' | cut -d ' ' -f7 | cut -d'/' -f1")) + if pid and pid ~= "" then + luci.util.exec("kill -9 " .. pid) + end + + local hosts + local remote = uci:get_bool("dockerd", "dockerman", "remote_endpoint") or false + local host = nil + local port = nil + local socket = nil + + if remote then + host = uci:get("dockerd", "dockerman", "remote_host") or nil + port = uci:get("dockerd", "dockerman", "remote_port") or nil + else + socket = uci:get("dockerd", "dockerman", "socket_path") or "/var/run/docker.sock" + end + + if remote and host and port then + hosts = "tcp://" .. host .. ':'.. port + elseif socket then + hosts = "unix://" .. socket + else + return + end + + if uid and uid ~= "" then + uid = "-u " .. uid + else + uid = "" + end + + local start_cmd = string.format('%s -d 2 --once -p 7682 %s -H "%s" exec -it %s %s %s&', cmd_ttyd, cmd_docker, hosts, uid, container_id, cmd) + + os.execute(start_cmd) + + o = s:option(DummyValue, "console") + o.container_id = container_id + o.template = "dockerman/container_console" + end + end +elseif action == "stats" then + local response = dk.containers:top({id = container_id, query = {ps_args="-aux"}}) + local container_top + + if response.code == 200 then + container_top=response.body + else + response = dk.containers:top({id = container_id}) + if response.code == 200 then + container_top=response.body + end + end + + if type(container_top) == "table" then + s = m:section(SimpleSection) + s.container_id = container_id + s.template = "dockerman/container_stats" + table_stats = { + cpu={ + key=translate("CPU Useage"), + value='-' + }, + memory={ + key=translate("Memory Useage"), + value='-' + } + } + + container_top = response.body + s = m:section(Table, table_stats, translate("Stats")) + s:option(DummyValue, "key", translate("Stats")).width="33%" + s:option(DummyValue, "value") + top_section = m:section(Table, container_top.Processes, translate("TOP")) + for i, v in ipairs(container_top.Titles) do + top_section:option(DummyValue, i, translate(v)) + end + end + + m.submit = false + m.reset = false +end + +return m diff --git a/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua b/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua new file mode 100644 index 000000000..47f634f8b --- /dev/null +++ b/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua @@ -0,0 +1,284 @@ +--[[ +LuCI - Lua Configuration Interface +Copyright 2019 lisaac +]]-- + +local http = require "luci.http" +local docker = require "luci.model.docker" + +local m, s, o +local images, networks, containers, res, lost_state +local urlencode = luci.http.protocol and luci.http.protocol.urlencode or luci.util.urlencode +local dk = docker.new() + +if dk:_ping().code ~= 200 then + lost_state = true +else + res = dk.images:list() + if res and res.code and res.code < 300 then + images = res.body + end + + res = dk.networks:list() + if res and res.code and res.code < 300 then + networks = res.body + end + + res = dk.containers:list({ + query = { + all = true + } + }) + if res and res.code and res.code < 300 then + containers = res.body + end +end + +function get_containers() + local data = {} + if type(containers) ~= "table" then + return nil + end + + for i, v in ipairs(containers) do + local index = (10^12 - v.Created) .. "_id_" .. v.Id + + data[index]={} + data[index]["_selected"] = 0 + data[index]["_id"] = v.Id:sub(1,12) + -- data[index]["name"] = v.Names[1]:sub(2) + data[index]["_status"] = v.Status + + if v.Status:find("^Up") then + data[index]["_name"] = ""..v.Names[1]:sub(2).."" + data[index]["_status"] = "".. data[index]["_status"] .. "" .. "


" + else + data[index]["_name"] = ""..v.Names[1]:sub(2).."" + data[index]["_status"] = ''.. data[index]["_status"] .. "" + end + + if (type(v.NetworkSettings) == "table" and type(v.NetworkSettings.Networks) == "table") then + for networkname, netconfig in pairs(v.NetworkSettings.Networks) do + data[index]["_network"] = (data[index]["_network"] ~= nil and (data[index]["_network"] .." | ") or "").. networkname .. (netconfig.IPAddress ~= "" and (": " .. netconfig.IPAddress) or "") + end + end + + -- networkmode = v.HostConfig.NetworkMode ~= "default" and v.HostConfig.NetworkMode or "bridge" + -- data[index]["_network"] = v.NetworkSettings.Networks[networkmode].IPAddress or nil + -- local _, _, image = v.Image:find("^sha256:(.+)") + -- if image ~= nil then + -- image=image:sub(1,12) + -- end + + if v.Ports and next(v.Ports) ~= nil then + data[index]["_ports"] = nil + local ip = require "luci.ip" + for _,v2 in ipairs(v.Ports) do + -- display ipv4 only + if ip.new(v2.IP or "0.0.0.0"):is4() then + data[index]["_ports"] = (data[index]["_ports"] and (data[index]["_ports"] .. ", ") or "") + .. ((v2.PublicPort and v2.Type and v2.Type == "tcp") and ('') or "") + .. (v2.PublicPort and (v2.PublicPort .. ":") or "") .. (v2.PrivatePort and (v2.PrivatePort .."/") or "") .. (v2.Type and v2.Type or "") + .. ((v2.PublicPort and v2.Type and v2.Type == "tcp")and "" or "") + end + end + end + + for ii,iv in ipairs(images) do + if iv.Id == v.ImageID then + data[index]["_image"] = iv.RepoTags and iv.RepoTags[1] or (iv.RepoDigests[1]:gsub("(.-)@.+", "%1") .. ":<none>") + end + end + data[index]["_id_name"] = ''.. data[index]["_name"] .. "
ID: " .. data[index]["_id"] + .. "

Image: " .. (data[index]["_image"] or "<none>") + .. "
" + + if type(v.Mounts) == "table" and next(v.Mounts) then + for _, v2 in pairs(v.Mounts) do + if v2.Type ~= "volume" then + local v_sorce_d, v_dest_d + local v_sorce = "" + local v_dest = "" + for v_sorce_d in v2["Source"]:gmatch('[^/]+') do + if v_sorce_d and #v_sorce_d > 12 then + v_sorce = v_sorce .. "/" .. v_sorce_d:sub(1,8) .. ".." + else + v_sorce = v_sorce .."/".. v_sorce_d + end + end + for v_dest_d in v2["Destination"]:gmatch('[^/]+') do + if v_dest_d and #v_dest_d > 12 then + v_dest = v_dest .. "/" .. v_dest_d:sub(1,8) .. ".." + else + v_dest = v_dest .."/".. v_dest_d + end + end + data[index]["_mounts"] = (data[index]["_mounts"] and (data[index]["_mounts"] .. "
") or "") .. '' .. v_sorce .. "→" .. v_dest..'' + end + end + end + + data[index]["_image_id"] = v.ImageID:sub(8,20) + data[index]["_command"] = v.Command + end + return data +end + +local container_list = not lost_state and get_containers() or {} + +m = SimpleForm("docker", + translate("Docker - Containers"), + translate("This page displays all containers that have been created on the connected docker host.")) +m.submit=false +m.reset=false +m:append(Template("dockerman/containers_running_stats")) + +s = m:section(SimpleSection) +s.template = "dockerman/apply_widget" +s.err=docker:read_status() +s.err=s.err and s.err:gsub("\n","
"):gsub(" "," ") +if s.err then + docker:clear_status() +end + +s = m:section(Table, container_list, translate("Containers")) +s.nodescr=true +s.config="containers" + +o = s:option(Flag, "_selected","") +o.disabled = 0 +o.enabled = 1 +o.default = 0 +o.width = "1%" +o.write=function(self, section, value) + container_list[section]._selected = value +end + +-- o = s:option(DummyValue, "_id", translate("ID")) +-- o.width="10%" + +-- o = s:option(DummyValue, "_name", translate("Container Name")) +-- o.rawhtml = true + +o = s:option(DummyValue, "_id_name", translate("Container Info")) +o.rawhtml = true +o.width="15%" + +o = s:option(DummyValue, "_status", translate("Status")) +o.width="15%" +o.rawhtml=true + +o = s:option(DummyValue, "_network", translate("Network")) +o.width="10%" + +o = s:option(DummyValue, "_ports", translate("Ports")) +o.width="5%" +o.rawhtml = true +o = s:option(DummyValue, "_mounts", translate("Mounts")) +o.width="25%" +o.rawhtml = true + +-- o = s:option(DummyValue, "_image", translate("Image")) +-- o.width="8%" + +o = s:option(DummyValue, "_command", translate("Command")) +o.width="15%" + +local start_stop_remove = function(m, cmd) + local container_selected = {} + -- 遍历table中sectionid + for k in pairs(container_list) do + -- 得到选中项的名字 + if container_list[k]._selected == 1 then + container_selected[#container_selected + 1] = container_list[k]["_id"] + end + end + if #container_selected > 0 then + local success = true + + docker:clear_status() + for _, cont in ipairs(container_selected) do + docker:append_status("Containers: " .. cmd .. " " .. cont .. "...") + local res = dk.containers[cmd](dk, {id = cont}) + if res and res.code and res.code >= 300 then + success = false + docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n") + else + docker:append_status("done\n") + end + end + + if success then + docker:clear_status() + end + + luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers")) + end +end + +s = m:section(Table,{{}}) +s.notitle=true +s.rowcolors=false +s.template="cbi/nullsection" + +o = s:option(Button, "_new") +o.inputtitle = translate("Add") +o.template = "dockerman/cbi/inlinebutton" +o.inputstyle = "add" +o.forcewrite = true +o.write = function(self, section) + luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer")) +end +o.disable = lost_state + +o = s:option(Button, "_start") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle = translate("Start") +o.inputstyle = "apply" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"start") +end +o.disable = lost_state + +o = s:option(Button, "_restart") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle = translate("Restart") +o.inputstyle = "reload" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"restart") +end +o.disable = lost_state + +o = s:option(Button, "_stop") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle = translate("Stop") +o.inputstyle = "reset" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"stop") +end +o.disable = lost_state + +o = s:option(Button, "_kill") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle = translate("Kill") +o.inputstyle = "reset" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"kill") +end +o.disable = lost_state + +o = s:option(Button, "_remove") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle = translate("Remove") +o.inputstyle = "remove" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m, "remove") +end +o.disable = lost_state + +return m diff --git a/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua b/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua new file mode 100644 index 000000000..c3d3eab0d --- /dev/null +++ b/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua @@ -0,0 +1,284 @@ +--[[ +LuCI - Lua Configuration Interface +Copyright 2019 lisaac +]]-- + +local docker = require "luci.model.docker" +local dk = docker.new() + +local containers, images, res, lost_state +local m, s, o + +if dk:_ping().code ~= 200 then + lost_state = true +else + res = dk.images:list() + if res and res.code and res.code < 300 then + images = res.body + end + + res = dk.containers:list({ query = { all = true } }) + if res and res.code and res.code < 300 then + containers = res.body + end +end + +function get_images() + local data = {} + + for i, v in ipairs(images) do + local index = v.Created .. v.Id + + data[index]={} + data[index]["_selected"] = 0 + data[index]["id"] = v.Id:sub(8) + data[index]["_id"] = '' .. v.Id:sub(8,20) .. '' + + if v.RepoTags and next(v.RepoTags)~=nil then + for i, v1 in ipairs(v.RepoTags) do + data[index]["_tags"] =(data[index]["_tags"] and ( data[index]["_tags"] .. "
" )or "") .. ((v1:match("") or (#v.RepoTags == 1)) and v1 or ('' .. v1 .. '')) + + if not data[index]["tag"] then + data[index]["tag"] = v1 + end + end + else + data[index]["_tags"] = v.RepoDigests[1] and v.RepoDigests[1]:match("^(.-)@.+") + data[index]["_tags"] = (data[index]["_tags"] and data[index]["_tags"] or "" ).. ":" + end + + data[index]["_tags"] = data[index]["_tags"]:gsub("","<none>") + for ci,cv in ipairs(containers) do + if v.Id == cv.ImageID then + data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "").. + ''.. cv.Names[1]:sub(2).."" + end + end + + data[index]["_size"] = string.format("%.2f", tostring(v.Size/1024/1024)).."MB" + data[index]["_created"] = os.date("%Y/%m/%d %H:%M:%S",v.Created) + end + + return data +end + +local image_list = not lost_state and get_images() or {} + +m = SimpleForm("docker", + translate("Docker - Images"), + translate("On this page all images are displayed that are available on the system and with which a container can be created.")) +m.submit=false +m.reset=false + +local pull_value={ + _image_tag_name="", + _registry="index.docker.io" +} + +s = m:section(SimpleSection, + translate("Pull Image"), + translate("By entering a valid image name with the corresponding version, the docker image can be downloaded from the configured registry.")) +s.template="cbi/nullsection" + +o = s:option(Value, "_image_tag_name") +o.template = "dockerman/cbi/inlinevalue" +o.placeholder="lisaac/luci:latest" +o.write = function(self, section, value) + local hastag = value:find(":") + + if not hastag then + value = value .. ":latest" + end + pull_value["_image_tag_name"] = value +end + +o = s:option(Button, "_pull") +o.inputtitle= translate("Pull") +o.template = "dockerman/cbi/inlinebutton" +o.inputstyle = "add" +o.disable = lost_state +o.write = function(self, section) + local tag = pull_value["_image_tag_name"] + local json_stringify = luci.jsonc and luci.jsonc.stringify + + if tag and tag ~= "" then + docker:write_status("Images: " .. "pulling" .. " " .. tag .. "...\n") + local res = dk.images:create({query = {fromImage=tag}}, docker.pull_image_show_status_cb) + + if res and res.code and res.code == 200 and (res.body[#res.body] and not res.body[#res.body].error and res.body[#res.body].status and (res.body[#res.body].status == "Status: Downloaded newer image for ".. tag)) then + docker:clear_status() + else + docker:append_status("code:" .. res.code.." ".. (res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)).. "\n") + end + else + docker:append_status("code: 400 please input the name of image name!") + end + + luci.http.redirect(luci.dispatcher.build_url("admin/docker/images")) +end + +s = m:section(SimpleSection, + translate("Import Image"), + translate("When pressing the Import button, both a local image can be loaded onto the system and a valid image tar can be downloaded from remote.")) + +o = s:option(DummyValue, "_image_import") +o.template = "dockerman/images_import" +o.disable = lost_state + +s = m:section(Table, image_list, translate("Images overview")) + +o = s:option(Flag, "_selected","") +o.disabled = 0 +o.enabled = 1 +o.default = 0 +o.write = function(self, section, value) + image_list[section]._selected = value +end + +o = s:option(DummyValue, "_id", translate("ID")) +o.rawhtml = true + +o = s:option(DummyValue, "_tags", translate("RepoTags")) +o.rawhtml = true + +o = s:option(DummyValue, "_containers", translate("Containers")) +o.rawhtml = true + +o = s:option(DummyValue, "_size", translate("Size")) + +o = s:option(DummyValue, "_created", translate("Created")) + +local remove_action = function(force) + local image_selected = {} + + for k in pairs(image_list) do + if image_list[k]._selected == 1 then + image_selected[#image_selected+1] = (image_list[k]["_tags"]:match("
") or image_list[k]["_tags"]:match("<none>")) and image_list[k].id or image_list[k].tag + end + end + + if next(image_selected) ~= nil then + local success = true + + docker:clear_status() + for _, img in ipairs(image_selected) do + local query + docker:append_status("Images: " .. "remove" .. " " .. img .. "...") + + if force then + query = {force = true} + end + + local msg = dk.images:remove({ + id = img, + query = query + }) + if msg and msg.code ~= 200 then + docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n") + success = false + else + docker:append_status("done\n") + end + end + + if success then + docker:clear_status() + end + + luci.http.redirect(luci.dispatcher.build_url("admin/docker/images")) + end +end + +s = m:section(SimpleSection) +s.template = "dockerman/apply_widget" +s.err = docker:read_status() +s.err = s.err and s.err:gsub("\n","
"):gsub(" "," ") +if s.err then + docker:clear_status() +end + +s = m:section(Table,{{}}) +s.notitle=true +s.rowcolors=false +s.template="cbi/nullsection" + +o = s:option(Button, "remove") +o.inputtitle= translate("Remove") +o.template = "dockerman/cbi/inlinebutton" +o.inputstyle = "remove" +o.forcewrite = true +o.write = function(self, section) + remove_action() +end +o.disable = lost_state + +o = s:option(Button, "forceremove") +o.inputtitle= translate("Force Remove") +o.template = "dockerman/cbi/inlinebutton" +o.inputstyle = "remove" +o.forcewrite = true +o.write = function(self, section) + remove_action(true) +end +o.disable = lost_state + +o = s:option(Button, "save") +o.inputtitle= translate("Save") +o.template = "dockerman/cbi/inlinebutton" +o.inputstyle = "edit" +o.disable = lost_state +o.forcewrite = true +o.write = function (self, section) + local image_selected = {} + + for k in pairs(image_list) do + if image_list[k]._selected == 1 then + image_selected[#image_selected + 1] = image_list[k].id + end + end + + if next(image_selected) ~= nil then + local names, first, show_name + + for _, img in ipairs(image_selected) do + names = names and (names .. "&names=".. img) or img + end + if #image_selected > 1 then + show_name = "images" + else + show_name = image_selected[1] + end + local cb = function(res, chunk) + if res and res.code and res.code == 200 then + if not first then + first = true + luci.http.header('Content-Disposition', 'inline; filename="'.. show_name .. '.tar"') + luci.http.header('Content-Type', 'application\/x-tar') + end + luci.ltn12.pump.all(chunk, luci.http.write) + else + if not first then + first = true + luci.http.prepare_content("text/plain") + end + luci.ltn12.pump.all(chunk, luci.http.write) + end + end + + docker:write_status("Images: " .. "save" .. " " .. table.concat(image_selected, "\n") .. "...") + local msg = dk.images:get({query = {names = names}}, cb) + if msg and msg.code and msg.code ~= 200 then + docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n") + else + docker:clear_status() + end + end +end + +o = s:option(Button, "load") +o.inputtitle= translate("Load") +o.template = "dockerman/images_load" +o.inputstyle = "add" +o.disable = lost_state + +return m diff --git a/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua b/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua new file mode 100644 index 000000000..37702c783 --- /dev/null +++ b/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua @@ -0,0 +1,159 @@ +--[[ +LuCI - Lua Configuration Interface +Copyright 2019 lisaac +]]-- + +local docker = require "luci.model.docker" + +local m, s, o +local networks, dk, res, lost_state + +dk = docker.new() + +if dk:_ping().code ~= 200 then + lost_state = true +else + res = dk.networks:list() + if res and res.code and res.code < 300 then + networks = res.body + end +end + +local get_networks = function () + local data = {} + + if type(networks) ~= "table" then + return nil + end + + for i, v in ipairs(networks) do + local index = v.Created .. v.Id + + data[index]={} + data[index]["_selected"] = 0 + data[index]["_id"] = v.Id:sub(1,12) + data[index]["_name"] = v.Name + data[index]["_driver"] = v.Driver + + if v.Driver == "bridge" then + data[index]["_interface"] = v.Options["com.docker.network.bridge.name"] + elseif v.Driver == "macvlan" then + data[index]["_interface"] = v.Options.parent + end + + data[index]["_subnet"] = v.IPAM and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil + data[index]["_gateway"] = v.IPAM and v.IPAM.Config[1] and v.IPAM.Config[1].Gateway or nil + end + + return data +end + +local network_list = not lost_state and get_networks() or {} + +m = SimpleForm("docker", + translate("Docker - Networks"), + translate("This page displays all docker networks that have been created on the connected docker host.")) +m.submit=false +m.reset=false + +s = m:section(Table, network_list, translate("Networks overview")) +s.nodescr=true + +o = s:option(Flag, "_selected","") +o.template = "dockerman/cbi/xfvalue" +o.disabled = 0 +o.enabled = 1 +o.default = 0 +o.render = function(self, section, scope) + self.disable = 0 + if network_list[section]["_name"] == "bridge" or network_list[section]["_name"] == "none" or network_list[section]["_name"] == "host" then + self.disable = 1 + end + Flag.render(self, section, scope) +end +o.write = function(self, section, value) + network_list[section]._selected = value +end + +o = s:option(DummyValue, "_id", translate("ID")) + +o = s:option(DummyValue, "_name", translate("Network Name")) + +o = s:option(DummyValue, "_driver", translate("Driver")) + +o = s:option(DummyValue, "_interface", translate("Parent Interface")) + +o = s:option(DummyValue, "_subnet", translate("Subnet")) + +o = s:option(DummyValue, "_gateway", translate("Gateway")) + +s = m:section(SimpleSection) +s.template = "dockerman/apply_widget" +s.err = docker:read_status() +s.err = s.err and s.err:gsub("\n","
"):gsub(" "," ") +if s.err then + docker:clear_status() +end + +s = m:section(Table,{{}}) +s.notitle=true +s.rowcolors=false +s.template="cbi/nullsection" + +o = s:option(Button, "_new") +o.inputtitle= translate("New") +o.template = "dockerman/cbi/inlinebutton" +o.notitle=true +o.inputstyle = "add" +o.forcewrite = true +o.disable = lost_state +o.write = function(self, section) + luci.http.redirect(luci.dispatcher.build_url("admin/docker/newnetwork")) +end + +o = s:option(Button, "_remove") +o.inputtitle= translate("Remove") +o.template = "dockerman/cbi/inlinebutton" +o.inputstyle = "remove" +o.forcewrite = true +o.disable = lost_state +o.write = function(self, section) + local network_selected = {} + local network_name_selected = {} + local network_driver_selected = {} + + for k in pairs(network_list) do + if network_list[k]._selected == 1 then + network_selected[#network_selected + 1] = network_list[k]._id + network_name_selected[#network_name_selected + 1] = network_list[k]._name + network_driver_selected[#network_driver_selected + 1] = network_list[k]._driver + end + end + + if next(network_selected) ~= nil then + local success = true + docker:clear_status() + + for ii, net in ipairs(network_selected) do + docker:append_status("Networks: " .. "remove" .. " " .. net .. "...") + local res = dk.networks["remove"](dk, {id = net}) + + if res and res.code and res.code >= 300 then + docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n") + success = false + else + docker:append_status("done\n") + if network_driver_selected[ii] == "macvlan" then + docker.remove_macvlan_interface(network_name_selected[ii]) + end + end + end + + if success then + docker:clear_status() + end + luci.http.redirect(luci.dispatcher.build_url("admin/docker/networks")) + end +end + +return m diff --git a/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua b/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua new file mode 100644 index 000000000..bfd1bf2a1 --- /dev/null +++ b/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua @@ -0,0 +1,923 @@ +--[[ +LuCI - Lua Configuration Interface +Copyright 2019 lisaac +]]-- + +local docker = require "luci.model.docker" + +local m, s, o + +local dk = docker.new() + +local cmd_line = table.concat(arg, '/') +local images, networks +local create_body = {} + +if dk:_ping().code ~= 200 then + lost_state = true + images = {} + networks = {} +else + images = dk.images:list().body + networks = dk.networks:list().body +end + +local is_quot_complete = function(str) + local num = 0, w + require "math" + + if not str then + return true + end + + local num = 0, w + for w in str:gmatch("\"") do + num = num + 1 + end + + if math.fmod(num, 2) ~= 0 then + return false + end + + num = 0 + for w in str:gmatch("\'") do + num = num + 1 + end + + if math.fmod(num, 2) ~= 0 then + return false + end + + return true +end + +function contains(list, x) + for _, v in pairs(list) do + if v == x then + return true + end + end + return false +end + +local resolve_cli = function(cmd_line) + local config = { + advance = 1 + } + + local key_no_val = { + 't', + 'd', + 'i', + 'tty', + 'rm', + 'read_only', + 'interactive', + 'init', + 'help', + 'detach', + 'privileged', + 'P', + 'publish_all', + } + + local key_with_val = { + 'sysctl', + 'add_host', + 'a', + 'attach', + 'blkio_weight_device', + 'cap_add', + 'cap_drop', + 'device', + 'device_cgroup_rule', + 'device_read_bps', + 'device_read_iops', + 'device_write_bps', + 'device_write_iops', + 'dns', + 'dns_option', + 'dns_search', + 'e', + 'env', + 'env_file', + 'expose', + 'group_add', + 'l', + 'label', + 'label_file', + 'link', + 'link_local_ip', + 'log_driver', + 'log_opt', + 'network_alias', + 'p', + 'publish', + 'security_opt', + 'storage_opt', + 'tmpfs', + 'v', + 'volume', + 'volumes_from', + 'blkio_weight', + 'cgroup_parent', + 'cidfile', + 'cpu_period', + 'cpu_quota', + 'cpu_rt_period', + 'cpu_rt_runtime', + 'c', + 'cpu_shares', + 'cpus', + 'cpuset_cpus', + 'cpuset_mems', + 'detach_keys', + 'disable_content_trust', + 'domainname', + 'entrypoint', + 'gpus', + 'health_cmd', + 'health_interval', + 'health_retries', + 'health_start_period', + 'health_timeout', + 'h', + 'hostname', + 'ip', + 'ip6', + 'ipc', + 'isolation', + 'kernel_memory', + 'mac_address', + 'm', + 'memory', + 'memory_reservation', + 'memory_swap', + 'memory_swappiness', + 'mount', + 'name', + 'network', + 'no_healthcheck', + 'oom_kill_disable', + 'oom_score_adj', + 'pid', + 'pids_limit', + 'restart', + 'runtime', + 'shm_size', + 'sig_proxy', + 'stop_signal', + 'stop_timeout', + 'ulimit', + 'u', + 'user', + 'userns', + 'uts', + 'volume_driver', + 'w', + 'workdir' + } + + local key_abb = { + net='network', + a='attach', + c='cpu-shares', + d='detach', + e='env', + h='hostname', + i='interactive', + l='label', + m='memory', + p='publish', + P='publish_all', + t='tty', + u='user', + v='volume', + w='workdir' + } + + local key_with_list = { + 'sysctl', + 'add_host', + 'a', + 'attach', + 'blkio_weight_device', + 'cap_add', + 'cap_drop', + 'device', + 'device_cgroup_rule', + 'device_read_bps', + 'device_read_iops', + 'device_write_bps', + 'device_write_iops', + 'dns', + 'dns_optiondns_search', + 'e', + 'env', + 'env_file', + 'expose', + 'group_add', + 'l', + 'label', + 'label_file', + 'link', + 'link_local_ip', + 'log_opt', + 'network_alias', + 'p', + 'publish', + 'security_opt', + 'storage_opt', + 'tmpfs', + 'v', + 'volume', + 'volumes_from', + } + + local key = nil + local _key = nil + local val = nil + local is_cmd = false + + cmd_line = cmd_line:match("^DOCKERCLI%s+(.+)") + for w in cmd_line:gmatch("[^%s]+") do + if w =='\\' then + elseif not key and not _key and not is_cmd then + --key=val + key, val = w:match("^%-%-([%lP%-]-)=(.+)") + if not key then + --key val + key = w:match("^%-%-([%lP%-]+)") + if not key then + -- -v val + key = w:match("^%-([%lP%-]+)") + if key then + -- for -dit + if key:match("i") or key:match("t") or key:match("d") then + if key:match("i") then + config[key_abb["i"]] = true + key:gsub("i", "") + end + if key:match("t") then + config[key_abb["t"]] = true + key:gsub("t", "") + end + if key:match("d") then + config[key_abb["d"]] = true + key:gsub("d", "") + end + if key:match("P") then + config[key_abb["P"]] = true + key:gsub("P", "") + end + if key == "" then + key = nil + end + end + end + end + end + if key then + key = key:gsub("-","_") + key = key_abb[key] or key + if contains(key_no_val, key) then + config[key] = true + val = nil + key = nil + elseif contains(key_with_val, key) then + -- if key == "cap_add" then config.privileged = true end + else + key = nil + val = nil + end + else + config.image = w + key = nil + val = nil + is_cmd = true + end + elseif (key or _key) and not is_cmd then + if key == "mount" then + -- we need resolve mount options here + -- type=bind,source=/source,target=/app + local _type = w:match("^type=([^,]+),") or "bind" + local source = (_type ~= "tmpfs") and (w:match("source=([^,]+),") or w:match("src=([^,]+),")) or "" + local target = w:match(",target=([^,]+)") or w:match(",dst=([^,]+)") or w:match(",destination=([^,]+)") or "" + local ro = w:match(",readonly") and "ro" or nil + + if source and target then + if _type ~= "tmpfs" then + local bind_propagation = (_type == "bind") and w:match(",bind%-propagation=([^,]+)") or nil + val = source..":"..target .. ((ro or bind_propagation) and (":" .. (ro and ro or "") .. (((ro and bind_propagation) and "," or "") .. (bind_propagation and bind_propagation or ""))or "")) + else + local tmpfs_mode = w:match(",tmpfs%-mode=([^,]+)") or nil + local tmpfs_size = w:match(",tmpfs%-size=([^,]+)") or nil + key = "tmpfs" + val = target .. ((tmpfs_mode or tmpfs_size) and (":" .. (tmpfs_mode and ("mode=" .. tmpfs_mode) or "") .. ((tmpfs_mode and tmpfs_size) and "," or "") .. (tmpfs_size and ("size=".. tmpfs_size) or "")) or "") + if not config[key] then + config[key] = {} + end + table.insert( config[key], val ) + key = nil + val = nil + end + end + else + val = w + end + elseif is_cmd then + config["command"] = (config["command"] and (config["command"] .. " " )or "") .. w + end + if (key or _key) and val then + key = _key or key + if contains(key_with_list, key) then + if not config[key] then + config[key] = {} + end + if _key then + config[key][#config[key]] = config[key][#config[key]] .. " " .. w + else + table.insert( config[key], val ) + end + if is_quot_complete(config[key][#config[key]]) then + config[key][#config[key]] = config[key][#config[key]]:gsub("[\"\']", "") + _key = nil + else + _key = key + end + else + config[key] = (config[key] and (config[key] .. " ") or "") .. val + if is_quot_complete(config[key]) then + config[key] = config[key]:gsub("[\"\']", "") + _key = nil + else + _key = key + end + end + key = nil + val = nil + end + end + + return config +end + +local default_config = {} + +if cmd_line and cmd_line:match("^DOCKERCLI.+") then + default_config = resolve_cli(cmd_line) +elseif cmd_line and cmd_line:match("^duplicate/[^/]+$") then + local container_id = cmd_line:match("^duplicate/(.+)") + create_body = dk:containers_duplicate_config({id = container_id}) or {} + if not create_body.HostConfig then + create_body.HostConfig = {} + end + + if next(create_body) ~= nil then + default_config.name = nil + default_config.image = create_body.Image + default_config.hostname = create_body.Hostname + default_config.tty = create_body.Tty and true or false + default_config.interactive = create_body.OpenStdin and true or false + default_config.privileged = create_body.HostConfig.Privileged and true or false + default_config.restart = create_body.HostConfig.RestartPolicy and create_body.HostConfig.RestartPolicy.name or nil + -- default_config.network = create_body.HostConfig.NetworkMode == "default" and "bridge" or create_body.HostConfig.NetworkMode + -- if container has leave original network, and add new network, .HostConfig.NetworkMode is INcorrect, so using first child of .NetworkingConfig.EndpointsConfig + default_config.network = create_body.NetworkingConfig and create_body.NetworkingConfig.EndpointsConfig and next(create_body.NetworkingConfig.EndpointsConfig) or nil + default_config.ip = default_config.network and default_config.network ~= "bridge" and default_config.network ~= "host" and default_config.network ~= "null" and create_body.NetworkingConfig.EndpointsConfig[default_config.network].IPAMConfig and create_body.NetworkingConfig.EndpointsConfig[default_config.network].IPAMConfig.IPv4Address or nil + default_config.link = create_body.HostConfig.Links + default_config.env = create_body.Env + default_config.dns = create_body.HostConfig.Dns + default_config.volume = create_body.HostConfig.Binds + default_config.cap_add = create_body.HostConfig.CapAdd + default_config.publish_all = create_body.HostConfig.PublishAllPorts + + if create_body.HostConfig.Sysctls and type(create_body.HostConfig.Sysctls) == "table" then + default_config.sysctl = {} + for k, v in pairs(create_body.HostConfig.Sysctls) do + table.insert( default_config.sysctl, k.."="..v ) + end + end + if create_body.HostConfig.LogConfig then + if create_body.HostConfig.LogConfig.Config and type(create_body.HostConfig.LogConfig.Config) == "table" then + default_config.log_opt = {} + for k, v in pairs(create_body.HostConfig.LogConfig.Config) do + table.insert( default_config.log_opt, k.."="..v ) + end + end + default_config.log_driver = create_body.HostConfig.LogConfig.Type or nil + end + + if create_body.HostConfig.PortBindings and type(create_body.HostConfig.PortBindings) == "table" then + default_config.publish = {} + for k, v in pairs(create_body.HostConfig.PortBindings) do + for x, y in ipairs(v) do + table.insert( default_config.publish, y.HostPort..":"..k:match("^(%d+)/.+").."/"..k:match("^%d+/(.+)") ) + end + end + end + + default_config.user = create_body.User or nil + default_config.command = create_body.Cmd and type(create_body.Cmd) == "table" and table.concat(create_body.Cmd, " ") or nil + default_config.advance = 1 + default_config.cpus = create_body.HostConfig.NanoCPUs + default_config.cpu_shares = create_body.HostConfig.CpuShares + default_config.memory = create_body.HostConfig.Memory + default_config.blkio_weight = create_body.HostConfig.BlkioWeight + + if create_body.HostConfig.Devices and type(create_body.HostConfig.Devices) == "table" then + default_config.device = {} + for _, v in ipairs(create_body.HostConfig.Devices) do + table.insert( default_config.device, v.PathOnHost..":"..v.PathInContainer..(v.CgroupPermissions ~= "" and (":" .. v.CgroupPermissions) or "") ) + end + end + + if create_body.HostConfig.Tmpfs and type(create_body.HostConfig.Tmpfs) == "table" then + default_config.tmpfs = {} + for k, v in pairs(create_body.HostConfig.Tmpfs) do + table.insert( default_config.tmpfs, k .. (v~="" and ":" or "")..v ) + end + end + end +end + +m = SimpleForm("docker", translate("Docker - Containers")) +m.redirect = luci.dispatcher.build_url("admin", "docker", "containers") +if lost_state then + m.submit=false + m.reset=false +end + +s = m:section(SimpleSection) +s.template = "dockerman/apply_widget" +s.err=docker:read_status() +s.err=s.err and s.err:gsub("\n","
"):gsub(" "," ") +if s.err then + docker:clear_status() +end + +s = m:section(SimpleSection, translate("Create new docker container")) +s.addremove = true +s.anonymous = true + +o = s:option(DummyValue,"cmd_line", translate("Resolve CLI")) +o.rawhtml = true +o.template = "dockerman/newcontainer_resolve" + +o = s:option(Value, "name", translate("Container Name")) +o.rmempty = true +o.default = default_config.name or nil + +o = s:option(Flag, "interactive", translate("Interactive (-i)")) +o.rmempty = true +o.disabled = 0 +o.enabled = 1 +o.default = default_config.interactive and 1 or 0 + +o = s:option(Flag, "tty", translate("TTY (-t)")) +o.rmempty = true +o.disabled = 0 +o.enabled = 1 +o.default = default_config.tty and 1 or 0 + +o = s:option(Value, "image", translate("Docker Image")) +o.rmempty = true +o.default = default_config.image or nil +for _, v in ipairs (images) do + if v.RepoTags then + o:value(v.RepoTags[1], v.RepoTags[1]) + end +end + +o = s:option(Flag, "_force_pull", translate("Always pull image first")) +o.rmempty = true +o.disabled = 0 +o.enabled = 1 +o.default = 0 + +o = s:option(Flag, "privileged", translate("Privileged")) +o.rmempty = true +o.disabled = 0 +o.enabled = 1 +o.default = default_config.privileged and 1 or 0 + +o = s:option(ListValue, "restart", translate("Restart Policy")) +o.rmempty = true +o:value("no", "No") +o:value("unless-stopped", "Unless stopped") +o:value("always", "Always") +o:value("on-failure", "On failure") +o.default = default_config.restart or "unless-stopped" + +local d_network = s:option(ListValue, "network", translate("Networks")) +d_network.rmempty = true +d_network.default = default_config.network or "bridge" + +local d_ip = s:option(Value, "ip", translate("IPv4 Address")) +d_ip.datatype="ip4addr" +d_ip:depends("network", "nil") +d_ip.default = default_config.ip or nil + +o = s:option(DynamicList, "link", translate("Links with other containers")) +o.placeholder = "container_name:alias" +o.rmempty = true +o:depends("network", "bridge") +o.default = default_config.link or nil + +o = s:option(DynamicList, "dns", translate("Set custom DNS servers")) +o.placeholder = "8.8.8.8" +o.rmempty = true +o.default = default_config.dns or nil + +o = s:option(Value, "user", + translate("User(-u)"), + translate("The user that commands are run as inside the container.(format: name|uid[:group|gid])")) +o.placeholder = "1000:1000" +o.rmempty = true +o.default = default_config.user or nil + +o = s:option(DynamicList, "env", + translate("Environmental Variable(-e)"), + translate("Set environment variables to inside the container")) +o.placeholder = "TZ=Asia/Shanghai" +o.rmempty = true +o.default = default_config.env or nil + +o = s:option(DynamicList, "volume", + translate("Bind Mount(-v)"), + translate("Bind mount a volume")) +o.placeholder = "/media:/media:slave" +o.rmempty = true +o.default = default_config.volume or nil + +local d_publish = s:option(DynamicList, "publish", + translate("Exposed Ports(-p)"), + translate("Publish container's port(s) to the host")) +d_publish.placeholder = "2200:22/tcp" +d_publish.rmempty = true +d_publish.default = default_config.publish or nil + +o = s:option(Value, "command", translate("Run command")) +o.placeholder = "/bin/sh init.sh" +o.rmempty = true +o.default = default_config.command or nil + +o = s:option(Flag, "advance", translate("Advance")) +o.rmempty = true +o.disabled = 0 +o.enabled = 1 +o.default = default_config.advance or 0 + +o = s:option(Value, "hostname", + translate("Host Name"), + translate("The hostname to use for the container")) +o.rmempty = true +o.default = default_config.hostname or nil +o:depends("advance", 1) + +o = s:option(Flag, "publish_all", + translate("Exposed All Ports(-P)"), + translate("Allocates an ephemeral host port for all of a container's exposed ports")) +o.rmempty = true +o.disabled = 0 +o.enabled = 1 +o.default = default_config.publish_all and 1 or 0 +o:depends("advance", 1) + +o = s:option(DynamicList, "device", + translate("Device(--device)"), + translate("Add host device to the container")) +o.placeholder = "/dev/sda:/dev/xvdc:rwm" +o.rmempty = true +o:depends("advance", 1) +o.default = default_config.device or nil + +o = s:option(DynamicList, "tmpfs", + translate("Tmpfs(--tmpfs)"), + translate("Mount tmpfs directory")) +o.placeholder = "/run:rw,noexec,nosuid,size=65536k" +o.rmempty = true +o:depends("advance", 1) +o.default = default_config.tmpfs or nil + +o = s:option(DynamicList, "sysctl", + translate("Sysctl(--sysctl)"), + translate("Sysctls (kernel parameters) options")) +o.placeholder = "net.ipv4.ip_forward=1" +o.rmempty = true +o:depends("advance", 1) +o.default = default_config.sysctl or nil + +o = s:option(DynamicList, "cap_add", + translate("CAP-ADD(--cap-add)"), + translate("A list of kernel capabilities to add to the container")) +o.placeholder = "NET_ADMIN" +o.rmempty = true +o:depends("advance", 1) +o.default = default_config.cap_add or nil + +o = s:option(Value, "cpus", + translate("CPUs"), + translate("Number of CPUs. Number is a fractional number. 0.000 means no limit")) +o.placeholder = "1.5" +o.rmempty = true +o:depends("advance", 1) +o.datatype="ufloat" +o.default = default_config.cpus or nil + +o = s:option(Value, "cpu_shares", + translate("CPU Shares Weight"), + translate("CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024")) +o.placeholder = "1024" +o.rmempty = true +o:depends("advance", 1) +o.datatype="uinteger" +o.default = default_config.cpu_shares or nil + +o = s:option(Value, "memory", + translate("Memory"), + translate("Memory limit (format: []). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M")) +o.placeholder = "128m" +o.rmempty = true +o:depends("advance", 1) +o.default = default_config.memory or nil + +o = s:option(Value, "blkio_weight", + translate("Block IO Weight"), + translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000")) +o.placeholder = "500" +o.rmempty = true +o:depends("advance", 1) +o.datatype="uinteger" +o.default = default_config.blkio_weight or nil + +o = s:option(Value, "log_driver", + translate("Logging driver"), + translate("The logging driver for the container")) +o.placeholder = "json-file" +o.rmempty = true +o:depends("advance", 1) +o.default = default_config.log_driver or nil + +o = s:option(DynamicList, "log_opt", + translate("Log driver options"), + translate("The logging configuration for this container")) +o.placeholder = "max-size=1m" +o.rmempty = true +o:depends("advance", 1) +o.default = default_config.log_opt or nil + +for _, v in ipairs (networks) do + if v.Name then + local parent = v.Options and v.Options.parent or nil + local ip = v.IPAM and v.IPAM.Config and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil + ipv6 = v.IPAM and v.IPAM.Config and v.IPAM.Config[2] and v.IPAM.Config[2].Subnet or nil + local network_name = v.Name .. " | " .. v.Driver .. (parent and (" | " .. parent) or "") .. (ip and (" | " .. ip) or "").. (ipv6 and (" | " .. ipv6) or "") + d_network:value(v.Name, network_name) + + if v.Name ~= "none" and v.Name ~= "bridge" and v.Name ~= "host" then + d_ip:depends("network", v.Name) + end + + if v.Driver == "bridge" then + d_publish:depends("network", v.Name) + end + end +end + +m.handle = function(self, state, data) + if state ~= FORM_VALID then + return + end + + local tmp + local name = data.name or ("luci_" .. os.date("%Y%m%d%H%M%S")) + local hostname = data.hostname + local tty = type(data.tty) == "number" and (data.tty == 1 and true or false) or default_config.tty or false + local publish_all = type(data.publish_all) == "number" and (data.publish_all == 1 and true or false) or default_config.publish_all or false + local interactive = type(data.interactive) == "number" and (data.interactive == 1 and true or false) or default_config.interactive or false + local image = data.image + local user = data.user + + if image and not image:match(".-:.+") then + image = image .. ":latest" + end + + local privileged = type(data.privileged) == "number" and (data.privileged == 1 and true or false) or default_config.privileged or false + local restart = data.restart + local env = data.env + local dns = data.dns + local cap_add = data.cap_add + local sysctl = {} + local log_driver = data.log_driver + + tmp = data.sysctl + if type(tmp) == "table" then + for i, v in ipairs(tmp) do + local k,v1 = v:match("(.-)=(.+)") + if k and v1 then + sysctl[k]=v1 + end + end + end + + local log_opt = {} + tmp = data.log_opt + if type(tmp) == "table" then + for i, v in ipairs(tmp) do + local k,v1 = v:match("(.-)=(.+)") + if k and v1 then + log_opt[k]=v1 + end + end + end + + local network = data.network + local ip = (network ~= "bridge" and network ~= "host" and network ~= "none") and data.ip or nil + local volume = data.volume + local memory = data.memory or nil + local cpu_shares = data.cpu_shares or nil + local cpus = data.cpus or nil + local blkio_weight = data.blkio_weight or nil + + local portbindings = {} + local exposedports = {} + + local tmpfs = {} + tmp = data.tmpfs + if type(tmp) == "table" then + for i, v in ipairs(tmp)do + local k= v:match("([^:]+)") + local v1 = v:match(".-:([^:]+)") or "" + if k then + tmpfs[k]=v1 + end + end + end + + local device = {} + tmp = data.device + if type(tmp) == "table" then + for i, v in ipairs(tmp) do + local t = {} + local _,_, h, c, p = v:find("(.-):(.-):(.+)") + if h and c then + t['PathOnHost'] = h + t['PathInContainer'] = c + t['CgroupPermissions'] = p or "rwm" + else + local _,_, h, c = v:find("(.-):(.+)") + if h and c then + t['PathOnHost'] = h + t['PathInContainer'] = c + t['CgroupPermissions'] = "rwm" + else + t['PathOnHost'] = v + t['PathInContainer'] = v + t['CgroupPermissions'] = "rwm" + end + end + + if next(t) ~= nil then + table.insert( device, t ) + end + end + end + + tmp = data.publish or {} + for i, v in ipairs(tmp) do + for v1 ,v2 in string.gmatch(v, "(%d+):([^%s]+)") do + local _,_,p= v2:find("^%d+/(%w+)") + if p == nil then + v2=v2..'/tcp' + end + portbindings[v2] = {{HostPort=v1}} + exposedports[v2] = {HostPort=v1} + end + end + + local link = data.link + tmp = data.command + local command = {} + if tmp ~= nil then + for v in string.gmatch(tmp, "[^%s]+") do + command[#command+1] = v + end + end + + if memory and memory ~= 0 then + _,_,n,unit = memory:find("([%d%.]+)([%l%u]+)") + if n then + unit = unit and unit:sub(1,1):upper() or "B" + if unit == "M" then + memory = tonumber(n) * 1024 * 1024 + elseif unit == "G" then + memory = tonumber(n) * 1024 * 1024 * 1024 + elseif unit == "K" then + memory = tonumber(n) * 1024 + else + memory = tonumber(n) + end + end + end + + create_body.Hostname = network ~= "host" and (hostname or name) or nil + create_body.Tty = tty and true or false + create_body.OpenStdin = interactive and true or false + create_body.User = user + create_body.Cmd = command + create_body.Env = env + create_body.Image = image + create_body.ExposedPorts = exposedports + create_body.HostConfig = create_body.HostConfig or {} + create_body.HostConfig.Dns = dns + create_body.HostConfig.Binds = volume + create_body.HostConfig.RestartPolicy = { Name = restart, MaximumRetryCount = 0 } + create_body.HostConfig.Privileged = privileged and true or false + create_body.HostConfig.PortBindings = portbindings + create_body.HostConfig.Memory = memory and tonumber(memory) + create_body.HostConfig.CpuShares = cpu_shares and tonumber(cpu_shares) + create_body.HostConfig.NanoCPUs = cpus and tonumber(cpus) * 10 ^ 9 + create_body.HostConfig.BlkioWeight = blkio_weight and tonumber(blkio_weight) + create_body.HostConfig.PublishAllPorts = publish_all + + if create_body.HostConfig.NetworkMode ~= network then + create_body.NetworkingConfig = nil + end + + create_body.HostConfig.NetworkMode = network + + if ip then + if create_body.NetworkingConfig and create_body.NetworkingConfig.EndpointsConfig and type(create_body.NetworkingConfig.EndpointsConfig) == "table" then + for k, v in pairs (create_body.NetworkingConfig.EndpointsConfig) do + if k == network and v.IPAMConfig and v.IPAMConfig.IPv4Address then + v.IPAMConfig.IPv4Address = ip + else + create_body.NetworkingConfig.EndpointsConfig = { [network] = { IPAMConfig = { IPv4Address = ip } } } + end + break + end + else + create_body.NetworkingConfig = { EndpointsConfig = { [network] = { IPAMConfig = { IPv4Address = ip } } } } + end + elseif not create_body.NetworkingConfig then + create_body.NetworkingConfig = nil + end + + create_body["HostConfig"]["Tmpfs"] = tmpfs + create_body["HostConfig"]["Devices"] = device + create_body["HostConfig"]["Sysctls"] = sysctl + create_body["HostConfig"]["CapAdd"] = cap_add + create_body["HostConfig"]["LogConfig"] = { + Config = log_opt, + Type = log_driver + } + + if network == "bridge" then + create_body["HostConfig"]["Links"] = link + end + + local pull_image = function(image) + local json_stringify = luci.jsonc and luci.jsonc.stringify + docker:append_status("Images: " .. "pulling" .. " " .. image .. "...\n") + local res = dk.images:create({query = {fromImage=image}}, docker.pull_image_show_status_cb) + if res and res.code and res.code == 200 and (res.body[#res.body] and not res.body[#res.body].error and res.body[#res.body].status and (res.body[#res.body].status == "Status: Downloaded newer image for ".. image or res.body[#res.body].status == "Status: Image is up to date for ".. image)) then + docker:append_status("done\n") + else + res.code = (res.code == 200) and 500 or res.code + docker:append_status("code:" .. res.code.." ".. (res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)).. "\n") + luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer")) + end + end + + docker:clear_status() + local exist_image = false + + if image then + for _, v in ipairs (images) do + if v.RepoTags and v.RepoTags[1] == image then + exist_image = true + break + end + end + if not exist_image then + pull_image(image) + elseif data._force_pull == 1 then + pull_image(image) + end + end + + create_body = docker.clear_empty_tables(create_body) + + docker:append_status("Container: " .. "create" .. " " .. name .. "...") + local res = dk.containers:create({name = name, body = create_body}) + if res and res.code and res.code == 201 then + docker:clear_status() + luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers")) + else + docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message)) + luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer")) + end +end + +return m diff --git a/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua b/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua new file mode 100644 index 000000000..c87678b85 --- /dev/null +++ b/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua @@ -0,0 +1,258 @@ +--[[ +LuCI - Lua Configuration Interface +Copyright 2019 lisaac +]]-- + +local docker = require "luci.model.docker" + +local m, s, o + +local dk = docker.new() +if dk:_ping().code ~= 200 then + lost_state = true +end + +m = SimpleForm("docker", translate("Docker - Network")) +m.redirect = luci.dispatcher.build_url("admin", "docker", "networks") +if lost_state then + m.submit=false + m.reset=false +end + + +s = m:section(SimpleSection) +s.template = "dockerman/apply_widget" +s.err=docker:read_status() +s.err=s.err and s.err:gsub("\n","
"):gsub(" "," ") +if s.err then + docker:clear_status() +end + +s = m:section(SimpleSection, translate("Create new docker network")) +s.addremove = true +s.anonymous = true + +o = s:option(Value, "name", + translate("Network Name"), + translate("Name of the network that can be selected during container creation")) +o.rmempty = true + +o = s:option(ListValue, "driver", translate("Driver")) +o.rmempty = true +o:value("bridge", translate("Bridge device")) +o:value("macvlan", translate("MAC VLAN")) +o:value("ipvlan", translate("IP VLAN")) +o:value("overlay", translate("Overlay network")) + +o = s:option(Value, "parent", translate("Base device")) +o.rmempty = true +o:depends("driver", "macvlan") +local interfaces = luci.sys and luci.sys.net and luci.sys.net.devices() or {} +for _, v in ipairs(interfaces) do + o:value(v, v) +end +o.default="br-lan" +o.placeholder="br-lan" + +o = s:option(ListValue, "macvlan_mode", translate("Mode")) +o.rmempty = true +o:depends("driver", "macvlan") +o.default="bridge" +o:value("bridge", translate("Bridge (Support direct communication between MAC VLANs)")) +o:value("private", translate("Private (Prevent communication between MAC VLANs)")) +o:value("vepa", translate("VEPA (Virtual Ethernet Port Aggregator)")) +o:value("passthru", translate("Pass-through (Mirror physical device to single MAC VLAN)")) + +o = s:option(ListValue, "ipvlan_mode", translate("Ipvlan Mode")) +o.rmempty = true +o:depends("driver", "ipvlan") +o.default="l3" +o:value("l2", translate("L2 bridge")) +o:value("l3", translate("L3 bridge")) + +o = s:option(Flag, "ingress", + translate("Ingress"), + translate("Ingress network is the network which provides the routing-mesh in swarm mode")) +o.rmempty = true +o.disabled = 0 +o.enabled = 1 +o.default = 0 +o:depends("driver", "overlay") + +o = s:option(DynamicList, "options", translate("Options")) +o.rmempty = true +o.placeholder="com.docker.network.driver.mtu=1500" + +o = s:option(Flag, "internal", translate("Internal"), translate("Restrict external access to the network")) +o.rmempty = true +o:depends("driver", "overlay") +o.disabled = 0 +o.enabled = 1 +o.default = 0 + +if nixio.fs.access("/etc/config/network") and nixio.fs.access("/etc/config/firewall")then + o = s:option(Flag, "op_macvlan", translate("Create macvlan interface"), translate("Auto create macvlan interface in Openwrt")) + o:depends("driver", "macvlan") + o.disabled = 0 + o.enabled = 1 + o.default = 1 +end + +o = s:option(Value, "subnet", translate("Subnet")) +o.rmempty = true +o.placeholder="10.1.0.0/16" +o.datatype="ip4addr" + +o = s:option(Value, "gateway", translate("Gateway")) +o.rmempty = true +o.placeholder="10.1.1.1" +o.datatype="ip4addr" + +o = s:option(Value, "ip_range", translate("IP range")) +o.rmempty = true +o.placeholder="10.1.1.0/24" +o.datatype="ip4addr" + +o = s:option(DynamicList, "aux_address", translate("Exclude IPs")) +o.rmempty = true +o.placeholder="my-route=10.1.1.1" + +o = s:option(Flag, "ipv6", translate("Enable IPv6")) +o.rmempty = true +o.disabled = 0 +o.enabled = 1 +o.default = 0 + +o = s:option(Value, "subnet6", translate("IPv6 Subnet")) +o.rmempty = true +o.placeholder="fe80::/10" +o.datatype="ip6addr" +o:depends("ipv6", 1) + +o = s:option(Value, "gateway6", translate("IPv6 Gateway")) +o.rmempty = true +o.placeholder="fe80::1" +o.datatype="ip6addr" +o:depends("ipv6", 1) + +m.handle = function(self, state, data) + if state == FORM_VALID then + local name = data.name + local driver = data.driver + + local internal = data.internal == 1 and true or false + + local subnet = data.subnet + local gateway = data.gateway + local ip_range = data.ip_range + + local aux_address = {} + local tmp = data.aux_address or {} + for i,v in ipairs(tmp) do + _,_,k1,v1 = v:find("(.-)=(.+)") + aux_address[k1] = v1 + end + + local options = {} + tmp = data.options or {} + for i,v in ipairs(tmp) do + _,_,k1,v1 = v:find("(.-)=(.+)") + options[k1] = v1 + end + + local ipv6 = data.ipv6 == 1 and true or false + + local create_body = { + Name = name, + Driver = driver, + EnableIPv6 = ipv6, + IPAM = { + Driver= "default" + }, + Internal = internal + } + + if subnet or gateway or ip_range then + create_body["IPAM"]["Config"] = { + { + Subnet = subnet, + Gateway = gateway, + IPRange = ip_range, + AuxAddress = aux_address, + AuxiliaryAddresses = aux_address + } + } + end + + if driver == "macvlan" then + create_body["Options"] = { + macvlan_mode = data.macvlan_mode, + parent = data.parent + } + elseif driver == "ipvlan" then + create_body["Options"] = { + ipvlan_mode = data.ipvlan_mode + } + elseif driver == "overlay" then + create_body["Ingress"] = data.ingerss == 1 and true or false + end + + if ipv6 and data.subnet6 and data.subnet6 then + if type(create_body["IPAM"]["Config"]) ~= "table" then + create_body["IPAM"]["Config"] = {} + end + local index = #create_body["IPAM"]["Config"] + create_body["IPAM"]["Config"][index+1] = { + Subnet = data.subnet6, + Gateway = data.gateway6 + } + end + + if next(options) ~= nil then + create_body["Options"] = create_body["Options"] or {} + for k, v in pairs(options) do + create_body["Options"][k] = v + end + end + + create_body = docker.clear_empty_tables(create_body) + docker:write_status("Network: " .. "create" .. " " .. create_body.Name .. "...") + + local res = dk.networks:create({ + body = create_body + }) + + if res and res.code == 201 then + docker:write_status("Network: " .. "create macvlan interface...") + res = dk.networks:inspect({ + name = create_body.Name + }) + + if driver == "macvlan" and + data.op_macvlan ~= 0 and + res and + res.code and + res.code == 200 and + res.body and + res.body.IPAM and + res.body.IPAM.Config and + res.body.IPAM.Config[1] and + res.body.IPAM.Config[1].Gateway and + res.body.IPAM.Config[1].Subnet then + + docker.create_macvlan_interface(data.name, + data.parent, + res.body.IPAM.Config[1].Gateway, + res.body.IPAM.Config[1].Subnet) + end + + docker:clear_status() + luci.http.redirect(luci.dispatcher.build_url("admin/docker/networks")) + else + docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n") + luci.http.redirect(luci.dispatcher.build_url("admin/docker/newnetwork")) + end + end +end + +return m diff --git a/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua b/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua new file mode 100644 index 000000000..c91f349ce --- /dev/null +++ b/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua @@ -0,0 +1,151 @@ +--[[ +LuCI - Lua Configuration Interface +Copyright 2019 lisaac +]]-- + +local docker = require "luci.model.docker" +local uci = (require "luci.model.uci").cursor() + +local m, s, o, lost_state +local dk = docker.new() + +if dk:_ping().code ~= 200 then + lost_state = true +end + +m = SimpleForm("dockerd", + translate("Docker - Overview"), + translate("An overview with the relevant data is displayed here with which the LuCI docker client is connected.") +.. + " " .. + [[]] .. + translate("Github") .. + [[]]) +m.submit=false +m.reset=false + +local docker_info_table = {} +-- docker_info_table['0OperatingSystem'] = {_key=translate("Operating System"),_value='-'} +-- docker_info_table['1Architecture'] = {_key=translate("Architecture"),_value='-'} +-- docker_info_table['2KernelVersion'] = {_key=translate("Kernel Version"),_value='-'} +docker_info_table['3ServerVersion'] = {_key=translate("Docker Version"),_value='-'} +docker_info_table['4ApiVersion'] = {_key=translate("Api Version"),_value='-'} +docker_info_table['5NCPU'] = {_key=translate("CPUs"),_value='-'} +docker_info_table['6MemTotal'] = {_key=translate("Total Memory"),_value='-'} +docker_info_table['7DockerRootDir'] = {_key=translate("Docker Root Dir"),_value='-'} +docker_info_table['8IndexServerAddress'] = {_key=translate("Index Server Address"),_value='-'} +docker_info_table['9RegistryMirrors'] = {_key=translate("Registry Mirrors"),_value='-'} + +if nixio.fs.access("/usr/bin/dockerd") and not uci:get_bool("dockerd", "dockerman", "remote_endpoint") then + s = m:section(SimpleSection) + s.template = "dockerman/apply_widget" + s.err=docker:read_status() + s.err=s.err and s.err:gsub("\n","
"):gsub(" "," ") + if s.err then + docker:clear_status() + end + s = m:section(Table,{{}}) + s.notitle=true + s.rowcolors=false + s.template = "cbi/nullsection" + + o = s:option(Button, "_start") + o.template = "dockerman/cbi/inlinebutton" + o.inputtitle = lost_state and translate("Start") or translate("Stop") + o.inputstyle = lost_state and "add" or "remove" + o.forcewrite = true + o.write = function(self, section) + docker:clear_status() + + if lost_state then + docker:append_status("Docker daemon: starting") + luci.util.exec("/etc/init.d/dockerd start") + luci.util.exec("sleep 5") + luci.util.exec("/etc/init.d/dockerman start") + + else + docker:append_status("Docker daemon: stopping") + luci.util.exec("/etc/init.d/dockerd stop") + end + docker:clear_status() + luci.http.redirect(luci.dispatcher.build_url("admin/docker/overview")) + end + + o = s:option(Button, "_restart") + o.template = "dockerman/cbi/inlinebutton" + o.inputtitle = translate("Restart") + o.inputstyle = "reload" + o.forcewrite = true + o.write = function(self, section) + docker:clear_status() + docker:append_status("Docker daemon: restarting") + luci.util.exec("/etc/init.d/dockerd restart") + luci.util.exec("sleep 5") + luci.util.exec("/etc/init.d/dockerman start") + docker:clear_status() + luci.http.redirect(luci.dispatcher.build_url("admin/docker/overview")) + end +end + +s = m:section(Table, docker_info_table) +s:option(DummyValue, "_key", translate("Info")) +s:option(DummyValue, "_value") + +s = m:section(SimpleSection) +s.template = "dockerman/overview" + +s.containers_running = '-' +s.images_used = '-' +s.containers_total = '-' +s.images_total = '-' +s.networks_total = '-' +s.volumes_total = '-' + +-- local socket = luci.model.uci.cursor():get("dockerd", "dockerman", "socket_path") +if not lost_state then + local containers_list = dk.containers:list({query = {all=true}}).body + local images_list = dk.images:list().body + local vol = dk.volumes:list() + local volumes_list = vol and vol.body and vol.body.Volumes or {} + local networks_list = dk.networks:list().body or {} + local docker_info = dk:info() + + -- docker_info_table['0OperatingSystem']._value = docker_info.body.OperatingSystem + -- docker_info_table['1Architecture']._value = docker_info.body.Architecture + -- docker_info_table['2KernelVersion']._value = docker_info.body.KernelVersion + docker_info_table['3ServerVersion']._value = docker_info.body.ServerVersion + docker_info_table['4ApiVersion']._value = docker_info.headers["Api-Version"] + docker_info_table['5NCPU']._value = tostring(docker_info.body.NCPU) + docker_info_table['6MemTotal']._value = docker.byte_format(docker_info.body.MemTotal) + if docker_info.body.DockerRootDir then + local statvfs = nixio.fs.statvfs(docker_info.body.DockerRootDir) + local size = statvfs and (statvfs.bavail * statvfs.bsize) or 0 + docker_info_table['7DockerRootDir']._value = docker_info.body.DockerRootDir .. " (" .. tostring(docker.byte_format(size)) .. " " .. translate("Available") .. ")" + end + + docker_info_table['8IndexServerAddress']._value = docker_info.body.IndexServerAddress + for i, v in ipairs(docker_info.body.RegistryConfig.Mirrors) do + docker_info_table['9RegistryMirrors']._value = docker_info_table['9RegistryMirrors']._value == "-" and v or (docker_info_table['9RegistryMirrors']._value .. ", " .. v) + end + + s.images_used = 0 + for i, v in ipairs(images_list) do + for ci,cv in ipairs(containers_list) do + if v.Id == cv.ImageID then + s.images_used = s.images_used + 1 + break + end + end + end + + s.containers_running = tostring(docker_info.body.ContainersRunning) + s.images_used = tostring(s.images_used) + s.containers_total = tostring(docker_info.body.Containers) + s.images_total = tostring(#images_list) + s.networks_total = tostring(#networks_list) + s.volumes_total = tostring(#volumes_list) +else + docker_info_table['3ServerVersion']._value = translate("Can NOT connect to docker daemon, please check!!") +end + +return m diff --git a/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua b/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua new file mode 100644 index 000000000..43e6bda3a --- /dev/null +++ b/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua @@ -0,0 +1,142 @@ +--[[ +LuCI - Lua Configuration Interface +Copyright 2019 lisaac +]]-- + +local docker = require "luci.model.docker" +local dk = docker.new() + +local m, s, o + +local res, containers, volumes, lost_state + +function get_volumes() + local data = {} + for i, v in ipairs(volumes) do + local index = v.Name + data[index]={} + data[index]["_selected"] = 0 + data[index]["_nameraw"] = v.Name + data[index]["_name"] = v.Name:sub(1,12) + + for ci,cv in ipairs(containers) do + if cv.Mounts and type(cv.Mounts) ~= "table" then + break + end + for vi, vv in ipairs(cv.Mounts) do + if v.Name == vv.Name then + data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "").. + ''.. cv.Names[1]:sub(2)..'' + end + end + end + data[index]["_driver"] = v.Driver + data[index]["_mountpoint"] = nil + + for v1 in v.Mountpoint:gmatch('[^/]+') do + if v1 == index then + data[index]["_mountpoint"] = data[index]["_mountpoint"] .."/" .. v1:sub(1,12) .. "..." + else + data[index]["_mountpoint"] = (data[index]["_mountpoint"] and data[index]["_mountpoint"] or "").."/".. v1 + end + end + data[index]["_created"] = v.CreatedAt + data[index]["_size"] = "-" + end + + return data +end +if dk:_ping().code ~= 200 then + lost_state = true +else + res = dk.volumes:list() + if res and res.code and res.code <300 then + volumes = res.body.Volumes + end + + res = dk.containers:list({ + query = { + all=true + } + }) + if res and res.code and res.code <300 then + containers = res.body + end +end + +local volume_list = not lost_state and get_volumes() or {} + +m = SimpleForm("docker", translate("Docker - Volumes")) +m.submit=false +m.reset=false +m:append(Template("dockerman/volume_size")) + +s = m:section(Table, volume_list, translate("Volumes overview")) + +o = s:option(Flag, "_selected","") +o.disabled = 0 +o.enabled = 1 +o.default = 0 +o.write = function(self, section, value) + volume_list[section]._selected = value +end + +o = s:option(DummyValue, "_name", translate("Name")) +o = s:option(DummyValue, "_driver", translate("Driver")) +o = s:option(DummyValue, "_containers", translate("Containers")) +o.rawhtml = true +o = s:option(DummyValue, "_mountpoint", translate("Mount Point")) +o = s:option(DummyValue, "_size", translate("Size")) +o.rawhtml = true +o = s:option(DummyValue, "_created", translate("Created")) + +s = m:section(SimpleSection) +s.template = "dockerman/apply_widget" +s.err=docker:read_status() +s.err=s.err and s.err:gsub("\n","
"):gsub(" "," ") +if s.err then + docker:clear_status() +end + +s = m:section(Table,{{}}) +s.notitle=true +s.rowcolors=false +s.template="cbi/nullsection" + +o = s:option(Button, "remove") +o.inputtitle= translate("Remove") +o.template = "dockerman/cbi/inlinebutton" +o.inputstyle = "remove" +o.forcewrite = true +o.disable = lost_state +o.write = function(self, section) + local volume_selected = {} + + for k in pairs(volume_list) do + if volume_list[k]._selected == 1 then + volume_selected[#volume_selected+1] = k + end + end + + if next(volume_selected) ~= nil then + local success = true + docker:clear_status() + for _,vol in ipairs(volume_selected) do + docker:append_status("Volumes: " .. "remove" .. " " .. vol .. "...") + local msg = dk.volumes["remove"](dk, {id = vol}) + if msg and msg.code and msg.code ~= 204 then + docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n") + success = false + else + docker:append_status("done\n") + end + end + + if success then + docker:clear_status() + end + luci.http.redirect(luci.dispatcher.build_url("admin/docker/volumes")) + end +end + +return m diff --git a/luci-app-dockerman/luasrc/model/docker.lua b/luci-app-dockerman/luasrc/model/docker.lua new file mode 100644 index 000000000..2a902912a --- /dev/null +++ b/luci-app-dockerman/luasrc/model/docker.lua @@ -0,0 +1,507 @@ +--[[ +LuCI - Lua Configuration Interface +Copyright 2019 lisaac +]]-- + +local docker = require "luci.docker" +local fs = require "nixio.fs" +local uci = (require "luci.model.uci").cursor() + +local _docker = {} +_docker.options = {} + +--pull image and return iamge id +local update_image = function(self, image_name) + local json_stringify = luci.jsonc and luci.jsonc.stringify + _docker:append_status("Images: " .. "pulling" .. " " .. image_name .. "...\n") + local res = self.images:create({query = {fromImage=image_name}}, _docker.pull_image_show_status_cb) + + if res and res.code and res.code == 200 and (#res.body > 0 and not res.body[#res.body].error and res.body[#res.body].status and (res.body[#res.body].status == "Status: Downloaded newer image for ".. image_name)) then + _docker:append_status("done\n") + else + res.body.message = res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message) + end + + new_image_id = self.images:inspect({name = image_name}).body.Id + return new_image_id, res +end + +local table_equal = function(t1, t2) + if not t1 then + return true + end + + if not t2 then + return false + end + + if #t1 ~= #t2 then + return false + end + + for i, v in ipairs(t1) do + if t1[i] ~= t2[i] then + return false + end + end + + return true +end + +local table_subtract = function(t1, t2) + if not t1 or next(t1) == nil then + return nil + end + + if not t2 or next(t2) == nil then + return t1 + end + + local res = {} + for _, v1 in ipairs(t1) do + local found = false + for _, v2 in ipairs(t2) do + if v1 == v2 then + found= true + break + end + end + if not found then + table.insert(res, v1) + end + end + + return next(res) == nil and nil or res +end + +local map_subtract = function(t1, t2) + if not t1 or next(t1) == nil then + return nil + end + + if not t2 or next(t2) == nil then + return t1 + end + + local res = {} + for k1, v1 in pairs(t1) do + local found = false + for k2, v2 in ipairs(t2) do + if k1 == k2 and luci.util.serialize_data(v1) == luci.util.serialize_data(v2) then + found= true + break + end + end + + if not found then + res[k1] = v1 + end + end + + return next(res) ~= nil and res or nil +end + +_docker.clear_empty_tables = function ( t ) + local k, v + + if next(t) == nil then + t = nil + else + for k, v in pairs(t) do + if type(v) == 'table' then + t[k] = _docker.clear_empty_tables(v) + if t[k] and next(t[k]) == nil then + t[k] = nil + end + end + end + end + + return t +end + +local get_config = function(container_config, image_config) + local config = container_config.Config + local old_host_config = container_config.HostConfig + local old_network_setting = container_config.NetworkSettings.Networks or {} + + if config.WorkingDir == image_config.WorkingDir then + config.WorkingDir = "" + end + + if config.User == image_config.User then + config.User = "" + end + + if table_equal(config.Cmd, image_config.Cmd) then + config.Cmd = nil + end + + if table_equal(config.Entrypoint, image_config.Entrypoint) then + config.Entrypoint = nil + end + + if table_equal(config.ExposedPorts, image_config.ExposedPorts) then + config.ExposedPorts = nil + end + + config.Env = table_subtract(config.Env, image_config.Env) + config.Labels = table_subtract(config.Labels, image_config.Labels) + config.Volumes = map_subtract(config.Volumes, image_config.Volumes) + + if old_host_config.PortBindings and next(old_host_config.PortBindings) ~= nil then + config.ExposedPorts = {} + for p, v in pairs(old_host_config.PortBindings) do + config.ExposedPorts[p] = { HostPort=v[1] and v[1].HostPort } + end + end + + local network_setting = {} + local multi_network = false + local extra_network = {} + + for k, v in pairs(old_network_setting) do + if multi_network then + extra_network[k] = v + else + network_setting[k] = v + end + multi_network = true + end + + local host_config = old_host_config + host_config.Mounts = {} + for i, v in ipairs(container_config.Mounts) do + if v.Type == "volume" then + table.insert(host_config.Mounts, { + Type = v.Type, + Target = v.Destination, + Source = v.Source:match("([^/]+)\/_data"), + BindOptions = (v.Type == "bind") and {Propagation = v.Propagation} or nil, + ReadOnly = not v.RW + }) + end + end + + local create_body = config + create_body["HostConfig"] = host_config + create_body["NetworkingConfig"] = {EndpointsConfig = network_setting} + create_body = _docker.clear_empty_tables(create_body) or {} + extra_network = _docker.clear_empty_tables(extra_network) or {} + + return create_body, extra_network +end + +local upgrade = function(self, request) + _docker:clear_status() + + local container_info = self.containers:inspect({id = request.id}) + + if container_info.code > 300 and type(container_info.body) == "table" then + return container_info + end + + local image_name = container_info.body.Config.Image + if not image_name:match(".-:.+") then + image_name = image_name .. ":latest" + end + + local old_image_id = container_info.body.Image + local container_name = container_info.body.Name:sub(2) + + local image_id, res = update_image(self, image_name) + if res and res.code and res.code ~= 200 then + return res + end + + if image_id == old_image_id then + return {code = 305, body = {message = "Already up to date"}} + end + + local t = os.date("%Y%m%d%H%M%S") + _docker:append_status("Container: rename" .. " " .. container_name .. " to ".. container_name .. "_old_".. t .. "...") + res = self.containers:rename({name = container_name, query = { name = container_name .. "_old_" ..t }}) + if res and res.code and res.code < 300 then + _docker:append_status("done\n") + else + return res + end + + local image_config = self.images:inspect({id = old_image_id}).body.Config + local create_body, extra_network = get_config(container_info.body, image_config) + + -- create new container + _docker:append_status("Container: Create" .. " " .. container_name .. "...") + create_body = _docker.clear_empty_tables(create_body) + res = self.containers:create({name = container_name, body = create_body}) + if res and res.code and res.code > 300 then + return res + end + _docker:append_status("done\n") + + -- extra networks need to network connect action + for k, v in pairs(extra_network) do + _docker:append_status("Networks: Connect" .. " " .. container_name .. "...") + res = self.networks:connect({id = k, body = {Container = container_name, EndpointConfig = v}}) + if res and res.code and res.code > 300 then + return res + end + _docker:append_status("done\n") + end + + _docker:append_status("Container: " .. "Stop" .. " " .. container_name .. "_old_".. t .. "...") + res = self.containers:stop({name = container_name .. "_old_" ..t }) + if res and res.code and res.code < 305 then + _docker:append_status("done\n") + else + return res + end + + _docker:append_status("Container: " .. "Start" .. " " .. container_name .. "...") + res = self.containers:start({name = container_name}) + if res and res.code and res.code < 305 then + _docker:append_status("done\n") + else + return res + end + + _docker:clear_status() + return res +end + +local duplicate_config = function (self, request) + local container_info = self.containers:inspect({id = request.id}) + if container_info.code > 300 and type(container_info.body) == "table" then + return nil + end + + local old_image_id = container_info.body.Image + local image_config = self.images:inspect({id = old_image_id}).body.Config + + return get_config(container_info.body, image_config) +end + +_docker.new = function() + local host = nil + local port = nil + local socket_path = nil + local debug_path = nil + + if uci:get_bool("dockerd", "dockerman", "remote_endpoint") then + host = uci:get("dockerd", "dockerman", "remote_host") or nil + port = uci:get("dockerd", "dockerman", "remote_port") or nil + else + socket_path = uci:get("dockerd", "dockerman", "socket_path") or "/var/run/docker.sock" + end + + local debug = uci:get_bool("dockerd", "dockerman", "debug") + if debug then + debug_path = uci:get("dockerd", "dockerman", "debug_path") or "/tmp/.docker_debug" + end + + local status_path = uci:get("dockerd", "dockerman", "status_path") or "/tmp/.docker_action_status" + + _docker.options = { + host = host, + port = port, + socket_path = socket_path, + debug = debug, + debug_path = debug_path, + status_path = status_path + } + + local _new = docker.new(_docker.options) + _new.containers_upgrade = upgrade + _new.containers_duplicate_config = duplicate_config + + return _new +end + +_docker.options.status_path = uci:get("dockerd", "dockerman", "status_path") or "/tmp/.docker_action_status" + +_docker.append_status=function(self,val) + if not val then + return + end + local file_docker_action_status=io.open(self.options.status_path, "a+") + file_docker_action_status:write(val) + file_docker_action_status:close() +end + +_docker.write_status=function(self,val) + if not val then + return + end + local file_docker_action_status=io.open(self.options.status_path, "w+") + file_docker_action_status:write(val) + file_docker_action_status:close() +end + +_docker.read_status=function(self) + return fs.readfile(self.options.status_path) +end + +_docker.clear_status=function(self) + fs.remove(self.options.status_path) +end + +local status_cb = function(res, source, handler) + res.body = res.body or {} + while true do + local chunk = source() + if chunk then + --standard output to res.body + table.insert(res.body, chunk) + handler(chunk) + else + return + end + end +end + +--{"status":"Pulling from library\/debian","id":"latest"} +--{"status":"Pulling fs layer","progressDetail":[],"id":"50e431f79093"} +--{"status":"Downloading","progressDetail":{"total":50381971,"current":2029978},"id":"50e431f79093","progress":"[==> ] 2.03MB\/50.38MB"} +--{"status":"Download complete","progressDetail":[],"id":"50e431f79093"} +--{"status":"Extracting","progressDetail":{"total":50381971,"current":17301504},"id":"50e431f79093","progress":"[=================> ] 17.3MB\/50.38MB"} +--{"status":"Pull complete","progressDetail":[],"id":"50e431f79093"} +--{"status":"Digest: sha256:a63d0b2ecbd723da612abf0a8bdb594ee78f18f691d7dc652ac305a490c9b71a"} +--{"status":"Status: Downloaded newer image for debian:latest"} +_docker.pull_image_show_status_cb = function(res, source) + return status_cb(res, source, function(chunk) + local json_parse = luci.jsonc.parse + local step = json_parse(chunk) + if type(step) == "table" then + local buf = _docker:read_status() + local num = 0 + local str = '\t' .. (step.id and (step.id .. ": ") or "") .. (step.status and step.status or "") .. (step.progress and (" " .. step.progress) or "").."\n" + if step.id then + buf, num = buf:gsub("\t"..step.id .. ": .-\n", str) + end + if num == 0 then + buf = buf .. str + end + _docker:write_status(buf) + end + end) +end + +--{"status":"Downloading from https://downloads.openwrt.org/releases/19.07.0/targets/x86/64/openwrt-19.07.0-x86-64-generic-rootfs.tar.gz"} +--{"status":"Importing","progressDetail":{"current":1572391,"total":3821714},"progress":"[====================\u003e ] 1.572MB/3.822MB"} +--{"status":"sha256:d5304b58e2d8cc0a2fd640c05cec1bd4d1229a604ac0dd2909f13b2b47a29285"} +_docker.import_image_show_status_cb = function(res, source) + return status_cb(res, source, function(chunk) + local json_parse = luci.jsonc.parse + local step = json_parse(chunk) + if type(step) == "table" then + local buf = _docker:read_status() + local num = 0 + local str = '\t' .. (step.status and step.status or "") .. (step.progress and (" " .. step.progress) or "").."\n" + if step.status then + buf, num = buf:gsub("\t"..step.status .. " .-\n", str) + end + if num == 0 then + buf = buf .. str + end + _docker:write_status(buf) + end + end) +end + +_docker.create_macvlan_interface = function(name, device, gateway, subnet) + if not fs.access("/etc/config/network") or not fs.access("/etc/config/firewall") then + return + end + + if uci:get_bool("dockerd", "dockerman", "remote_endpoint") then + return + end + + local ip = require "luci.ip" + local if_name = "docker_"..name + local dev_name = "macvlan_"..name + local net_mask = tostring(ip.new(subnet):mask()) + local lan_interfaces + + -- add macvlan device + uci:delete("network", dev_name) + uci:set("network", dev_name, "device") + uci:set("network", dev_name, "name", dev_name) + uci:set("network", dev_name, "ifname", device) + uci:set("network", dev_name, "type", "macvlan") + uci:set("network", dev_name, "mode", "bridge") + + -- add macvlan interface + uci:delete("network", if_name) + uci:set("network", if_name, "interface") + uci:set("network", if_name, "proto", "static") + uci:set("network", if_name, "ifname", dev_name) + uci:set("network", if_name, "ipaddr", gateway) + uci:set("network", if_name, "netmask", net_mask) + uci:foreach("firewall", "zone", function(s) + if s.name == "lan" then + local interfaces + if type(s.network) == "table" then + interfaces = table.concat(s.network, " ") + uci:delete("firewall", s[".name"], "network") + else + interfaces = s.network and s.network or "" + end + interfaces = interfaces .. " " .. if_name + interfaces = interfaces:gsub("%s+", " ") + uci:set("firewall", s[".name"], "network", interfaces) + end + end) + + uci:commit("firewall") + uci:commit("network") + + os.execute("ifup " .. if_name) +end + +_docker.remove_macvlan_interface = function(name) + if not fs.access("/etc/config/network") or not fs.access("/etc/config/firewall") then + return + end + + if uci:get_bool("dockerd", "dockerman", "remote_endpoint") then + return + end + + local if_name = "docker_"..name + local dev_name = "macvlan_"..name + uci:foreach("firewall", "zone", function(s) + if s.name == "lan" then + local interfaces + if type(s.network) == "table" then + interfaces = table.concat(s.network, " ") + else + interfaces = s.network and s.network or "" + end + interfaces = interfaces and interfaces:gsub(if_name, "") + interfaces = interfaces and interfaces:gsub("%s+", " ") + uci:set("firewall", s[".name"], "network", interfaces) + end + end) + + uci:delete("network", dev_name) + uci:delete("network", if_name) + uci:commit("network") + uci:commit("firewall") + + os.execute("ip link del " .. if_name) +end + +_docker.byte_format = function (byte) + if not byte then return 'NaN' end + local suff = {"B", "KB", "MB", "GB", "TB"} + for i=1, 5 do + if byte > 1024 and i < 5 then + byte = byte / 1024 + else + return string.format("%.2f %s", byte, suff[i]) + end + end +end + +return _docker diff --git a/luci-app-dockerman/luasrc/view/dockerman/apply_widget.htm b/luci-app-dockerman/luasrc/view/dockerman/apply_widget.htm new file mode 100644 index 000000000..f96b2d72a --- /dev/null +++ b/luci-app-dockerman/luasrc/view/dockerman/apply_widget.htm @@ -0,0 +1,147 @@ + + + diff --git a/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinebutton.htm b/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinebutton.htm new file mode 100644 index 000000000..a061a6dba --- /dev/null +++ b/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinebutton.htm @@ -0,0 +1,7 @@ +
+ <% if self:cfgvalue(section) ~= false then %> + " type="submit"" <% if self.disable then %>disabled <% end %><%= attr("name", cbid) .. attr("id", cbid) .. attr("value", self.inputtitle or self.title)%> /> + <% else %> + - + <% end %> +
diff --git a/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinevalue.htm b/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinevalue.htm new file mode 100644 index 000000000..e4b0cf7a0 --- /dev/null +++ b/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinevalue.htm @@ -0,0 +1,33 @@ +
+ + <%- if self.password then -%> + /> + <%- end -%> + 0, "data-choices", { self.keylist, self.vallist }) + %> /> + <%- if self.password then -%> +
+ <% end %> +
diff --git a/luci-app-dockerman/luasrc/view/dockerman/cbi/namedsection.htm b/luci-app-dockerman/luasrc/view/dockerman/cbi/namedsection.htm new file mode 100644 index 000000000..244d2c10a --- /dev/null +++ b/luci-app-dockerman/luasrc/view/dockerman/cbi/namedsection.htm @@ -0,0 +1,9 @@ +<% if self:cfgvalue(self.section) then section = self.section %> +
+ <%+cbi/tabmenu%> +
+ <%+cbi/ucisection%> +
+
+<% end %> + diff --git a/luci-app-dockerman/luasrc/view/dockerman/cbi/xfvalue.htm b/luci-app-dockerman/luasrc/view/dockerman/cbi/xfvalue.htm new file mode 100644 index 000000000..04f7bc2ee --- /dev/null +++ b/luci-app-dockerman/luasrc/view/dockerman/cbi/xfvalue.htm @@ -0,0 +1,10 @@ +<%+cbi/valueheader%> + /> + disabled <% end %><%= + attr("id", cbid) .. attr("name", cbid) .. attr("value", self.enabled or 1) .. + ifattr((self:cfgvalue(section) or self.default) == self.enabled, "checked", "checked") + %> /> + > +<%+cbi/valuefooter%> diff --git a/luci-app-dockerman/luasrc/view/dockerman/container.htm b/luci-app-dockerman/luasrc/view/dockerman/container.htm new file mode 100644 index 000000000..9f05d9d58 --- /dev/null +++ b/luci-app-dockerman/luasrc/view/dockerman/container.htm @@ -0,0 +1,28 @@ +
+ + + diff --git a/luci-app-dockerman/luasrc/view/dockerman/container_console.htm b/luci-app-dockerman/luasrc/view/dockerman/container_console.htm new file mode 100644 index 000000000..1a4dc2a6b --- /dev/null +++ b/luci-app-dockerman/luasrc/view/dockerman/container_console.htm @@ -0,0 +1,6 @@ +
+ +
+ diff --git a/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm b/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm new file mode 100644 index 000000000..2e0650d9d --- /dev/null +++ b/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm @@ -0,0 +1,332 @@ + +
+ +
+ + +
+
+
+ + diff --git a/luci-app-dockerman/luasrc/view/dockerman/container_stats.htm b/luci-app-dockerman/luasrc/view/dockerman/container_stats.htm new file mode 100644 index 000000000..bbcd633e7 --- /dev/null +++ b/luci-app-dockerman/luasrc/view/dockerman/container_stats.htm @@ -0,0 +1,81 @@ + diff --git a/luci-app-dockerman/luasrc/view/dockerman/containers_running_stats.htm b/luci-app-dockerman/luasrc/view/dockerman/containers_running_stats.htm new file mode 100644 index 000000000..d88e28be9 --- /dev/null +++ b/luci-app-dockerman/luasrc/view/dockerman/containers_running_stats.htm @@ -0,0 +1,91 @@ + \ No newline at end of file diff --git a/luci-app-dockerman/luasrc/view/dockerman/images_import.htm b/luci-app-dockerman/luasrc/view/dockerman/images_import.htm new file mode 100644 index 000000000..0ad6e0fce --- /dev/null +++ b/luci-app-dockerman/luasrc/view/dockerman/images_import.htm @@ -0,0 +1,104 @@ + + +
+ disabled <% end %>/> + +
+ + diff --git a/luci-app-dockerman/luasrc/view/dockerman/images_load.htm b/luci-app-dockerman/luasrc/view/dockerman/images_load.htm new file mode 100644 index 000000000..b201510ac --- /dev/null +++ b/luci-app-dockerman/luasrc/view/dockerman/images_load.htm @@ -0,0 +1,40 @@ +
+ disabled <% end %>/> + +
+ diff --git a/luci-app-dockerman/luasrc/view/dockerman/logs.htm b/luci-app-dockerman/luasrc/view/dockerman/logs.htm new file mode 100644 index 000000000..6cd2cb095 --- /dev/null +++ b/luci-app-dockerman/luasrc/view/dockerman/logs.htm @@ -0,0 +1,13 @@ +<% if self.title == "Events" then %> +<%+header%> +

<%:Docker - Events%>

+
+

<%:Events%>

+<% end %> +
+ +
+<% if self.title == "Events" then %> +
+<%+footer%> +<% end %> diff --git a/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm b/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm new file mode 100644 index 000000000..338fd59d5 --- /dev/null +++ b/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm @@ -0,0 +1,102 @@ + + + +<%+cbi/valueheader%> + + + +<%+cbi/valuefooter%> diff --git a/luci-app-dockerman/luasrc/view/dockerman/overview.htm b/luci-app-dockerman/luasrc/view/dockerman/overview.htm new file mode 100644 index 000000000..e491fc512 --- /dev/null +++ b/luci-app-dockerman/luasrc/view/dockerman/overview.htm @@ -0,0 +1,197 @@ + + +
+
+
+
+
+ +
+
+
+

<%:Containers%>

+

+ <%- if self.containers_total ~= "-" then -%><%- end -%> + <%=self.containers_running%> + /<%=self.containers_total%> + <%- if self.containers_total ~= "-" then -%><%- end -%> +

+
+
+
+
+
+
+
+ +
+
+
+

<%:Images%>

+

+ <%- if self.images_total ~= "-" then -%><%- end -%> + <%=self.images_used%> + /<%=self.images_total%> + <%- if self.images_total ~= "-" then -%><%- end -%> +

+
+
+
+
+
+
+
+ +
+
+
+

<%:Networks%>

+

+ <%- if self.networks_total ~= "-" then -%><%- end -%> + <%=self.networks_total%> + + <%- if self.networks_total ~= "-" then -%><%- end -%> +

+
+
+
+
+
+
+
+ +
+
+
+

<%:Volumes%>

+

+ <%- if self.volumes_total ~= "-" then -%><%- end -%> + <%=self.volumes_total%> + + <%- if self.volumes_total ~= "-" then -%><%- end -%> +

+
+
+
+
diff --git a/luci-app-dockerman/luasrc/view/dockerman/volume_size.htm b/luci-app-dockerman/luasrc/view/dockerman/volume_size.htm new file mode 100644 index 000000000..dc024734b --- /dev/null +++ b/luci-app-dockerman/luasrc/view/dockerman/volume_size.htm @@ -0,0 +1,21 @@ + \ No newline at end of file diff --git a/luci-app-dockerman/po/templates/dockerman.pot b/luci-app-dockerman/po/templates/dockerman.pot new file mode 100644 index 000000000..0d6a5de98 --- /dev/null +++ b/luci-app-dockerman/po/templates/dockerman.pot @@ -0,0 +1,1002 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:619 +msgid "A list of kernel capabilities to add to the container" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:69 +msgid "Access Control" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:223 +msgid "Add" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:595 +msgid "Add host device to the container" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:571 +msgid "Advance" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:586 +msgid "Allocates an ephemeral host port for all of a container's exposed ports" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:118 +msgid "Allowed access interfaces" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:498 +msgid "Always pull image first" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:29 +msgid "" +"An overview with the relevant data is displayed here with which the LuCI " +"docker client is connected." +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:43 +msgid "Api Version" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:94 +msgid "Auto create macvlan interface in Openwrt" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:18 +msgid "Auto start" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:134 +msgid "Available" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:47 +msgid "Base device" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:553 +msgid "Bind Mount(-v)" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:554 +msgid "Bind mount a volume" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:596 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:652 +msgid "Block IO Weight" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:653 +msgid "" +"Block IO weight (relative weight) accepts a weight value between 10 and 1000" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:597 +msgid "" +"Block IO weight (relative weight) accepts a weight value between 10 and 1000." +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:61 +msgid "Bridge (Support direct communication between MAC VLANs)" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:42 +msgid "Bridge device" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:84 +msgid "" +"By entering a valid image name with the corresponding version, the docker " +"image can be downloaded from the configured registry." +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:618 +msgid "CAP-ADD(--cap-add)" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:581 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:635 +msgid "CPU Shares Weight" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:779 +msgid "CPU Useage" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:636 +msgid "" +"CPU shares relative weight, if 0 is set, the system will ignore the value " +"and use the default of 1024" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:582 +msgid "" +"CPU shares relative weight, if 0 is set, the system will ignore the value " +"and use the default of 1024." +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:573 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:626 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:44 +msgid "CPUs" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:159 +msgid "Can NOT connect to docker daemon, please check!!" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm:91 +msgid "Cancel" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:60 +msgid "Client connection" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:347 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:687 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:182 +msgid "Command" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm:100 +msgid "Command line" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm:72 +msgid "Command line Error" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/controller/dockerman.lua:17 +msgid "Configuration" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:36 +msgid "Configure the default bridge network" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:405 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:707 +msgid "Connect" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:403 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:437 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:473 +msgid "Connect Network" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:74 +msgid "Connect to remote docker endpoint" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container.htm:7 +msgid "Console" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:161 +msgid "Container Info" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:650 +msgid "Container Inspect" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:671 +msgid "Container Logs" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:473 +msgid "Container Name" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:92 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:58 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua:29 +msgid "Container detail" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/controller/dockerman.lua:38 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:142 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:148 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua:87 +#: applications/luci-app-dockerman/luasrc/view/dockerman/overview.htm:133 +msgid "Containers" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:94 +msgid "Create macvlan interface" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:465 +msgid "Create new docker container" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:31 +msgid "Create new docker network" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:312 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:153 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua:92 +msgid "Created" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:33 +msgid "DELETING" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:371 +msgid "DNS" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:51 +msgid "Debug" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:35 +msgid "Default bridge" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:363 +msgid "Device" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:594 +msgid "Device(--device)" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:396 +msgid "Disconnect" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/controller/dockerman.lua:14 +msgid "Docker" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:12 +msgid "Docker - Configuration" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:192 +msgid "Docker - Container (%s)" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:128 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:450 +msgid "Docker - Containers" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/logs.htm:3 +msgid "Docker - Events" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:72 +msgid "Docker - Images" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:15 +msgid "Docker - Network" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:54 +msgid "Docker - Networks" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:28 +msgid "Docker - Overview" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua:69 +msgid "Docker - Volumes" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:16 +msgid "Docker Daemon settings" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:489 +msgid "Docker Image" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:30 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:46 +msgid "Docker Root Dir" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:93 +msgid "Docker Socket Path" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:42 +msgid "Docker Version" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/apply_widget.htm:91 +msgid "Docker actions done." +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:70 +msgid "DockerMan" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:13 +msgid "DockerMan is a simple docker manager client for LuCI" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:68 +msgid "DockerMan settings" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:172 +msgid "Download" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:82 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:40 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua:85 +msgid "Driver" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:265 +msgid "Duplicate/Edit" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:120 +msgid "Enable IPv6" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:351 +msgid "Env" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:546 +msgid "Environmental Variable(-e)" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:54 +msgid "Error" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/controller/dockerman.lua:42 +#: applications/luci-app-dockerman/luasrc/view/dockerman/logs.htm:5 +msgid "Events" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:116 +msgid "Exclude IPs" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:247 +msgid "Export" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:585 +msgid "Exposed All Ports(-P)" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:560 +msgid "Exposed Ports(-p)" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:55 +msgid "Fatal" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container.htm:6 +msgid "File" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:324 +msgid "Finish Time" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:220 +msgid "Force Remove" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:88 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:106 +msgid "Gateway" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:33 +msgid "Github" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinevalue.htm:4 +msgid "Go to relevant configuration page" +msgstr "" + +#: applications/luci-app-dockerman/root/usr/share/rpcd/acl.d/luci-app-dockerman.json:3 +msgid "Grant UCI access for luci-app-dockerman" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:330 +msgid "Healthy" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:578 +msgid "Host Name" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:100 +msgid "Host or IP Address for the connection to a remote docker instance" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:300 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:142 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:78 +msgid "ID" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:44 +msgid "IP VLAN" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:111 +msgid "IP range" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:522 +msgid "IPv4 Address" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:132 +msgid "IPv6 Gateway" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:126 +msgid "IPv6 Subnet" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:304 +#: applications/luci-app-dockerman/luasrc/view/dockerman/images_import.htm:54 +msgid "Image" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/controller/dockerman.lua:39 +#: applications/luci-app-dockerman/luasrc/view/dockerman/overview.htm:151 +msgid "Images" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:132 +msgid "Images overview" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/images_import.htm:4 +msgid "Import" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:125 +msgid "Import Image" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:47 +msgid "Index Server Address" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:52 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:414 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:102 +#: applications/luci-app-dockerman/luasrc/view/dockerman/container.htm:3 +msgid "Info" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:74 +msgid "Ingress" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:75 +msgid "" +"Ingress network is the network which provides the routing-mesh in swarm mode" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container.htm:8 +msgid "Inspect" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:477 +msgid "Interactive (-i)" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:86 +msgid "Internal" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:66 +msgid "Ipvlan Mode" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:43 +msgid "" +"It replaces the daemon registry mirrors with a new set of registry mirrors" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:238 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:264 +msgid "Kill" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:70 +msgid "L2 bridge" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:71 +msgid "L3 bridge" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:359 +msgid "Links" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:527 +msgid "Links with other containers" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:283 +#: applications/luci-app-dockerman/luasrc/view/dockerman/images_load.htm:2 +msgid "Load" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:49 +msgid "Log Level" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:661 +msgid "Log driver options" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container.htm:9 +msgid "Logs" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:43 +msgid "MAC VLAN" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:589 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:644 +msgid "Memory" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:783 +msgid "Memory Useage" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:645 +msgid "" +"Memory limit (format: []). Number is a positive integer. Unit " +"can be one of b, k, m, or g. Minimum is 4M" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:590 +msgid "" +"Memory limit (format: []). Number is a positive integer. Unit " +"can be one of b, k, m, or g. Minimum is 4M." +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:57 +msgid "Mode" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua:90 +msgid "Mount Point" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:603 +msgid "Mount tmpfs directory" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:343 +msgid "Mount/Volume" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:175 +msgid "Mounts" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:295 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:419 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua:83 +msgid "Name" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:37 +msgid "Name of the network that can be selected during container creation" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:394 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:528 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:169 +msgid "Network" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:80 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:36 +msgid "Network Name" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/controller/dockerman.lua:40 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:518 +#: applications/luci-app-dockerman/luasrc/view/dockerman/overview.htm:169 +msgid "Networks" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:59 +msgid "Networks overview" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:104 +msgid "New" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:39 +#: applications/luci-app-dockerman/luasrc/view/dockerman/images_import.htm:54 +msgid "New tag" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:627 +msgid "Number of CPUs. Number is a fractional number. 0.000 means no limit" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:574 +msgid "Number of CPUs. Number is a fractional number. 0.000 means no limit." +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:73 +msgid "" +"On this page all images are displayed that are available on the system and " +"with which a container can be created." +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:193 +msgid "On this page, the selected container can be managed." +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:82 +msgid "Options" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:45 +msgid "Overlay network" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/controller/dockerman.lua:37 +msgid "Overview" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:33 +msgid "PLEASE CONFIRM" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:84 +msgid "Parent Interface" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:64 +msgid "Pass-through (Mirror physical device to single MAC VLAN)" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/images_import.htm:54 +msgid "Please input new tag" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:270 +msgid "Please input the PATH and select the file !" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:82 +msgid "Please input the PORT or HOST IP of remote docker instance!" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:86 +msgid "Please input the SOCKET PATH of docker daemon!" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm:91 +msgid "Plese input command line:" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:355 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:172 +msgid "Ports" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:124 +msgid "Ports allowed to be accessed" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:62 +msgid "Private (Prevent communication between MAC VLANs)" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:504 +msgid "Privileged" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:561 +msgid "Publish container's port(s) to the host" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:100 +msgid "Pull" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:83 +msgid "Pull Image" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:42 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:48 +msgid "Registry Mirrors" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:73 +msgid "Remote Endpoint" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:99 +msgid "Remote Host" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:106 +msgid "Remote Port" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:274 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:274 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:210 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:115 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua:108 +#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:173 +msgid "Remove" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:43 +#: applications/luci-app-dockerman/luasrc/view/dockerman/images_import.htm:82 +msgid "Remove tag" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:171 +msgid "Rename" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:145 +msgid "RepoTags" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:469 +msgid "Resolve CLI" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container.htm:4 +msgid "Resources" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:220 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:244 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:87 +msgid "Restart" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:334 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:427 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:510 +msgid "Restart Policy" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:86 +msgid "Restrict external access to the network" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinevalue.htm:31 +msgid "Reveal/hide password" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:566 +msgid "Run command" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:230 +msgid "Save" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:533 +msgid "Set custom DNS servers" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:547 +msgid "Set environment variables to inside the container" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:50 +msgid "Set the logging level" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:151 +msgid "Size" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:61 +msgid "" +"Specifies where the Docker daemon will listen for client connections " +"(default: unix:///var/run/docker.sock)" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:211 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:234 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:65 +msgid "Start" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:319 +msgid "Start Time" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:789 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:790 +#: applications/luci-app-dockerman/luasrc/view/dockerman/container.htm:5 +msgid "Stats" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:308 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:165 +msgid "Status" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:229 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:254 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:65 +msgid "Stop" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm:91 +msgid "Submit" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:86 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:101 +msgid "Subnet" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:375 +msgid "Sysctl" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:610 +msgid "Sysctl(--sysctl)" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:611 +msgid "Sysctls (kernel parameters) options" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:792 +msgid "TOP" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:483 +msgid "TTY (-t)" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container_stats.htm:56 +msgid "TX/RX" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:579 +msgid "The hostname to use for the container" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:662 +msgid "The logging configuration for this container" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:540 +msgid "" +"The user that commands are run as inside the container.(format: name|uid[:" +"group|gid])" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:129 +msgid "" +"This page displays all containers that have been created on the connected " +"docker host." +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:55 +msgid "" +"This page displays all docker networks that have been created on the " +"connected docker host." +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:367 +msgid "Tmpfs" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:602 +msgid "Tmpfs(--tmpfs)" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:45 +msgid "Total Memory" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:697 +msgid "UID" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:297 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:336 +msgid "Update" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:256 +msgid "Upgrade" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:7 +msgid "Upload" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:303 +#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:304 +msgid "Upload Error" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:294 +msgid "Upload Success" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container_stats.htm:48 +msgid "Upload/Download" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:339 +msgid "User" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:539 +msgid "User(-u)" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:63 +msgid "VEPA (Virtual Ethernet Port Aggregator)" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/controller/dockerman.lua:41 +#: applications/luci-app-dockerman/luasrc/view/dockerman/overview.htm:187 +msgid "Volumes" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua:73 +msgid "Volumes overview" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:53 +msgid "Warning" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:126 +msgid "" +"When pressing the Import button, both a local image can be loaded onto the " +"system and a valid image tar can be downloaded from remote." +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:124 +msgid "" +"Which Port(s) can be accessed, it's not restricted by the Allowed Access " +"interfaces configuration. Use this configuration with caution!" +msgstr "" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:118 +msgid "" +"Which interface(s) can access containers under the bridge network, fill-in " +"Interface Name" +msgstr "" diff --git a/luci-app-dockerman/po/zh-cn/dockerman.po b/luci-app-dockerman/po/zh-cn/dockerman.po new file mode 100644 index 000000000..2bdc11b8d --- /dev/null +++ b/luci-app-dockerman/po/zh-cn/dockerman.po @@ -0,0 +1,1094 @@ +msgid "" +msgstr "" +"PO-Revision-Date: 2021-03-19 04:16+0000\n" +"Last-Translator: Eric \n" +"Language-Team: Chinese (Simplified) \n" +"Language: zh_Hans\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Weblate 4.5.2-dev\n" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:619 +msgid "A list of kernel capabilities to add to the container" +msgstr "要添加到容器的内核功能列表" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:69 +msgid "Access Control" +msgstr "访问控制" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:223 +msgid "Add" +msgstr "新增" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:595 +msgid "Add host device to the container" +msgstr "将主机设备添加到容器" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:571 +msgid "Advance" +msgstr "高级选项" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:586 +msgid "Allocates an ephemeral host port for all of a container's exposed ports" +msgstr "为容器的所有暴露端口分配临时主机端口" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:118 +msgid "Allowed access interfaces" +msgstr "允许的访问接口" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:498 +msgid "Always pull image first" +msgstr "总是先拉取镜像" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:29 +msgid "" +"An overview with the relevant data is displayed here with which the LuCI " +"docker client is connected." +msgstr "在此展示与LuCI docker客户端相连接的相关数据的概览。" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:43 +msgid "Api Version" +msgstr "Api 版本" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:94 +msgid "Auto create macvlan interface in Openwrt" +msgstr "在 Openwrt 中自动创建 macvlan 界面" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:18 +msgid "Auto start" +msgstr "自动启动" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:134 +msgid "Available" +msgstr "可用" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:47 +msgid "Base device" +msgstr "基设备" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:553 +msgid "Bind Mount(-v)" +msgstr "绑定挂载(-v)" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:554 +msgid "Bind mount a volume" +msgstr "绑定挂载卷" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:596 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:652 +msgid "Block IO Weight" +msgstr "块 IO 权重" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:653 +msgid "" +"Block IO weight (relative weight) accepts a weight value between 10 and 1000" +msgstr "块 IO 权重(相对权重)接受10到1000之间的数值" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:597 +msgid "" +"Block IO weight (relative weight) accepts a weight value between 10 and 1000." +msgstr "块 IO 权重(相对权重)接受10到1000之间的数值。" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:61 +msgid "Bridge (Support direct communication between MAC VLANs)" +msgstr "桥接(支持 MAC VLAN 之间的直接通信)" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:42 +msgid "Bridge device" +msgstr "Bridge device" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:84 +msgid "" +"By entering a valid image name with the corresponding version, the docker " +"image can be downloaded from the configured registry." +msgstr "" +"通过输入具有相应版本的有效映像名称,可以从镜像存储中心(Registry)中下载" +"docker映像。" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:618 +msgid "CAP-ADD(--cap-add)" +msgstr "权限控制(--cap-add)" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:581 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:635 +msgid "CPU Shares Weight" +msgstr "CPU 共享权重" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:779 +msgid "CPU Useage" +msgstr "CPU 使用率" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:636 +msgid "" +"CPU shares relative weight, if 0 is set, the system will ignore the value " +"and use the default of 1024" +msgstr "CPU 共享相对权重,如果设置为 0,则系统将忽略该值并使用默认值 1024" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:582 +msgid "" +"CPU shares relative weight, if 0 is set, the system will ignore the value " +"and use the default of 1024." +msgstr "CPU 共享相对权重,如果设置为 0,则系统将忽略该值并使用默认值 1024。" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:573 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:626 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:44 +msgid "CPUs" +msgstr "线程数量" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:159 +msgid "Can NOT connect to docker daemon, please check!!" +msgstr "无法连接到docker守护进程(docker daemon),请检查!!" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm:91 +msgid "Cancel" +msgstr "取消" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:60 +msgid "Client connection" +msgstr "客户端连接" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:347 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:687 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:182 +msgid "Command" +msgstr "命令" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm:100 +msgid "Command line" +msgstr "命令行" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm:72 +msgid "Command line Error" +msgstr "命令行错误" + +#: applications/luci-app-dockerman/luasrc/controller/dockerman.lua:17 +msgid "Configuration" +msgstr "配置" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:36 +msgid "Configure the default bridge network" +msgstr "配置默认桥接网络" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:405 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:707 +msgid "Connect" +msgstr "连接" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:403 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:437 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:473 +msgid "Connect Network" +msgstr "连接网络" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:74 +msgid "Connect to remote docker endpoint" +msgstr "连接到远程docker" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container.htm:7 +msgid "Console" +msgstr "控制台" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:161 +msgid "Container Info" +msgstr "容器信息" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:650 +msgid "Container Inspect" +msgstr "检查容器" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:671 +msgid "Container Logs" +msgstr "容器日志" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:473 +msgid "Container Name" +msgstr "容器名称" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:92 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:58 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua:29 +msgid "Container detail" +msgstr "容器详情" + +#: applications/luci-app-dockerman/luasrc/controller/dockerman.lua:38 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:142 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:148 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua:87 +#: applications/luci-app-dockerman/luasrc/view/dockerman/overview.htm:133 +msgid "Containers" +msgstr "容器" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:94 +msgid "Create macvlan interface" +msgstr "创建 macvlan 接口" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:465 +msgid "Create new docker container" +msgstr "创建 docker 容器" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:31 +msgid "Create new docker network" +msgstr "创建 docker 网络" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:312 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:153 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua:92 +msgid "Created" +msgstr "创建时间" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:33 +msgid "DELETING" +msgstr "删除中" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:371 +msgid "DNS" +msgstr "DNS" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:51 +msgid "Debug" +msgstr "调试" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:35 +msgid "Default bridge" +msgstr "默认桥接" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:363 +msgid "Device" +msgstr "设备" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:594 +msgid "Device(--device)" +msgstr "设备(--device)" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:396 +msgid "Disconnect" +msgstr "断开" + +#: applications/luci-app-dockerman/luasrc/controller/dockerman.lua:14 +msgid "Docker" +msgstr "Docker" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:12 +msgid "Docker - Configuration" +msgstr "Docker - 配置" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:192 +msgid "Docker - Container (%s)" +msgstr "Docker - 容器 (%s)" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:128 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:450 +msgid "Docker - Containers" +msgstr "Docker - 容器" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/logs.htm:3 +msgid "Docker - Events" +msgstr "Docker - 事件" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:72 +msgid "Docker - Images" +msgstr "Docker - 镜像" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:15 +msgid "Docker - Network" +msgstr "Docker - 网络" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:54 +msgid "Docker - Networks" +msgstr "Docker - 网络" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:28 +msgid "Docker - Overview" +msgstr "Docker - 概览" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua:69 +msgid "Docker - Volumes" +msgstr "Docker - 存储卷" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:16 +msgid "Docker Daemon settings" +msgstr "Docker 服务端(Docker Daemon)设置" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:489 +msgid "Docker Image" +msgstr "Docker 镜像" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:30 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:46 +msgid "Docker Root Dir" +msgstr "Docker 根目录" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:93 +msgid "Docker Socket Path" +msgstr "Docker 套接字路径" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:42 +msgid "Docker Version" +msgstr "Docker 版本" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/apply_widget.htm:91 +msgid "Docker actions done." +msgstr "Docker 执行完成。" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:70 +msgid "DockerMan" +msgstr "DockerMan" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:13 +msgid "DockerMan is a simple docker manager client for LuCI" +msgstr "DockerMan是用于LuCI的简单docker管理器客户端" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:68 +msgid "DockerMan settings" +msgstr "DockerMan设置" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:172 +msgid "Download" +msgstr "下载" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:82 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:40 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua:85 +msgid "Driver" +msgstr "驱动" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:265 +msgid "Duplicate/Edit" +msgstr "复制/编辑" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:120 +msgid "Enable IPv6" +msgstr "启用 IPv6" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:351 +msgid "Env" +msgstr "环境变量" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:546 +msgid "Environmental Variable(-e)" +msgstr "环境变量(-e)" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:54 +msgid "Error" +msgstr "错误" + +#: applications/luci-app-dockerman/luasrc/controller/dockerman.lua:42 +#: applications/luci-app-dockerman/luasrc/view/dockerman/logs.htm:5 +msgid "Events" +msgstr "事件" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:116 +msgid "Exclude IPs" +msgstr "排除 IP" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:247 +msgid "Export" +msgstr "导出" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:585 +msgid "Exposed All Ports(-P)" +msgstr "暴露所有端口(-P)" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:560 +msgid "Exposed Ports(-p)" +msgstr "暴露端口(-p)" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:55 +msgid "Fatal" +msgstr "致命的" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container.htm:6 +msgid "File" +msgstr "文件" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:324 +msgid "Finish Time" +msgstr "完成时间" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:220 +msgid "Force Remove" +msgstr "强制移除" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:88 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:106 +msgid "Gateway" +msgstr "网关" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:33 +msgid "Github" +msgstr "Github" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinevalue.htm:4 +msgid "Go to relevant configuration page" +msgstr "进入相关配置页面" + +#: applications/luci-app-dockerman/root/usr/share/rpcd/acl.d/luci-app-dockerman.json:3 +msgid "Grant UCI access for luci-app-dockerman" +msgstr "授予 UCI 访问 luci-app-dockerman 的权限" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:330 +msgid "Healthy" +msgstr "健康" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:578 +msgid "Host Name" +msgstr "主机名" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:100 +msgid "Host or IP Address for the connection to a remote docker instance" +msgstr "连接到远程Docker实例的主机名或IP地址" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:300 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:142 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:78 +msgid "ID" +msgstr "ID" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:44 +msgid "IP VLAN" +msgstr "IP VLAN" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:111 +msgid "IP range" +msgstr "IP 范围" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:522 +msgid "IPv4 Address" +msgstr "IPv4 地址" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:132 +msgid "IPv6 Gateway" +msgstr "IPv6 网关" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:126 +msgid "IPv6 Subnet" +msgstr "IPv6 子网" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:304 +#: applications/luci-app-dockerman/luasrc/view/dockerman/images_import.htm:54 +msgid "Image" +msgstr "镜像" + +#: applications/luci-app-dockerman/luasrc/controller/dockerman.lua:39 +#: applications/luci-app-dockerman/luasrc/view/dockerman/overview.htm:151 +msgid "Images" +msgstr "镜像" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:132 +msgid "Images overview" +msgstr "镜像概览" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/images_import.htm:4 +msgid "Import" +msgstr "导入" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:125 +msgid "Import Image" +msgstr "导入镜像" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:47 +msgid "Index Server Address" +msgstr "索引服务器地址" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:52 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:414 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:102 +#: applications/luci-app-dockerman/luasrc/view/dockerman/container.htm:3 +msgid "Info" +msgstr "信息" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:74 +msgid "Ingress" +msgstr "入口" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:75 +msgid "" +"Ingress network is the network which provides the routing-mesh in swarm mode" +msgstr "入口网络是以群模式提供路由网格的网络" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container.htm:8 +msgid "Inspect" +msgstr "检查" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:477 +msgid "Interactive (-i)" +msgstr "交互(-i)" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:86 +msgid "Internal" +msgstr "内部" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:66 +msgid "Ipvlan Mode" +msgstr "Ipvlan 模式" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:43 +msgid "" +"It replaces the daemon registry mirrors with a new set of registry mirrors" +msgstr "" +"设置新的镜像存储中心(Registry)镜像源,这将取代服务端(daemon)配置的镜像存" +"储中心(Registry)的镜像源" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:238 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:264 +msgid "Kill" +msgstr "强制关闭" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:70 +msgid "L2 bridge" +msgstr "L2 桥接" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:71 +msgid "L3 bridge" +msgstr "L3 桥接" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:359 +msgid "Links" +msgstr "链接" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:527 +msgid "Links with other containers" +msgstr "与其他容器的链接" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:283 +#: applications/luci-app-dockerman/luasrc/view/dockerman/images_load.htm:2 +msgid "Load" +msgstr "负载" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:49 +msgid "Log Level" +msgstr "日志等级" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:661 +msgid "Log driver options" +msgstr "日志驱动选项" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container.htm:9 +msgid "Logs" +msgstr "日志" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:43 +msgid "MAC VLAN" +msgstr "MAC VLAN" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:589 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:644 +msgid "Memory" +msgstr "内存" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:783 +msgid "Memory Useage" +msgstr "内存使用率" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:645 +msgid "" +"Memory limit (format: []). Number is a positive integer. Unit " +"can be one of b, k, m, or g. Minimum is 4M" +msgstr "" +"内存限制(格式:<数字>[<单位>])。数字是正整数。单位可以是 b、k、m 或 g 之一。" +"最小值为 4M" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:590 +msgid "" +"Memory limit (format: []). Number is a positive integer. Unit " +"can be one of b, k, m, or g. Minimum is 4M." +msgstr "" +"内存限制(格式:<数字>[<单位>])。数字是正整数。单位可以是 b、k、m 或 g 之一。" +"最小值为 4M。" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:57 +msgid "Mode" +msgstr "模式" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua:90 +msgid "Mount Point" +msgstr "挂载点" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:603 +msgid "Mount tmpfs directory" +msgstr "挂载 tmpfs 目录" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:343 +msgid "Mount/Volume" +msgstr "挂载/卷" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:175 +msgid "Mounts" +msgstr "挂载点" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:295 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:419 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua:83 +msgid "Name" +msgstr "名称" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:37 +msgid "Name of the network that can be selected during container creation" +msgstr "在容器创建时可以选择网络的名称" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:394 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:528 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:169 +msgid "Network" +msgstr "网络" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:80 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:36 +msgid "Network Name" +msgstr "网络名称" + +#: applications/luci-app-dockerman/luasrc/controller/dockerman.lua:40 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:518 +#: applications/luci-app-dockerman/luasrc/view/dockerman/overview.htm:169 +msgid "Networks" +msgstr "网络" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:59 +msgid "Networks overview" +msgstr "网络概览" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:104 +msgid "New" +msgstr "新建" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:39 +#: applications/luci-app-dockerman/luasrc/view/dockerman/images_import.htm:54 +msgid "New tag" +msgstr "新建标签" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:627 +msgid "Number of CPUs. Number is a fractional number. 0.000 means no limit" +msgstr "CPU 数量。数字是小数。0.000 表示没有限制" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:574 +msgid "Number of CPUs. Number is a fractional number. 0.000 means no limit." +msgstr "CPU 数量。数字是小数。0.000 表示没有限制。" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:73 +msgid "" +"On this page all images are displayed that are available on the system and " +"with which a container can be created." +msgstr "在此页面上,显示系统上可用的所有镜像文件,并可以用它们来创建容器" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:193 +msgid "On this page, the selected container can be managed." +msgstr "在此页面可以管理所选的容器。" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:82 +msgid "Options" +msgstr "选项" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:45 +msgid "Overlay network" +msgstr "Overlay network" + +#: applications/luci-app-dockerman/luasrc/controller/dockerman.lua:37 +msgid "Overview" +msgstr "概览" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:33 +msgid "PLEASE CONFIRM" +msgstr "请确认" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:84 +msgid "Parent Interface" +msgstr "父接口" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:64 +msgid "Pass-through (Mirror physical device to single MAC VLAN)" +msgstr "直通(将物理设备镜像到单独的 MAC VLAN)" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/images_import.htm:54 +msgid "Please input new tag" +msgstr "请输入新的标签" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:270 +msgid "Please input the PATH and select the file !" +msgstr "请输入路径并选择文件!" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:82 +msgid "Please input the PORT or HOST IP of remote docker instance!" +msgstr "请输入合法的远程docker实例端口和主机IP" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:86 +msgid "Please input the SOCKET PATH of docker daemon!" +msgstr "请输入合法docker服务端(docker daemon)的SOCKET地址" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm:91 +msgid "Plese input command line:" +msgstr "请输入 的命令行:" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:355 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:172 +msgid "Ports" +msgstr "端口" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:124 +msgid "Ports allowed to be accessed" +msgstr "允许访问的端口" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:62 +msgid "Private (Prevent communication between MAC VLANs)" +msgstr "专用(阻止 MAC VLAN 之间的通信)" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:504 +msgid "Privileged" +msgstr "特权模式" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:561 +msgid "Publish container's port(s) to the host" +msgstr "将容器的端口发布到主机" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:100 +msgid "Pull" +msgstr "拉取" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:83 +msgid "Pull Image" +msgstr "拉取镜像" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:42 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:48 +msgid "Registry Mirrors" +msgstr "镜像加速器" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:73 +msgid "Remote Endpoint" +msgstr "远程实例" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:99 +msgid "Remote Host" +msgstr "远程主机" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:106 +msgid "Remote Port" +msgstr "远程端口" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:274 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:274 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:210 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:115 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua:108 +#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:173 +msgid "Remove" +msgstr "移除" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:43 +#: applications/luci-app-dockerman/luasrc/view/dockerman/images_import.htm:82 +msgid "Remove tag" +msgstr "移除标签" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:171 +msgid "Rename" +msgstr "重命名" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:145 +msgid "RepoTags" +msgstr "仓库标签" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:469 +msgid "Resolve CLI" +msgstr "解析 CLI" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container.htm:4 +msgid "Resources" +msgstr "资源" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:220 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:244 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:87 +msgid "Restart" +msgstr "重新启动" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:334 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:427 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:510 +msgid "Restart Policy" +msgstr "重启策略" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:86 +msgid "Restrict external access to the network" +msgstr "限制外部网络访问" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/cbi/inlinevalue.htm:31 +msgid "Reveal/hide password" +msgstr "显示/隐藏 密码" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:566 +msgid "Run command" +msgstr "运行命令" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:230 +msgid "Save" +msgstr "保存" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:533 +msgid "Set custom DNS servers" +msgstr "设置自定义 DNS 服务器" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:547 +msgid "Set environment variables to inside the container" +msgstr "在容器内部设置环境变量" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:50 +msgid "Set the logging level" +msgstr "设置日志记录级别" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:151 +msgid "Size" +msgstr "大小" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:61 +msgid "" +"Specifies where the Docker daemon will listen for client connections " +"(default: unix:///var/run/docker.sock)" +msgstr "" +"指定Docker服务端(Docker daemon)将在何处侦听客户端连接(默认: unix:///var/" +"run/docker.sock)" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:211 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:234 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:65 +msgid "Start" +msgstr "启动" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:319 +msgid "Start Time" +msgstr "开始时间" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:789 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:790 +#: applications/luci-app-dockerman/luasrc/view/dockerman/container.htm:5 +msgid "Stats" +msgstr "状态" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:308 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:165 +msgid "Status" +msgstr "状态" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:229 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:254 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:65 +msgid "Stop" +msgstr "停止" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm:91 +msgid "Submit" +msgstr "提交" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:86 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:101 +msgid "Subnet" +msgstr "子网" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:375 +msgid "Sysctl" +msgstr "系统控制" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:610 +msgid "Sysctl(--sysctl)" +msgstr "系统控制(--sysctl)" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:611 +msgid "Sysctls (kernel parameters) options" +msgstr "系统控制(内核参数)选项" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:792 +msgid "TOP" +msgstr "TOP" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:483 +msgid "TTY (-t)" +msgstr "TTY(-t)" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container_stats.htm:56 +msgid "TX/RX" +msgstr "发射/接收" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:579 +msgid "The hostname to use for the container" +msgstr "容器使用的主机名" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:662 +msgid "The logging configuration for this container" +msgstr "该容器的日志记录配置" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:540 +msgid "" +"The user that commands are run as inside the container.(format: name|uid[:" +"group|gid])" +msgstr "在容器中以用户运行命令。(格式:name|uid[:group|gid])" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua:129 +msgid "" +"This page displays all containers that have been created on the connected " +"docker host." +msgstr "此页面显示在连接的Docker主机上已创建的所有容器。" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua:55 +msgid "" +"This page displays all docker networks that have been created on the " +"connected docker host." +msgstr "此页面显示在已连接的Docker主机上创建的所有Docker网络。" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:367 +msgid "Tmpfs" +msgstr "Tmpfs" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:602 +msgid "Tmpfs(--tmpfs)" +msgstr "Tmpfs(--tmpfs)" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua:45 +msgid "Total Memory" +msgstr "总内存" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:697 +msgid "UID" +msgstr "UID" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:297 +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:336 +msgid "Update" +msgstr "更新" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:256 +msgid "Upgrade" +msgstr "升级" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:7 +msgid "Upload" +msgstr "上传" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:303 +#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:304 +msgid "Upload Error" +msgstr "上传错误" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container_file_manager.htm:294 +msgid "Upload Success" +msgstr "上传成功" + +#: applications/luci-app-dockerman/luasrc/view/dockerman/container_stats.htm:48 +msgid "Upload/Download" +msgstr "上传/下载" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua:339 +msgid "User" +msgstr "用户" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua:539 +msgid "User(-u)" +msgstr "用户(-u)" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua:63 +msgid "VEPA (Virtual Ethernet Port Aggregator)" +msgstr "VEPA(虚拟以太网端口聚合器)" + +#: applications/luci-app-dockerman/luasrc/controller/dockerman.lua:41 +#: applications/luci-app-dockerman/luasrc/view/dockerman/overview.htm:187 +msgid "Volumes" +msgstr "存储卷" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua:73 +msgid "Volumes overview" +msgstr "卷概览" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:53 +msgid "Warning" +msgstr "警告" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua:126 +msgid "" +"When pressing the Import button, both a local image can be loaded onto the " +"system and a valid image tar can be downloaded from remote." +msgstr "" +"按下导入按钮时,既可以将本地镜像文件加载到系统上,也可以从远程下载有效的Tar格" +"式的镜像文件。" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:124 +msgid "" +"Which Port(s) can be accessed, it's not restricted by the Allowed Access " +"interfaces configuration. Use this configuration with caution!" +msgstr "设置可以被访问的端口,该配置不受“允许的访问接口”配置的限制。请谨慎使用该配置选项!" + +#: applications/luci-app-dockerman/luasrc/model/cbi/dockerman/configuration.lua:118 +msgid "" +"Which interface(s) can access containers under the bridge network, fill-in " +"Interface Name" +msgstr "哪些接口可以访问桥接网络下的容器,请填写接口名称" + +#~ msgid "Containers allowed to be accessed" +#~ msgstr "允许访问的容器" + +#~ msgid "" +#~ "Which container(s) under bridge network can be accessed, even from " +#~ "interfaces that are not allowed, fill-in Container Id or Name" +#~ msgstr "" +#~ "桥接网络下哪些容器可以访问,即使是不允许从接口访问,也要填写容器 ID 或名称" + +#~ msgid "Connect to remote endpoint" +#~ msgstr "连接到远程终端" + +#~ msgid "Global settings" +#~ msgstr "全局设定" + +#~ msgid "Path" +#~ msgstr "路径" + +#~ msgid "Please input the PATH !" +#~ msgstr "请输入合法路径!" + +#~ msgid "Setting" +#~ msgstr "设置" + +#~ msgid "Specifies where the Docker daemon will listen for client connections" +#~ msgstr "指定Docker服务端(Docker daemon)侦听客户端连接的位置" + +#~ msgid "Docker Container" +#~ msgstr "Docker 容器" + +#~ msgid "" +#~ "DockerMan is a Simple Docker manager client for LuCI, If you have any " +#~ "issue please visit:" +#~ msgstr "" +#~ "DockerMan 是一个简单的 LuCI 客户端 Docker 管理器,如果您有任何问题,请访" +#~ "问:" + +#~ msgid "Import Images" +#~ msgstr "导入镜像" + +#~ msgid "New Container" +#~ msgstr "新建容器" + +#~ msgid "New Network" +#~ msgstr "新建网络" + +#~ msgid "Macvlan Mode" +#~ msgstr "Macvlan 模式" + +#~ msgid "" +#~ "Daemon unix socket (unix:///var/run/docker.sock) or TCP Remote Hosts " +#~ "(tcp://0.0.0.0:2375), default: unix:///var/run/docker.sock" +#~ msgstr "" +#~ "守护进程 unix 套接字 (unix:///var/run/docker.sock) 或 TCP 远程主机 " +#~ "(tcp://0.0.0.0:2375),默认值:unix:///var/run/docker.sock" + +#~ msgid "Docker Daemon" +#~ msgstr "Docker 服务端" + +#~ msgid "Dockerman connect to remote endpoint" +#~ msgstr "Dockerman 连接到远程端点" + +#~ msgid "Enable" +#~ msgstr "启用" + +#~ msgid "Server Host" +#~ msgstr "服务器主机" + +#~ msgid "Contaienr Info" +#~ msgstr "容器信息" diff --git a/luci-app-dockerman/po/zh_Hans b/luci-app-dockerman/po/zh_Hans new file mode 100644 index 000000000..41451e4a1 --- /dev/null +++ b/luci-app-dockerman/po/zh_Hans @@ -0,0 +1 @@ +zh-cn \ No newline at end of file diff --git a/luci-app-dockerman/postinst b/luci-app-dockerman/postinst new file mode 100644 index 000000000..b0db1cb89 --- /dev/null +++ b/luci-app-dockerman/postinst @@ -0,0 +1,14 @@ +#!/bin/sh + +/init.sh env +touch /etc/config/dockerd +uci set dockerd.dockerman=dockerman +uci set dockerd.dockerman.socket_path=`uci get dockerd.dockerman.socket_path 2&> /dev/null || echo '/var/run/docker.sock'` +uci set dockerd.dockerman.status_path=`uci get dockerd.dockerman.status_path 2&> /dev/null || echo '/tmp/.docker_action_status'` +uci set dockerd.dockerman.debug=`uci get dockerd.dockerman.debug 2&> /dev/null || echo 'false'` +uci set dockerd.dockerman.debug_path=`uci get dockerd.dockerman.debug_path 2&> /dev/null || echo '/tmp/.docker_debug'` +uci set dockerd.dockerman.remote_port=`uci get dockerd.dockerman.remote_port 2&> /dev/null || echo '2375'` +uci set dockerd.dockerman.remote_endpoint=`uci get dockerd.dockerman.remote_endpoint 2&> /dev/null || echo '0'` +uci del_list dockerd.dockerman.ac_allowed_interface='br-lan' +uci add_list dockerd.dockerman.ac_allowed_interface='br-lan' +uci commit dockerd \ No newline at end of file diff --git a/luci-app-dockerman/root/etc/init.d/dockerman b/luci-app-dockerman/root/etc/init.d/dockerman new file mode 100644 index 000000000..80309aeab --- /dev/null +++ b/luci-app-dockerman/root/etc/init.d/dockerman @@ -0,0 +1,131 @@ +#!/bin/sh /etc/rc.common + +START=99 +USE_PROCD=1 +# PROCD_DEBUG=1 +config_load 'dockerd' +# config_get daemon_ea "dockerman" daemon_ea +_DOCKERD=/etc/init.d/dockerd + +docker_running(){ + docker version > /dev/null 2>&1 + return $? +} + +add_ports() { + [ $# -eq 0 ] && return + $($_DOCKERD running) && docker_running || return 1 + ids=$@ + for id in $ids; do + id=$(docker ps --filter "ID=$id" --quiet) + [ -z "$id" ] && { + echo "Docker containner not running"; + return 1; + } + ports=$(docker ps --filter "ID=$id" --format "{{.Ports}}") + # echo "$ports" + for port in $ports; do + echo "$port" | grep -qE "^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}:.*$" || continue; + [ "${port: -1}" == "," ] && port="${port:0:-1}" + local protocol="" + [ "${port%tcp}" != "$port" ] && protocol="/tcp" + [ "${port%udp}" != "$port" ] && protocol="/udp" + [ "$protocol" == "" ] && continue + port="${port%%->*}" + port="${port##*:}" + uci_add_list dockerd dockerman ac_allowed_ports "${port}${protocol}" + done + done + uci_commit dockerd +} + + +convert() { + _convert() { + _id=$1 + _id=$(docker ps --all --filter "ID=$_id" --quiet) + if [ -z "$_id" ]; then + uci_remove_list dockerd dockerman ac_allowed_container "$1" + return + fi + if /etc/init.d/dockerman add_ports "$_id"; then + uci_remove_list dockerd dockerman ac_allowed_container "$_id" + fi + } + config_list_foreach dockerman ac_allowed_container _convert + uci_commit dockerd +} + +iptables_append(){ + # Wait for a maximum of 10 second per command, retrying every millisecond + local iptables_wait_args="--wait 10 --wait-interval 1000" + if ! iptables ${iptables_wait_args} --check $@ 2>/dev/null; then + iptables ${iptables_wait_args} -A $@ 2>/dev/null + fi +} + +init_dockerman_chain(){ + iptables -N DOCKER-MAN >/dev/null 2>&1 + iptables -F DOCKER-MAN >/dev/null 2>&1 + iptables -D DOCKER-USER -j DOCKER-MAN >/dev/null 2>&1 + iptables -I DOCKER-USER -j DOCKER-MAN >/dev/null 2>&1 +} + +delete_dockerman_chain(){ + iptables -D DOCKER-USER -j DOCKER-MAN >/dev/null 2>&1 + iptables -F DOCKER-MAN >/dev/null 2>&1 + iptables -X DOCKER-MAN >/dev/null 2>&1 +} + +add_allowed_interface(){ + iptables_append DOCKER-MAN -i $1 -o docker0 -j RETURN +} + +add_allowed_ports(){ + port=$1 + if [ "${port%/tcp}" != "$port" ]; then + iptables_append DOCKER-MAN -p tcp -m conntrack --ctorigdstport ${port%/tcp} --ctdir ORIGINAL -j RETURN + elif [ "${port%/udp}" != "$port" ]; then + iptables_append DOCKER-MAN -p udp -m conntrack --ctorigdstport ${port%/udp} --ctdir ORIGINAL -j RETURN + fi +} + +handle_allowed_ports(){ + config_list_foreach "dockerman" "ac_allowed_ports" add_allowed_ports +} + +handle_allowed_interface(){ + config_list_foreach "dockerman" "ac_allowed_interface" add_allowed_interface + iptables_append DOCKER-MAN -m conntrack --ctstate ESTABLISHED,RELATED -o docker0 -j RETURN >/dev/null 2>&1 + iptables_append DOCKER-MAN -m conntrack --ctstate NEW,INVALID -o docker0 -j DROP >/dev/null 2>&1 + iptables_append DOCKER-MAN -j RETURN >/dev/null 2>&1 +} + +start_service(){ + [ -x "$_DOCKERD" ] && $($_DOCKERD enabled) || return 0 + delete_dockerman_chain + $($_DOCKERD running) && docker_running || return 0 + init_dockerman_chain + handle_allowed_ports + handle_allowed_interface +} + +stop_service(){ + delete_dockerman_chain +} + +service_triggers() { + procd_add_reload_trigger 'dockerd' +} + +reload_service() { + start +} + +boot() { + sleep 5s + start +} + +extra_command "add_ports" "Add allowed ports based on the container ID(s)" +extra_command "convert" "Convert Ac allowed container to AC allowed ports" diff --git a/luci-app-dockerman/root/etc/uci-defaults/luci-app-dockerman b/luci-app-dockerman/root/etc/uci-defaults/luci-app-dockerman new file mode 100644 index 000000000..4358728a1 --- /dev/null +++ b/luci-app-dockerman/root/etc/uci-defaults/luci-app-dockerman @@ -0,0 +1,36 @@ +#!/bin/sh + +. $IPKG_INSTROOT/lib/functions.sh + +[ -x "$(command -v dockerd)" ] && chmod +x /etc/init.d/dockerman && /etc/init.d/dockerman enable >/dev/null 2>&1 +sed -i 's/self:cfgvalue(section) or {}/self:cfgvalue(section) or self.default or {}/' /usr/lib/lua/luci/view/cbi/dynlist.htm +/etc/init.d/uhttpd restart >/dev/null 2>&1 +rm -fr /tmp/luci-indexcache /tmp/luci-modulecache >/dev/null 2>&1 +touch /etc/config/dockerd +ls /etc/rc.d/*dockerd &> /dev/null && uci -q set dockerd.globals.auto_start="1" || uci -q set dockerd.globals.auto_start="0" +uci -q batch <<-EOF >/dev/null + set uhttpd.main.script_timeout="3600" + commit uhttpd + set dockerd.dockerman=dockerman + set dockerd.dockerman.socket_path='/var/run/docker.sock' + set dockerd.dockerman.status_path='/tmp/.docker_action_status' + set dockerd.dockerman.debug='false' + set dockerd.dockerman.debug_path='/tmp/.docker_debug' + set dockerd.dockerman.remote_endpoint='0' + + del_list dockerd.dockerman.ac_allowed_interface='br-lan' + add_list dockerd.dockerman.ac_allowed_interface='br-lan' + + commit dockerd +EOF +# remove dockerd firewall +config_load dockerd +remove_firewall(){ + cfg=${1} + uci_remove dockerd ${1} +} +config_foreach remove_firewall firewall +# Convert ac_allowed_container to ac_allowed_ports +(sleep 30s && /etc/init.d/dockerman convert;/etc/init.d/dockerman restart) & + +exit 0 diff --git a/luci-app-dockerman/root/usr/share/rpcd/acl.d/luci-app-dockerman.json b/luci-app-dockerman/root/usr/share/rpcd/acl.d/luci-app-dockerman.json new file mode 100644 index 000000000..78c2c6418 --- /dev/null +++ b/luci-app-dockerman/root/usr/share/rpcd/acl.d/luci-app-dockerman.json @@ -0,0 +1,11 @@ +{ + "luci-app-dockerman": { + "description": "Grant UCI access for luci-app-dockerman", + "read": { + "uci": [ "dockerd" ] + }, + "write": { + "uci": [ "dockerd" ] + } + } +} diff --git a/luci-app-firewall/Makefile b/luci-app-firewall/Makefile index e6e891e2d..b255cd140 100755 --- a/luci-app-firewall/Makefile +++ b/luci-app-firewall/Makefile @@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk LUCI_TITLE:=Firewall and Portforwarding application -LUCI_DEPENDS:=+@LINUX_5_4:firewall +@LINUX_5_15:uci-firewall +LUCI_DEPENDS:=+@LINUX_5_4:firewall +@(LINUX_5_15||LINUX_6_1):uci-firewall PKG_LICENSE:=Apache-2.0 PKG_VERSION:=omr-202103 diff --git a/luci-app-iperf/luasrc/controller/iperf.lua b/luci-app-iperf/luasrc/controller/iperf.lua index e6cb2ec3a..5df6b8ccb 100755 --- a/luci-app-iperf/luasrc/controller/iperf.lua +++ b/luci-app-iperf/luasrc/controller/iperf.lua @@ -26,7 +26,7 @@ function run_test(server,proto,mode,updown,omit,parallel,transmit,bitrate) if mode == "udp" then options = options .. " -u -b " .. bitrate end - if mode ~= "upload" then + if updown ~= "upload" then options = options .. " -R" end local ipv = "4" diff --git a/luci-app-ipsec-server/Makefile b/luci-app-ipsec-server/Makefile new file mode 100644 index 000000000..7b344a125 --- /dev/null +++ b/luci-app-ipsec-server/Makefile @@ -0,0 +1,24 @@ +# Copyright (C) 2018-2021 Lienol +# +# 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:=2 + +PKG_MAINTAINER:=Lienol + +LUCI_TITLE:=LuCI support for IPSec VPN Server +LUCI_DEPENDS:=+kmod-tun +luci-lib-jsonc +strongswan +strongswan-minimal +strongswan-mod-kernel-libipsec +strongswan-mod-openssl +strongswan-mod-xauth-generic +xl2tpd +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 diff --git a/luci-app-ipsec-server/luasrc/controller/ipsec-server.lua b/luci-app-ipsec-server/luasrc/controller/ipsec-server.lua new file mode 100644 index 000000000..e9a271af4 --- /dev/null +++ b/luci-app-ipsec-server/luasrc/controller/ipsec-server.lua @@ -0,0 +1,24 @@ +-- Copyright 2018-2020 Lienol +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 diff --git a/luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/l2tp_user.lua b/luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/l2tp_user.lua new file mode 100644 index 000000000..3b8460c65 --- /dev/null +++ b/luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/l2tp_user.lua @@ -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 diff --git a/luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/online.lua b/luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/online.lua new file mode 100644 index 000000000..d47b30053 --- /dev/null +++ b/luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/online.lua @@ -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 diff --git a/luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/settings.lua b/luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/settings.lua new file mode 100644 index 000000000..b88dd230d --- /dev/null +++ b/luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/settings.lua @@ -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 '' +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 '' + 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('%s', translate("L2TP/IPSec is not compatible with kernel-libipsec, which will disable this module.")) + end + o:depends("l2tp_enable", true) + end +end + +return m diff --git a/luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/users.lua b/luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/users.lua new file mode 100644 index 000000000..87a21be74 --- /dev/null +++ b/luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/users.lua @@ -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 diff --git a/luci-app-ipsec-server/luasrc/view/ipsec-server/ipsec-server_status.htm b/luci-app-ipsec-server/luasrc/view/ipsec-server/ipsec-server_status.htm new file mode 100644 index 000000000..93e36a405 --- /dev/null +++ b/luci-app-ipsec-server/luasrc/view/ipsec-server/ipsec-server_status.htm @@ -0,0 +1,21 @@ +<% include("cbi/map") %> + diff --git a/luci-app-ipsec-server/po/zh-cn/ipsec-server.po b/luci-app-ipsec-server/po/zh-cn/ipsec-server.po new file mode 100644 index 000000000..5c9049e02 --- /dev/null +++ b/luci-app-ipsec-server/po/zh-cn/ipsec-server.po @@ -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 "运行中" diff --git a/luci-app-ipsec-server/po/zh_Hans b/luci-app-ipsec-server/po/zh_Hans new file mode 100644 index 000000000..41451e4a1 --- /dev/null +++ b/luci-app-ipsec-server/po/zh_Hans @@ -0,0 +1 @@ +zh-cn \ No newline at end of file diff --git a/luci-app-ipsec-server/root/etc/config/luci-app-ipsec-server b/luci-app-ipsec-server/root/etc/config/luci-app-ipsec-server new file mode 100644 index 000000000..6d90a5d69 --- /dev/null +++ b/luci-app-ipsec-server/root/etc/config/luci-app-ipsec-server @@ -0,0 +1,7 @@ + +config service 'ipsec' + option enabled '0' + option secret 'ipsec' + option clientip '192.168.100.10/24' + + diff --git a/luci-app-ipsec-server/root/etc/init.d/luci-app-ipsec-server b/luci-app-ipsec-server/root/etc/init.d/luci-app-ipsec-server new file mode 100644 index 000000000..9371763e2 --- /dev/null +++ b/luci-app-ipsec-server/root/etc/init.d/luci-app-ipsec-server @@ -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 -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 -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 -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 -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" + ipt_del "iptables -t nat" "POSTROUTING" + 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} + 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 diff --git a/luci-app-ipsec-server/root/etc/uci-defaults/luci-app-ipsec-server b/luci-app-ipsec-server/root/etc/uci-defaults/luci-app-ipsec-server new file mode 100644 index 000000000..3a791a03a --- /dev/null +++ b/luci-app-ipsec-server/root/etc/uci-defaults/luci-app-ipsec-server @@ -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 diff --git a/luci-app-ipsec-server/root/usr/share/rpcd/acl.d/luci-app-ipsec-server.json b/luci-app-ipsec-server/root/usr/share/rpcd/acl.d/luci-app-ipsec-server.json new file mode 100644 index 000000000..d12ed9841 --- /dev/null +++ b/luci-app-ipsec-server/root/usr/share/rpcd/acl.d/luci-app-ipsec-server.json @@ -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" ] + } + } +} diff --git a/luci-app-ipsec-server/root/usr/share/xl2tpd/ip-down b/luci-app-ipsec-server/root/usr/share/xl2tpd/ip-down new file mode 100644 index 000000000..9434e7615 --- /dev/null +++ b/luci-app-ipsec-server/root/usr/share/xl2tpd/ip-down @@ -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" "$@" +} diff --git a/luci-app-ipsec-server/root/usr/share/xl2tpd/ip-up b/luci-app-ipsec-server/root/usr/share/xl2tpd/ip-up new file mode 100644 index 000000000..6109d037e --- /dev/null +++ b/luci-app-ipsec-server/root/usr/share/xl2tpd/ip-up @@ -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" "$@" +} diff --git a/luci-app-ipsec-vpnd/Makefile b/luci-app-ipsec-vpnd/Makefile new file mode 100644 index 000000000..f445cb15f --- /dev/null +++ b/luci-app-ipsec-vpnd/Makefile @@ -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 IPSec VPN Server (IKEv1 with PSK and Xauth) +LUCI_DEPENDS:=+strongswan +strongswan-minimal +strongswan-mod-xauth-generic +strongswan-mod-kernel-libipsec +kmod-tun +LUCI_PKGARCH:=all + +PKG_NAME:=luci-app-ipsec-vpnd +PKG_VERSION:=1.0 +PKG_RELEASE:=11 + +include $(TOPDIR)/feeds/luci/luci.mk + +# call BuildPackage - OpenWrt buildroot signature diff --git a/luci-app-ipsec-vpnd/luasrc/controller/ipsec-server.lua b/luci-app-ipsec-vpnd/luasrc/controller/ipsec-server.lua new file mode 100644 index 000000000..4594275e6 --- /dev/null +++ b/luci-app-ipsec-vpnd/luasrc/controller/ipsec-server.lua @@ -0,0 +1,18 @@ +module("luci.controller.ipsec-server", package.seeall) + +function index() + if not nixio.fs.access("/etc/config/ipsec") then + return + end + + entry({"admin", "vpn"}, firstchild(), "VPN", 45).dependent = false + entry({"admin", "vpn", "ipsec-server"}, cbi("ipsec-server"), _("IPSec VPN Server"), 80).dependent = false + entry({"admin", "vpn", "ipsec-server", "status"}, call("act_status")).leaf = true +end + +function act_status() + local e = {} + e.running = luci.sys.call("pgrep ipsec >/dev/null") == 0 + luci.http.prepare_content("application/json") + luci.http.write_json(e) +end diff --git a/luci-app-ipsec-vpnd/luasrc/model/cbi/ipsec-server.lua b/luci-app-ipsec-vpnd/luasrc/model/cbi/ipsec-server.lua new file mode 100644 index 000000000..c02a16070 --- /dev/null +++ b/luci-app-ipsec-vpnd/luasrc/model/cbi/ipsec-server.lua @@ -0,0 +1,35 @@ +mp = Map("ipsec") +mp.title = translate("IPSec VPN Server") +mp.description = translate("IPSec VPN connectivity using the native built-in VPN Client on iOS or Andriod (IKEv1 with PSK and Xauth)") + +mp:section(SimpleSection).template = "ipsec/ipsec_status" + +s = mp:section(NamedSection, "ipsec", "service") +s.anonymouse = true + +enabled = s:option(Flag, "enabled", translate("Enable")) +enabled.default = 0 +enabled.rmempty = false + +clientip = s:option(Value, "clientip", translate("VPN Client IP")) +clientip.description = translate("LAN DHCP reserved started IP addresses with the same subnet mask") +clientip.datatype = "ip4addr" +clientip.optional = false +clientip.rmempty = false + +clientdns = s:option(Value, "clientdns", translate("VPN Client DNS")) +clientdns.description = translate("DNS using in VPN tunnel.Set to the router's LAN IP is recommended") +clientdns.datatype = "ip4addr" +clientdns.optional = false +clientdns.rmempty = false + +account = s:option(Value, "account", translate("Account")) +account.datatype = "string" + +password = s:option(Value, "password", translate("Password")) +password.password = true + +secret = s:option(Value, "secret", translate("Secret Pre-Shared Key")) +secret.password = true + +return mp diff --git a/luci-app-ipsec-vpnd/luasrc/view/ipsec/ipsec_status.htm b/luci-app-ipsec-vpnd/luasrc/view/ipsec/ipsec_status.htm new file mode 100644 index 000000000..1e256c73b --- /dev/null +++ b/luci-app-ipsec-vpnd/luasrc/view/ipsec/ipsec_status.htm @@ -0,0 +1,22 @@ + + +
+

+ <%:Collecting data...%> +

+
diff --git a/luci-app-ipsec-vpnd/po/zh-cn/ipsec.po b/luci-app-ipsec-vpnd/po/zh-cn/ipsec.po new file mode 100644 index 000000000..727633502 --- /dev/null +++ b/luci-app-ipsec-vpnd/po/zh-cn/ipsec.po @@ -0,0 +1,32 @@ +msgid "IPSec VPN Server" +msgstr "IPSec VPN 服务器" + +msgid "IPSec VPN connectivity using the native built-in VPN Client on iOS or Andriod (IKEv1 with PSK and Xauth)" +msgstr "使用iOS 或者 Andriod (IKEv1 with PSK and Xauth) 原生内置 IPSec VPN 客户端进行连接" + +msgid "VPN Client IP" +msgstr "VPN客户端地址段" + +msgid "LAN DHCP reserved started IP addresses with the same subnet mask" +msgstr "VPN客户端使用独立子网段,默认为 10.10.10.2/24" + +msgid "VPN Client DNS" +msgstr "VPN客户端DNS服务器" + +msgid "DNS using in VPN tunnel.Set to the router's LAN IP is recommended" +msgstr "指定VPN客户端的DNS地址。推荐设置为 ipsec0 虚拟接口地址,默认为 10.10.10.1" + +msgid "Account" +msgstr "账户" + +msgid "Secret Pre-Shared Key" +msgstr "PSK密钥" + +msgid "IPSec VPN Server status" +msgstr "IPSec VPN 服务器运行状态" + +msgid "Disable from startup" +msgstr "禁止开机启动" + +msgid "Enable on startup" +msgstr "允许开机启动" diff --git a/luci-app-ipsec-vpnd/root/etc/config/ipsec b/luci-app-ipsec-vpnd/root/etc/config/ipsec new file mode 100644 index 000000000..4cd3f6422 --- /dev/null +++ b/luci-app-ipsec-vpnd/root/etc/config/ipsec @@ -0,0 +1,9 @@ + +config service 'ipsec' + option clientdns '10.10.10.1' + option account 'lean' + option secret 'myopenwrt' + option enabled '0' + option password '12345678' + option clientip '10.10.10.2/24' + diff --git a/luci-app-ipsec-vpnd/root/etc/init.d/ipsec b/luci-app-ipsec-vpnd/root/etc/init.d/ipsec new file mode 100644 index 000000000..5a4c6a217 --- /dev/null +++ b/luci-app-ipsec-vpnd/root/etc/init.d/ipsec @@ -0,0 +1,427 @@ +#!/bin/sh /etc/rc.common + +START=90 +STOP=10 + +USE_PROCD=1 +PROG=/usr/lib/ipsec/starter + +. $IPKG_INSTROOT/lib/functions.sh +. $IPKG_INSTROOT/lib/functions/network.sh + +IPSEC_SECRETS_FILE=/etc/ipsec.secrets +IPSEC_CONN_FILE=/etc/ipsec.conf +STRONGSWAN_CONF_FILE=/etc/strongswan.conf + +IPSEC_VAR_SECRETS_FILE=/var/ipsec/ipsec.secrets +IPSEC_VAR_CONN_FILE=/var/ipsec/ipsec.conf +STRONGSWAN_VAR_CONF_FILE=/var/ipsec/strongswan.conf + +WAIT_FOR_INTF=0 + +file_reset() { + : > "$1" +} + +xappend() { + local file="$1" + shift + + echo "${@}" >> "${file}" +} + +remove_include() { + local file="$1" + local include="$2" + + sed -i "\_${include}_d" "${file}" +} + +remove_includes() { + remove_include "${IPSEC_CONN_FILE}" "${IPSEC_VAR_CONN_FILE}" + remove_include "${IPSEC_SECRETS_FILE}" "${IPSEC_VAR_SECRETS_FILE}" + remove_include "${STRONGSWAN_CONF_FILE}" "${STRONGSWAN_VAR_CONF_FILE}" +} + +do_include() { + local conf="$1" + local uciconf="$2" + local backup=`mktemp -t -p /tmp/ ipsec-init-XXXXXX` + + [ ! -f "${conf}" ] && rm -rf "${conf}" + touch "${conf}" + + cat "${conf}" | grep -v "${uciconf}" > "${backup}" + mv "${backup}" "${conf}" + xappend "${conf}" "include ${uciconf}" + file_reset "${uciconf}" +} + +ipsec_reset() { + do_include "${IPSEC_CONN_FILE}" "${IPSEC_VAR_CONN_FILE}" +} + +ipsec_xappend() { + xappend "${IPSEC_VAR_CONN_FILE}" "$@" +} + +swan_reset() { + do_include "${STRONGSWAN_CONF_FILE}" "${STRONGSWAN_VAR_CONF_FILE}" +} + +swan_xappend() { + xappend "${STRONGSWAN_VAR_CONF_FILE}" "$@" +} + +secret_reset() { + do_include "${IPSEC_SECRETS_FILE}" "${IPSEC_VAR_SECRETS_FILE}" +} + +secret_xappend() { + xappend "${IPSEC_VAR_SECRETS_FILE}" "$@" +} + +warning() { + echo "WARNING: $@" >&2 +} + +add_crypto_proposal() { + local encryption_algorithm + local hash_algorithm + local dh_group + + config_get encryption_algorithm "$1" encryption_algorithm + config_get hash_algorithm "$1" hash_algorithm + config_get dh_group "$1" dh_group + + [ -n "${encryption_algorithm}" ] && \ + crypto="${crypto:+${crypto},}${encryption_algorithm}${hash_algorithm:+-${hash_algorithm}}${dh_group:+-${dh_group}}" +} + +set_crypto_proposal() { + local conf="$1" + local proposal + + crypto="" + + config_get crypto_proposal "$conf" crypto_proposal "" + for proposal in $crypto_proposal; do + add_crypto_proposal "$proposal" + done + + [ -n "${crypto}" ] && { + local force_crypto_proposal + + config_get_bool force_crypto_proposal "$conf" force_crypto_proposal + + [ "${force_crypto_proposal}" = "1" ] && crypto="${crypto}!" + } + + crypto_proposal="${crypto}" +} + +config_conn() { + # Generic ipsec conn section shared by tunnel and transport + local mode + local local_subnet + local local_nat + local local_sourceip + local local_updown + local local_firewall + local remote_subnet + local remote_sourceip + local remote_updown + local remote_firewall + local ikelifetime + local lifetime + local margintime + local keyingtries + local dpdaction + local dpddelay + local inactivity + local keyexchange + + config_get mode "$1" mode "route" + config_get local_subnet "$1" local_subnet "" + config_get local_nat "$1" local_nat "" + config_get local_sourceip "$1" local_sourceip "" + config_get local_updown "$1" local_updown "" + config_get local_firewall "$1" local_firewall "" + config_get remote_subnet "$1" remote_subnet "" + config_get remote_sourceip "$1" remote_sourceip "" + config_get remote_updown "$1" remote_updown "" + config_get remote_firewall "$1" remote_firewall "" + config_get ikelifetime "$1" ikelifetime "3h" + config_get lifetime "$1" lifetime "1h" + config_get margintime "$1" margintime "9m" + config_get keyingtries "$1" keyingtries "3" + config_get dpdaction "$1" dpdaction "none" + config_get dpddelay "$1" dpddelay "30s" + config_get inactivity "$1" inactivity + config_get keyexchange "$1" keyexchange "ikev2" + + [ -n "$local_nat" ] && local_subnet=$local_nat + + ipsec_xappend "conn $config_name-$1" + ipsec_xappend " left=%any" + ipsec_xappend " right=$remote_gateway" + + [ -n "$local_sourceip" ] && ipsec_xappend " leftsourceip=$local_sourceip" + [ -n "$local_subnet" ] && ipsec_xappend " leftsubnet=$local_subnet" + + [ -n "$local_firewall" ] && ipsec_xappend " leftfirewall=$local_firewall" + [ -n "$remote_firewall" ] && ipsec_xappend " rightfirewall=$remote_firewall" + + ipsec_xappend " ikelifetime=$ikelifetime" + ipsec_xappend " lifetime=$lifetime" + ipsec_xappend " margintime=$margintime" + ipsec_xappend " keyingtries=$keyingtries" + ipsec_xappend " dpdaction=$dpdaction" + ipsec_xappend " dpddelay=$dpddelay" + + [ -n "$inactivity" ] && ipsec_xappend " inactivity=$inactivity" + + if [ "$auth_method" = "psk" ]; then + ipsec_xappend " leftauth=psk" + ipsec_xappend " rightauth=psk" + + [ "$remote_sourceip" != "" ] && ipsec_xappend " rightsourceip=$remote_sourceip" + [ "$remote_subnet" != "" ] && ipsec_xappend " rightsubnet=$remote_subnet" + + ipsec_xappend " auto=$mode" + else + warning "AuthenticationMethod $auth_method not supported" + fi + + [ -n "$local_identifier" ] && ipsec_xappend " leftid=$local_identifier" + [ -n "$remote_identifier" ] && ipsec_xappend " rightid=$remote_identifier" + [ -n "$local_updown" ] && ipsec_xappend " leftupdown=$local_updown" + [ -n "$remote_updown" ] && ipsec_xappend " rightupdown=$remote_updown" + ipsec_xappend " keyexchange=$keyexchange" + + set_crypto_proposal "$1" + [ -n "${crypto_proposal}" ] && ipsec_xappend " esp=$crypto_proposal" + [ -n "${ike_proposal}" ] && ipsec_xappend " ike=$ike_proposal" +} + +config_tunnel() { + config_conn "$1" + + # Specific for the tunnel part + ipsec_xappend " type=tunnel" +} + +config_transport() { + config_conn "$1" + + # Specific for the transport part + ipsec_xappend " type=transport" +} + +config_remote() { + local enabled + local gateway + local pre_shared_key + local auth_method + + config_name=$1 + + config_get_bool enabled "$1" enabled 0 + [ $enabled -eq 0 ] && return + + config_get gateway "$1" gateway + config_get pre_shared_key "$1" pre_shared_key + config_get auth_method "$1" authentication_method + config_get local_identifier "$1" local_identifier "" + config_get remote_identifier "$1" remote_identifier "" + + [ "$gateway" = "any" ] && remote_gateway="%any" || remote_gateway="$gateway" + + [ -z "$local_identifier" ] && { + local ipdest + + [ "$remote_gateway" = "%any" ] && ipdest="1.1.1.1" || ipdest="$remote_gateway" + local_gateway=`ip route get $ipdest | awk -F"src" '/src/{gsub(/ /,"");print $2}'` + } + + [ -n "$local_identifier" ] && secret_xappend -n "$local_identifier " || secret_xappend -n "$local_gateway " + [ -n "$remote_identifier" ] && secret_xappend -n "$remote_identifier " || secret_xappend -n "$remote_gateway " + + secret_xappend ": PSK \"$pre_shared_key\"" + + set_crypto_proposal "$1" + ike_proposal="$crypto_proposal" + + config_list_foreach "$1" tunnel config_tunnel + + config_list_foreach "$1" transport config_transport + + ipsec_xappend "" +} + +config_ipsec() { + local debug + local rtinstall_enabled + local routing_tables_ignored + local routing_table + local routing_table_id + local interface + local device_list + + ipsec_reset + secret_reset + swan_reset + + ipsec_xappend "# generated by /etc/init.d/ipsec" + ipsec_xappend "version 2" + ipsec_xappend "" + + secret_xappend "# generated by /etc/init.d/ipsec" + + config_get debug "$1" debug 0 + config_get_bool rtinstall_enabled "$1" rtinstall_enabled 1 + [ $rtinstall_enabled -eq 1 ] && install_routes=yes || install_routes=no + + # prepare extra charon config option ignore_routing_tables + for routing_table in $(config_get "$1" "ignore_routing_tables"); do + if [ "$routing_table" -ge 0 ] 2>/dev/null; then + routing_table_id=$routing_table + else + routing_table_id=$(sed -n '/[ \t]*[0-9]\+[ \t]\+'$routing_table'[ \t]*$/s/[ \t]*\([0-9]\+\).*/\1/p' /etc/iproute2/rt_tables) + fi + + [ -n "$routing_table_id" ] && append routing_tables_ignored "$routing_table_id" + done + + local interface_list=$(config_get "$1" "interface") + if [ -z "$interface_list" ]; then + WAIT_FOR_INTF=0 + else + for interface in $interface_list; do + network_get_device device $interface + [ -n "$device" ] && append device_list "$device" "," + done + [ -n "$device_list" ] && WAIT_FOR_INTF=0 || WAIT_FOR_INTF=1 + fi + + swan_xappend "# generated by /etc/init.d/ipsec" + swan_xappend "charon {" + swan_xappend " load_modular = yes" + swan_xappend " install_routes = $install_routes" + [ -n "$routing_tables_ignored" ] && swan_xappend " ignore_routing_tables = $routing_tables_ignored" + [ -n "$device_list" ] && swan_xappend " interfaces_use = $device_list" + swan_xappend " plugins {" + swan_xappend " include /etc/strongswan.d/charon/*.conf" + swan_xappend " }" + swan_xappend " syslog {" + swan_xappend " identifier = ipsec" + swan_xappend " daemon {" + swan_xappend " default = $debug" + swan_xappend " }" + swan_xappend " auth {" + swan_xappend " default = $debug" + swan_xappend " }" + swan_xappend " }" + swan_xappend "}" +} + +prepare_env() { + mkdir -p /var/ipsec + remove_includes + config_load ipsec + config_foreach config_ipsec ipsec + config_foreach config_remote remote +} + +service_running() { + ipsec status > /dev/null 2>&1 +} + +reload_service() { + local bool vt_enabled=`uci get ipsec.@service[0].enabled 2>/dev/null` + [ "$vt_enabled" = 0 ] && /etc/init.d/ipsec stop && return + running && { + prepare_env + [ $WAIT_FOR_INTF -eq 0 ] && { + ipsec rereadall + ipsec reload + return + } + } + [ "$vt_enabled" = 1 ] && start +} + +check_ipsec_interface() { + local intf + + for intf in $(config_get "$1" interface); do + procd_add_interface_trigger "interface.*" "$intf" /etc/init.d/ipsec reload + done +} + +service_triggers() { + procd_add_reload_trigger "ipsec" + config load "ipsec" + config_foreach check_ipsec_interface ipsec +} + +start_service() { + local vt_enabled=`uci get ipsec.@service[0].enabled 2>/dev/null` + local vt_clientip=`uci get ipsec.@service[0].clientip` + local vt_clientdns=`uci get ipsec.@service[0].clientdns` + local vt_account=`uci get ipsec.@service[0].account` + local vt_password=`uci get ipsec.@service[0].password 2>/dev/null` + local vt_secret=`uci get ipsec.@service[0].secret 2>/dev/null` + + [ "$vt_enabled" = 0 ] && /etc/init.d/ipsec stop && return + + cat > /etc/ipsec.conf < /etc/ipsec.secrets </dev/null +iptables -D FORWARD -m policy --dir out --pol ipsec --proto esp -j ACCEPT 2>/dev/null +iptables -D INPUT -m policy --dir in --pol ipsec --proto esp -j ACCEPT 2>/dev/null +iptables -D OUTPUT -m policy --dir out --pol ipsec --proto esp -j ACCEPT 2>/dev/null + +iptables -I FORWARD -m policy --dir in --pol ipsec --proto esp -j ACCEPT +iptables -I FORWARD -m policy --dir out --pol ipsec --proto esp -j ACCEPT +iptables -I INPUT -m policy --dir in --pol ipsec --proto esp -j ACCEPT +iptables -I OUTPUT -m policy --dir out --pol ipsec --proto esp -j ACCEPT + +echo 1 > /proc/sys/net/ipv4/conf/br-lan/proxy_arp diff --git a/luci-app-ipsec-vpnd/root/etc/uci-defaults/luci-ipsec b/luci-app-ipsec-vpnd/root/etc/uci-defaults/luci-ipsec new file mode 100644 index 000000000..83b4c231e --- /dev/null +++ b/luci-app-ipsec-vpnd/root/etc/uci-defaults/luci-ipsec @@ -0,0 +1,81 @@ +#!/bin/sh + +uci -q batch <<-EOF >/dev/null + delete firewall.ipsecd + set firewall.ipsecd=include + set firewall.ipsecd.type=script + set firewall.ipsecd.path=/etc/ipsec.include + set firewall.ipsecd.reload=1 + commit firewall +EOF + +uci -q batch <<-EOF >/dev/null + delete network.VPN + set network.VPN=interface + set network.VPN.ifname="ipsec0" + set network.VPN.proto="static" + set network.VPN.ipaddr="10.10.10.1" + set network.VPN.netmask="255.255.255.0" + + commit network + + delete firewall.ike + add firewall rule + rename firewall.@rule[-1]="ike" + set firewall.@rule[-1].name="ike" + set firewall.@rule[-1].target="ACCEPT" + set firewall.@rule[-1].src="wan" + set firewall.@rule[-1].proto="udp" + set firewall.@rule[-1].dest_port="500" + + delete firewall.ipsec + add firewall rule + rename firewall.@rule[-1]="ipsec" + set firewall.@rule[-1].name="ipsec" + set firewall.@rule[-1].target="ACCEPT" + set firewall.@rule[-1].src="wan" + set firewall.@rule[-1].proto="udp" + set firewall.@rule[-1].dest_port="4500" + + delete firewall.ah + add firewall rule + rename firewall.@rule[-1]="ah" + set firewall.@rule[-1].name="ah" + set firewall.@rule[-1].target="ACCEPT" + set firewall.@rule[-1].src="wan" + set firewall.@rule[-1].proto="ah" + + delete firewall.esp + add firewall rule + rename firewall.@rule[-1]="esp" + set firewall.@rule[-1].name="esp" + set firewall.@rule[-1].target="ACCEPT" + set firewall.@rule[-1].src="wan" + set firewall.@rule[-1].proto="esp" + + delete firewall.VPN + set firewall.VPN=zone + set firewall.VPN.name="VPN" + set firewall.VPN.input="ACCEPT" + set firewall.VPN.forward="ACCEPT" + set firewall.VPN.output="ACCEPT" + set firewall.VPN.network="VPN" + + delete firewall.vpn + set firewall.vpn=forwarding + set firewall.vpn.name="vpn" + set firewall.vpn.dest="wan" + set firewall.vpn.src="VPN" + + commit firewall +EOF + +uci -q batch <<-EOF >/dev/null + delete ucitrack.@ipsec[-1] + add ucitrack ipsec + set ucitrack.@ipsec[-1].init=ipsec + commit ucitrack +EOF + +rm -f /tmp/luci-indexcache +exit 0 diff --git a/luci-app-mptcp/luasrc/model/cbi/mptcp.lua b/luci-app-mptcp/luasrc/model/cbi/mptcp.lua index 6c9091033..7d2e8ffaf 100755 --- a/luci-app-mptcp/luasrc/model/cbi/mptcp.lua +++ b/luci-app-mptcp/luasrc/model/cbi/mptcp.lua @@ -21,7 +21,8 @@ o:value(0, translate("disable")) o = s:option(ListValue, "mptcp_path_manager", translate("Multipath TCP path-manager"), translate("Default is fullmesh")) o:value("default", translate("default")) o:value("fullmesh", "fullmesh") -if uname.release:sub(1,4) ~= "5.14" then +--if tonumber(uname.release:sub(1,4)) <= 5.15 then +if uname.release:sub(1,4) ~= "5.15" or uname.release:sub(1,1) ~= "6" then o:value("ndiffports", "ndiffports") o:value("binder", "binder") if uname.release:sub(1,4) ~= "4.14" then @@ -30,7 +31,8 @@ if uname.release:sub(1,4) ~= "5.14" then end o = s:option(ListValue, "mptcp_scheduler", translate("Multipath TCP scheduler")) o:value("default", translate("default")) -if uname.release:sub(1,4) ~= "5.15" then +-- if tonumber(uname.release:sub(1,4)) <= 5.15 then +if uname.release:sub(1,4) ~= "5.15" or uname.release:sub(1,1) ~= "6" then o:value("roundrobin", "round-robin") o:value("redundant", "redundant") if uname.release:sub(1,4) ~= "4.14" then @@ -38,12 +40,14 @@ if uname.release:sub(1,4) ~= "5.15" then o:value("ecf", "ECF") end end -if uname.release:sub(1,4) ~= "5.15" then +-- if tonumber(uname.release:sub(1,4)) <= 5.15 then +if uname.release:sub(1,4) ~= "5.15" or uname.release:sub(1,1) ~= "6" then o = s:option(Value, "mptcp_syn_retries", translate("Multipath TCP SYN retries")) o.datatype = "uinteger" o.rmempty = false end -if uname.release:sub(1,4) ~= "5.15" then +-- if tonumber(uname.release:sub(1,4)) <= 5.15 then +if uname.release:sub(1,4) ~= "5.15" or uname.release:sub(1,1) ~= "6" then o = s:option(ListValue, "mptcp_version", translate("Multipath TCP version")) o:value(0, translate("0")) o:value(1, translate("1")) @@ -55,7 +59,8 @@ for cong in string.gmatch(availablecong, "[^%s]+") do o:value(cong, translate(cong)) end -if uname.release:sub(1,4) == "5.15" then +-- if tonumber(uname.release:sub(1,4)) >= 5.15 then +if uname.release:sub(1,4) == "5.15" or uname.release:sub(1,1) == "6" then o = s:option(Value, "mptcp_subflows", translate("specifies the maximum number of additional subflows allowed for each MPTCP connection")) o.datatype = "uinteger" o.rmempty = false diff --git a/luci-app-openmptcprouter/htdocs/luci-static/resources/computer.png b/luci-app-openmptcprouter/htdocs/luci-static/resources/computer.png index f90e4789b..4db90d225 100755 Binary files a/luci-app-openmptcprouter/htdocs/luci-static/resources/computer.png and b/luci-app-openmptcprouter/htdocs/luci-static/resources/computer.png differ diff --git a/luci-app-openmptcprouter/htdocs/luci-static/resources/modem.svg b/luci-app-openmptcprouter/htdocs/luci-static/resources/modem.svg index 5826795ae..d81067e16 100755 --- a/luci-app-openmptcprouter/htdocs/luci-static/resources/modem.svg +++ b/luci-app-openmptcprouter/htdocs/luci-static/resources/modem.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/luci-app-openmptcprouter/htdocs/luci-static/resources/openmptcprouter.png b/luci-app-openmptcprouter/htdocs/luci-static/resources/openmptcprouter.png index 9a4b11123..6a34fb74d 100755 Binary files a/luci-app-openmptcprouter/htdocs/luci-static/resources/openmptcprouter.png and b/luci-app-openmptcprouter/htdocs/luci-static/resources/openmptcprouter.png differ diff --git a/luci-app-openmptcprouter/htdocs/luci-static/resources/server.png b/luci-app-openmptcprouter/htdocs/luci-static/resources/server.png index f6ca87d5e..bbe685dcf 100755 Binary files a/luci-app-openmptcprouter/htdocs/luci-static/resources/server.png and b/luci-app-openmptcprouter/htdocs/luci-static/resources/server.png differ diff --git a/luci-app-openmptcprouter/luasrc/controller/openmptcprouter.lua b/luci-app-openmptcprouter/luasrc/controller/openmptcprouter.lua index 67e907243..be6f36f31 100755 --- a/luci-app-openmptcprouter/luasrc/controller/openmptcprouter.lua +++ b/luci-app-openmptcprouter/luasrc/controller/openmptcprouter.lua @@ -167,7 +167,7 @@ function wizard_add() ucic:set("network","wan" .. i .. "_dev","mode","vepa") ucic:set("network","wan" .. i .. "_dev","ifname",defif) ucic:set("network","wan" .. i .. "_dev","name","wan" .. i) - ucic:set("network","wan" .. i .. "_dev","txqueuelen","20") + ucic:set("network","wan" .. i .. "_dev","txqueuelen","1000") end ucic:set("network","wan" .. i,"ip4table","wan") if multipath_master then @@ -388,9 +388,11 @@ function wizard_add() if ip6addr ~= "" then ucic:set("network",intf,"ip6addr",ip6addr:gsub("%s+", "")) ucic:set("network",intf,"ip6gw",ip6gw:gsub("%s+", "")) + ucic:set("network",intf,"ipv6","1") else ucic:set("network",intf,"ip6addr","") ucic:set("network",intf,"ip6gw","") + ucic:set("network",intf,"ipv6","0") end if proto == "dhcpv6" then diff --git a/luci-app-openmptcprouter/root/bin/omr-modemmanager b/luci-app-openmptcprouter/root/bin/omr-modemmanager index 57115c2e0..d53865837 100755 --- a/luci-app-openmptcprouter/root/bin/omr-modemmanager +++ b/luci-app-openmptcprouter/root/bin/omr-modemmanager @@ -7,7 +7,9 @@ timeout 1 mmcli -L | while read MODEM; do MODEM_INFO="$(timeout 1 mmcli -m $MODEM_ID --output-keyvalue)" if [ -n "$MODEM_INFO" ] && [ "$(echo "$MODEM_INFO" | grep 'modem.generic.device ' | awk -F": " '{print $2}')" = "$MODEM_INTF" ]; then PERCENT=$(echo "$MODEM_INFO" | grep 'modem.generic.signal-quality.value ' | awk -F": " '{print $2}') + [ -z "$PERCENT" ] && PERCENT=$(awk -v n="$(mmcli -m $MODEM_ID --command 'AT+CSQ')" 'BEGIN{ print int(n*10) }') OPERATOR=$(echo "$MODEM_INFO" | grep 'modem.3gpp.operator-name ' | awk -F": " '{print $2}') + [ -z "$OPERATOR" ] && OPERATOR=$(mmcli -m $MODEM_ID --command 'AT+QSPN' | awk -F[\"\"] '{ print $2 }') NUMBER=$(echo "$MODEM_INFO" | grep 'modem.generic.own-numbders.value[1]' | awk -F": " '{print $2}') STATE=$(echo "$MODEM_INFO" | grep 'modem.generic.state ' | awk -F": " '{print $2}') TYPE=$(echo "$MODEM_INFO" | grep 'modem.generic.access-technologies.value\[1\]' | awk -F": " '{print $2}') diff --git a/luci-app-openmptcprouter/root/etc/init.d/openmptcprouter b/luci-app-openmptcprouter/root/etc/init.d/openmptcprouter index 189f2669b..e74aced38 100755 --- a/luci-app-openmptcprouter/root/etc/init.d/openmptcprouter +++ b/luci-app-openmptcprouter/root/etc/init.d/openmptcprouter @@ -177,7 +177,7 @@ start_service() { else rm -f /dev/sfe_ipv6 fi - else + elif [ -d "/sys/module/fast_classifier" ]; then rmmod fast_classifier 2>&1 >/dev/null fi @@ -188,9 +188,9 @@ start_service() { set firewall.zone_vpn.auto_helper='0' commit firewall EOF - rmmod nf_nat_sip 2>&1 >/dev/null + [ -n "$(lsmod | grep nf_nat_sip)" ] && rmmod nf_nat_sip 2>&1 >/dev/null sleep 2 - rmmod nf_conntrack_sip 2>&1 >/dev/null + [ -n "$(lsmod | grep nf_conntrack_sip)" ] && rmmod nf_conntrack_sip 2>&1 >/dev/null else uci -q batch <<-EOF >/dev/null set firewall.zone_lan.auto_helper='1' diff --git a/luci-app-openmptcprouter/root/etc/uci-defaults/openmptcprouter b/luci-app-openmptcprouter/root/etc/uci-defaults/openmptcprouter index f8a0dc21c..647c23303 100755 --- a/luci-app-openmptcprouter/root/etc/uci-defaults/openmptcprouter +++ b/luci-app-openmptcprouter/root/etc/uci-defaults/openmptcprouter @@ -135,11 +135,11 @@ _set_omr_ip() { server=$1 serverip="$(uci -q get openmptcprouter.${server}.ip)" if [ -n "$serverip" ]; then - uci -q batch <<-EOF >/dev/null - delete openmptcprouter.${server}.ip - add_list openmptcprouter.${server}.ip="${serverip}" - commit openmptcprouter - EOF + uci -q delete openmptcprouter.${server}.ip + for ip in ${serverip}; do + uci -q add_list openmptcprouter.${server}.ip="${ip}" + done + uci -q commit openmptcprouter fi } diff --git a/luci-app-status/luasrc/controller/status.lua b/luci-app-status/luasrc/controller/status.lua index 8cf891974..1bbb31e17 100755 --- a/luci-app-status/luasrc/controller/status.lua +++ b/luci-app-status/luasrc/controller/status.lua @@ -9,9 +9,9 @@ module("luci.controller.status", package.seeall) function index() entry({"admin", "system", "status"}, alias("admin", "system", "status", "server"), _("Settings"), 1) - entry({"admin", "system", "status","server"}, template("status/server"),_('Settings'),1).leaf = true + -- entry({"admin", "system", "status","server"}, template("status/server"),_('Settings'),1).leaf = true entry({"admin", "system", "status","status"}, template("status/wanstatus"),_('Status'),2).leaf = true - entry({"admin", "system", "status","server_add"}, post("server_add")) + -- entry({"admin", "system", "status","server_add"}, post("server_add")) entry({"admin", "system", "status", "interfaces_status"}, call("interfaces_status")).leaf = true entry({"admin", "system", "status", "multipath_bandwidth"}, call("multipath_bandwidth")).leaf = true entry({"admin", "system", "status", "interface_bandwidth"}, call("interface_bandwidth")).leaf = true diff --git a/luci-app-status/luasrc/controller/wan.lua b/luci-app-status/luasrc/controller/wan.lua new file mode 100755 index 000000000..b1b6eddae --- /dev/null +++ b/luci-app-status/luasrc/controller/wan.lua @@ -0,0 +1,563 @@ +local math = require "math" +local sys = require "luci.sys" +local json = require("luci.json") +local fs = require("nixio.fs") +local net = require "luci.model.network".init() +local ucic = luci.model.uci.cursor() +local ipc = require "luci.ip" +module("luci.controller.wan", package.seeall) + +function index() + local ucic = luci.model.uci.cursor() + menuentry = "status" + entry({"admin", "system", menuentry:lower()}, alias("admin", "system", menuentry:lower(), "wizard"), _(menuentry), 1) + entry({"admin", "system", menuentry:lower(), "wizard"}, template("status/wan"), _("Settings Wizard"), 1) + entry({"admin", "system", menuentry:lower(), "wizard_add"}, post("wizard_add")) +-- entry({"admin", "system", menuentry:lower(), "status"}, template("status/wanstatus"), _("Status"), 2).leaf = true +-- entry({"admin", "system", menuentry:lower(), "interfaces_status"}, call("interfaces_status")).leaf = true +-- entry({"admin", "system", menuentry:lower(), "settings"}, template("openmptcprouter/settings"), _("Advanced Settings"), 3).leaf = true +-- entry({"admin", "system", menuentry:lower(), "settings_add"}, post("settings_add")) +-- entry({"admin", "system", menuentry:lower(), "update_vps"}, post("update_vps")) +-- entry({"admin", "system", menuentry:lower(), "backup"}, template("openmptcprouter/backup"), _("Backup on server"), 3).leaf = true +-- entry({"admin", "system", menuentry:lower(), "backupgr"}, post("backupgr")) +-- entry({"admin", "system", menuentry:lower(), "debug"}, template("openmptcprouter/debug"), _("Show all settings"), 5).leaf = true +end + +function interface_from_device(dev) + for _, iface in ipairs(net:get_networks()) do + local ifacen = iface:name() + local ifacename = "" + ifacename = ucic:get("network",ifacen,"device") + if ifacename == "" then + ifacename = ucic:get("network",ifacen,"ifname") + end + if ifacename == dev then + return ifacen + end + end + return "" +end + +function uci_device_from_interface(intf) + intfname = ucic:get("network",intf,"device") + deviceuci = "" + ucic:foreach("network", "device", function(s) + if intfname == ucic:get("network",s[".name"],"name") then + deviceuci = s[".name"] + end + end) + return deviceuci +end + +function wizard_add() + local gostatus = true + + -- Force WAN zone firewall members to be a list + local fwwan = sys.exec("uci -q get firewall.zone_wan.network") + luci.sys.call("uci -q delete firewall.zone_wan.network") + for interface in fwwan:gmatch("%S+") do + luci.sys.call("uci -q add_list firewall.zone_wan.network=" .. interface) + end + ucic:save("firewall") + + -- Add new interface + local add_interface = luci.http.formvalue("add_interface") or "" + local add_interface_ifname = luci.http.formvalue("add_interface_ifname") or "" + if add_interface ~= "" then + local i = 1 + local multipath_master = false + ucic:foreach("network", "interface", function(s) + local sectionname = s[".name"] + if sectionname:match("^wan(%d+)$") then + if i <= tonumber(string.match(sectionname, '%d+')) then + i = tonumber(string.match(sectionname, '%d+')) + 1 + end + end + if ucic:get("network",sectionname,"multipath") == "master" then + multipath_master = true + end + end) + local defif = "eth0" + if add_interface_ifname == "" then + local defif1 = ucic:get("network","wan1_dev","device") or "" + if defif1 == "" then + defif1 = ucic:get("network","wan1_dev","ifname") or "" + end + if defif1 ~= "" then + defif = defif1 + end + else + defif = add_interface_ifname + end + + local ointf = interface_from_device(defif) or "" + local wanif = defif + if ointf ~= "" then + if ucic:get("network",ointf,"type") == "" then + ucic:set("network",ointf,"type","macvlan") + ucic:set("network",ointf,"device",ointf) + ucic:set("network",ointf .. "_dev","device") + ucic:set("network",ointf .. "_dev","type","macvlan") + ucic:set("network",ointf .. "_dev","mode","vepa") + ucic:set("network",ointf .. "_dev","ifname",defif) + ucic:set("network",ointf .. "_dev","name",ointf) + end + wanif = "wan" .. i + end + + ucic:set("network","wan" .. i,"interface") + ucic:set("network","wan" .. i,"device",defif) + ucic:set("network","wan" .. i,"proto","static") + ucic:set("openmptcprouter","wan" .. i,"interface") + if ointf ~= "" then + ucic:set("network","wan" .. i,"type","macvlan") + ucic:set("network","wan" .. i,"device","wan" .. i) + ucic:set("network","wan" .. i,"masterintf",defif) + ucic:set("network","wan" .. i .. "_dev","device") + ucic:set("network","wan" .. i .. "_dev","type","macvlan") + ucic:set("network","wan" .. i .. "_dev","mode","vepa") + ucic:set("network","wan" .. i .. "_dev","ifname",defif) + ucic:set("network","wan" .. i .. "_dev","name","wan" .. i) + ucic:set("network","wan" .. i .. "_dev","txqueuelen","20") + end + ucic:set("network","wan" .. i,"ip4table","wan") + if multipath_master then + ucic:set("network","wan" .. i,"multipath","on") + ucic:set("openmptcprouter","wan" .. i,"multipath","on") + else + ucic:set("network","wan" .. i,"multipath","master") + ucic:set("openmptcprouter","wan" .. i,"multipath","master") + end + ucic:set("network","wan" .. i,"defaultroute","0") + ucic:reorder("network","wan" .. i, i + 2) + ucic:save("network") + ucic:commit("network") + ucic:save("openmptcprouter") + ucic:commit("openmptcprouter") + + ucic:set("qos","wan" .. i,"interface") + ucic:set("qos","wan" .. i,"classgroup","Default") + ucic:set("qos","wan" .. i,"enabled","0") + ucic:set("qos","wan" .. i,"upload","4000") + ucic:set("qos","wan" .. i,"download","100000") + ucic:save("qos") + ucic:commit("qos") + + ucic:set("sqm","wan" .. i,"queue") + if ointf ~= "" then + ucic:set("sqm","wan" .. i,"interface","wan" .. i) + else + ucic:set("sqm","wan" .. i,"interface",defif) + end + ucic:set("sqm","wan" .. i,"qdisc","fq_codel") + ucic:set("sqm","wan" .. i,"script","simple.qos") + ucic:set("sqm","wan" .. i,"qdisc_advanced","0") + ucic:set("sqm","wan" .. i,"linklayer","none") + ucic:set("sqm","wan" .. i,"enabled","1") + ucic:set("sqm","wan" .. i,"debug_logging","0") + ucic:set("sqm","wan" .. i,"verbosity","5") + ucic:set("sqm","wan" .. i,"download","0") + ucic:set("sqm","wan" .. i,"upload","0") + ucic:set("sqm","wan" .. i,"iqdisc_opts","autorate-ingress dual-dsthost") + ucic:set("sqm","wan" .. i,"eqdisc_opts","dual-srchost") + ucic:save("sqm") + ucic:commit("sqm") + + luci.sys.call("uci -q add_list vnstat.@vnstat[-1].interface=" .. wanif) + luci.sys.call("uci -q commit vnstat") + + -- Dirty way to add new interface to firewall... + luci.sys.call("uci -q add_list firewall.zone_wan.network=wan" .. i) + luci.sys.call("uci -q commit firewall") + + luci.sys.call("/etc/init.d/macvlan restart >/dev/null 2>/dev/null") + luci.sys.call("/etc/init.d/vnstat restart >/dev/null 2>/dev/null") + gostatus = false + end + + -- Remove existing interface + local delete_intf = luci.http.formvaluetable("delete") or "" + if delete_intf ~= "" then + for intf, _ in pairs(delete_intf) do + local defif = ucic:get("network",intf,"ifname") or "" + if defif == "" then + defif = ucic:get("network",intf,"ifname") + end + ucic:delete("network",intf) + if ucic:get("network",intf .. "_dev") ~= "" then + ucic:delete("network",intf .. "_dev") + end + ucic:save("network") + ucic:commit("network") + ucic:delete("sqm",intf) + ucic:save("sqm") + ucic:commit("sqm") + ucic:delete("qos",intf) + ucic:save("qos") + ucic:commit("qos") + ucic:delete("openmptcprouter",intf) + ucic:save("openmptcprouter") + ucic:commit("openmptcprouter") + if defif ~= nil and defif ~= "" then + luci.sys.call("uci -q del_list vnstat.@vnstat[-1].interface=" .. defif) + end + luci.sys.call("uci -q commit vnstat") + luci.sys.call("uci -q del_list firewall.zone_wan.network=" .. intf) + luci.sys.call("uci -q commit firewall") + gostatus = false + end + end + -- Set wireless settings + local wifi_interfaces = luci.http.formvaluetable("wifi") + for wifi_intf, _ in pairs(wifi_interfaces) do + local channel = luci.http.formvalue("cbid.wifi.%s.channel" % wifi_intf) or "" + local name = luci.http.formvalue("cbid.wifi.%s.name" % wifi_intf) or "" + local key = luci.http.formvalue("cbid.wifi.%s.key" % wifi_intf) or "" + ucic:set("wireless",wifi_intf,"channel",channel) + ucic:set("wireless","default_" .. wifi_intf,"ssid",name) + ucic:set("wireless","default_" .. wifi_intf,"key",key) + end + ucic:save("wireless") + ucic:commit("wireless") + + -- Set interfaces settings + local interfaces = luci.http.formvaluetable("intf") + for intf, _ in pairs(interfaces) do + local label = luci.http.formvalue("cbid.network.%s.label" % intf) or "" + local proto = luci.http.formvalue("cbid.network.%s.proto" % intf) or "static" + local typeintf = luci.http.formvalue("cbid.network.%s.type" % intf) or "" + local masterintf = luci.http.formvalue("cbid.network.%s.masterintf" % intf) or "" + local ifname = luci.http.formvalue("cbid.network.%s.intf" % intf) or "" + local vlan = luci.http.formvalue("cbid.network.%s.vlan" % intf) or "" + local device_ncm = luci.http.formvalue("cbid.network.%s.device.ncm" % intf) or "" + local device_qmi = luci.http.formvalue("cbid.network.%s.device.qmi" % intf) or "" + local device_modemmanager = luci.http.formvalue("cbid.network.%s.device.modemmanager" % intf) or "" + local ipaddr = luci.http.formvalue("cbid.network.%s.ipaddr" % intf) or "" + local ip6addr = luci.http.formvalue("cbid.network.%s.ip6addr" % intf) or "" + local netmask = luci.http.formvalue("cbid.network.%s.netmask" % intf) or "" + local gateway = luci.http.formvalue("cbid.network.%s.gateway" % intf) or "" + local ip6gw = luci.http.formvalue("cbid.network.%s.ip6gw" % intf) or "" + local ipv6 = luci.http.formvalue("cbid.network.%s.ipv6" % intf) or "0" + local apn = luci.http.formvalue("cbid.network.%s.apn" % intf) or "" + local pincode = luci.http.formvalue("cbid.network.%s.pincode" % intf) or "" + local delay = luci.http.formvalue("cbid.network.%s.delay" % intf) or "" + local username = luci.http.formvalue("cbid.network.%s.username" % intf) or "" + local password = luci.http.formvalue("cbid.network.%s.password" % intf) or "" + local auth = luci.http.formvalue("cbid.network.%s.auth" % intf) or "" + local mode = luci.http.formvalue("cbid.network.%s.mode" % intf) or "" + local sqmenabled = luci.http.formvalue("cbid.sqm.%s.enabled" % intf) or "0" + local sqmautorate = luci.http.formvalue("cbid.sqm.%s.autorate" % intf) or "0" + local qosenabled = luci.http.formvalue("cbid.qos.%s.enabled" % intf) or "0" + local multipath = luci.http.formvalue("cbid.network.%s.multipath" % intf) or "on" + local lan = luci.http.formvalue("cbid.network.%s.lan" % intf) or "0" + local ttl = luci.http.formvalue("cbid.network.%s.ttl" % intf) or "" + if typeintf ~= "" then + if typeintf == "normal" then + typeintf = "" + end + ucic:set("network",intf,"type",typeintf) + end + if vlan ~= "" then + ifname = ifname .. '.' .. vlan + end + if typeintf == "macvlan" and masterintf ~= "" then + ucic:set("network",intf,"type","macvlan") + ucic:set("network",intf .. "_dev","device") + ucic:set("network",intf .. "_dev","type","macvlan") + ucic:set("network",intf .. "_dev","ifname",masterintf) + ucic:set("network",intf .. "_dev","mode","vepa") + ucic:set("network",intf .. "_dev","name",intf) + ucic:set("network",intf,"device",intf) + ucic:set("network",intf,"masterintf",masterintf) + elseif typeintf == "" and ifname ~= "" and (proto == "static" or proto == "dhcp" or proto == "dhcpv6") then + ucic:set("network",intf,"device",ifname) + if uci_device_from_interface(intf) == "" then + ucic:set("network",intf .. "_dev","device") + ucic:set("network",intf .. "_dev","name",ifname) + end + elseif typeintf == "" and device ~= "" and proto == "ncm" then + ucic:set("network",intf,"device",device_ncm) + if uci_device_from_interface(intf) == "" then + ucic:set("network",intf .. "_dev","device") + ucic:set("network",intf .. "_dev","name",device_ncm) + end + elseif typeintf == "" and device ~= "" and proto == "qmi" then + ucic:set("network",intf,"device",device_qmi) + if uci_device_from_interface(intf) == "" then + ucic:set("network",intf .. "_dev","device") + ucic:set("network",intf .. "_dev","name",device_qmi) + end + elseif typeintf == "" and device ~= "" and proto == "modemmanager" then + ucic:set("network",intf,"device",device_manager) + if uci_device_from_interface(intf) == "" then + ucic:set("network",intf .. "_dev","device") + ucic:set("network",intf .. "_dev","name",device_manager) + end + elseif typeintf == "" and ifname ~= "" and proto == "static" then + ucic:set("network",intf,"device",ifname) + if uci_device_from_interface(intf) == "" then + ucic:set("network",intf .. "_dev","device") + ucic:set("network",intf .. "_dev","name",ifname) + end + end + if typeintf ~= "macvlan" then + if ucic:get("network",intf .. "_dev","type") == "macvlan" then + ucic:delete("network",intf .. "_dev","type") + ucic:delete("network",intf .. "_dev","mode") + ucic:delete("network",intf .. "_dev","ifname") + ucic:delete("network",intf .. "_dev","macaddr") + end + ucic:delete("network",intf,"masterintf") + end + if proto == "pppoe" then + ucic:set("network",intf,"pppd_options","persist maxfail 0") + end + if proto ~= "other" then + ucic:set("network",intf,"proto",proto) + end + + uci_device = uci_device_from_interface(intf) + if uci_device == "" then + uci_device = intf .. "_dev" + end + ucic:set("network",uci_device,"ttl",ttl) + + --ucic:set("network",intf,"apn",apn) + --ucic:set("network",intf,"pincode",pincode) + --ucic:set("network",intf,"delay",delay) + --ucic:set("network",intf,"username",username) + --ucic:set("network",intf,"password",password) + --ucic:set("network",intf,"auth",auth) + --ucic:set("network",intf,"mode",mode) + --ucic:set("network",intf,"label",label) + --ucic:set("network",intf,"ipv6",ipv6) + if lan == "1" then + ucic:set("network",intf,"multipath","off") + else + ucic:set("network",intf,"multipath",multipath) + ucic:set("openmptcprouter",intf,"multipath",multipath) + end + ucic:set("network",intf,"defaultroute",0) + ucic:set("network",intf,"peerdns",0) + if ipaddr ~= "" then + ucic:set("network",intf,"ipaddr",ipaddr:gsub("%s+", "")) + ucic:set("network",intf,"netmask",netmask:gsub("%s+", "")) + ucic:set("network",intf,"gateway",gateway:gsub("%s+", "")) + else + ucic:set("network",intf,"ipaddr","") + ucic:set("network",intf,"netmask","") + ucic:set("network",intf,"gateway","") + end + if ip6addr ~= "" then + ucic:set("network",intf,"ip6addr",ip6addr:gsub("%s+", "")) + ucic:set("network",intf,"ip6gw",ip6gw:gsub("%s+", "")) + else + ucic:set("network",intf,"ip6addr","") + ucic:set("network",intf,"ip6gw","") + end + + if proto == "dhcpv6" then + ucic:set("network",intf,"reqaddress","try") + ucic:set("network",intf,"reqprefix","no") + ucic:set("network",intf,"iface_map","0") + ucic:set("network",intf,"iface_dslite","0") + ucic:set("network",intf,"iface_464xlate","0") + ucic:set("network",intf,"ipv6","1") + end + + ucic:delete("openmptcprouter",intf,"lc") + ucic:save("openmptcprouter") + + --local multipathvpn = luci.http.formvalue("multipathvpn.%s.enabled" % intf) or "0" + --ucic:set("openmptcprouter",intf,"multipathvpn",multipathvpn) + --ucic:save("openmptcprouter") + + end + -- Disable multipath on LAN, VPN and loopback + ucic:set("network","loopback","multipath","off") + ucic:set("network","lan","multipath","off") + ucic:set("network","omr6in4","multipath","off") + ucic:set("network","omrvpn","multipath","off") + + ucic:save("network") + ucic:commit("network") + + ucic:save("network") + ucic:commit("network") + + ucic:save("openmptcprouter") + ucic:commit("openmptcprouter") + + -- Restart all + menuentry = "status" + if gostatus == true then + --luci.sys.call("/etc/init.d/macvlan restart >/dev/null 2>/dev/null") + luci.sys.call("(env -i /bin/ubus call network reload) >/dev/null 2>/dev/null") + luci.sys.call("ip addr flush dev tun0 >/dev/null 2>/dev/null") + luci.sys.call("/etc/init.d/omr-tracker stop >/dev/null 2>/dev/null") + luci.sys.call("/etc/init.d/mptcp restart >/dev/null 2>/dev/null") + --if openmptcprouter_vps_key ~= "" then + -- luci.sys.call("/etc/init.d/openmptcprouter-vps restart >/dev/null 2>/dev/null") + -- luci.sys.call("sleep 2") + --end + luci.sys.call("/etc/init.d/shadowsocks-libev restart >/dev/null 2>/dev/null") + luci.sys.call("/etc/init.d/glorytun restart >/dev/null 2>/dev/null") + luci.sys.call("/etc/init.d/glorytun-udp restart >/dev/null 2>/dev/null") + --luci.sys.call("/etc/init.d/mlvpn restart >/dev/null 2>/dev/null") + --luci.sys.call("/etc/init.d/ubond restart >/dev/null 2>/dev/null") + --luci.sys.call("/etc/init.d/mptcpovervpn restart >/dev/null 2>/dev/null") + --luci.sys.call("/etc/init.d/openvpn restart >/dev/null 2>/dev/null") + --luci.sys.call("/etc/init.d/openvpnbonding restart >/dev/null 2>/dev/null") + --luci.sys.call("/etc/init.d/dsvpn restart >/dev/null 2>/dev/null") + luci.sys.call("/etc/init.d/omr-tracker start >/dev/null 2>/dev/null") + --luci.sys.call("/etc/init.d/omr-6in4 restart >/dev/null 2>/dev/null") + luci.sys.call("/etc/init.d/vnstat restart >/dev/null 2>/dev/null") + luci.sys.call("/etc/init.d/v2ray restart >/dev/null 2>/dev/null") + luci.sys.call("/etc/init.d/sqm-autorate restart >/dev/null 2>/dev/null") + luci.sys.call("/etc/init.d/sysntpd restart >/dev/null 2>/dev/null") + luci.http.redirect(luci.dispatcher.build_url("admin/system/" .. menuentry:lower() .. "/status")) + else + luci.http.redirect(luci.dispatcher.build_url("admin/system/" .. menuentry:lower() .. "/wizard")) + end + return +end + +function get_device(interface) + local dump = require("luci.util").ubus("network.interface.%s" % interface, "status", {}) + if dump ~= nil then + if dump['l3_device'] ~= nil then + return dump['l3_device'] + elseif dump['device'] ~= nil then + return dump['device'] + else + return "" + end + else + return "" + end +end + +-- This function come from modules/luci-bbase/luasrc/tools/status.lua from old OpenWrt +-- Copyright 2011 Jo-Philipp Wich +-- Licensed to the public under the Apache License 2.0. +local function dhcp_leases_common(family) + local rv = { } + local nfs = require "nixio.fs" + local sys = require "luci.sys" + local leasefile = "/tmp/dhcp.leases" + + ucic:foreach("dhcp", "dnsmasq", + function(s) + if s.leasefile and nfs.access(s.leasefile) then + leasefile = s.leasefile + return false + end + end) + + local fd = io.open(leasefile, "r") + if fd then + while true do + local ln = fd:read("*l") + if not ln then + break + else + local ts, mac, ip, name, duid = ln:match("^(%d+) (%S+) (%S+) (%S+) (%S+)") + local expire = tonumber(ts) or 0 + if ts and mac and ip and name and duid then + if family == 4 and not ip:match(":") then + rv[#rv+1] = { + expires = (expire ~= 0) and os.difftime(expire, os.time()), + macaddr = ipc.checkmac(mac) or "00:00:00:00:00:00", + ipaddr = ip, + hostname = (name ~= "*") and name + } + elseif family == 6 and ip:match(":") then + rv[#rv+1] = { + expires = (expire ~= 0) and os.difftime(expire, os.time()), + ip6addr = ip, + duid = (duid ~= "*") and duid, + hostname = (name ~= "*") and name + } + end + end + end + end + fd:close() + end + + local lease6file = "/tmp/hosts/odhcpd" + ucic:foreach("dhcp", "odhcpd", + function(t) + if t.leasefile and nfs.access(t.leasefile) then + lease6file = t.leasefile + return false + end + end) + local fd = io.open(lease6file, "r") + if fd then + while true do + local ln = fd:read("*l") + if not ln then + break + else + local iface, duid, iaid, name, ts, id, length, ip = ln:match("^# (%S+) (%S+) (%S+) (%S+) (-?%d+) (%S+) (%S+) (.*)") + local expire = tonumber(ts) or 0 + if ip and iaid ~= "ipv4" and family == 6 then + rv[#rv+1] = { + expires = (expire >= 0) and os.difftime(expire, os.time()), + duid = duid, + ip6addr = ip, + hostname = (name ~= "-") and name + } + elseif ip and iaid == "ipv4" and family == 4 then + rv[#rv+1] = { + expires = (expire >= 0) and os.difftime(expire, os.time()), + macaddr = sys.net.duid_to_mac(duid) or "00:00:00:00:00:00", + ipaddr = ip, + hostname = (name ~= "-") and name + } + end + end + end + fd:close() + end + + if family == 6 then + local _, lease + local hosts = sys.net.host_hints() + for _, lease in ipairs(rv) do + local mac = sys.net.duid_to_mac(lease.duid) + local host = mac and hosts[mac] + if host then + if not lease.name then + lease.host_hint = host.name or host.ipv4 or host.ipv6 + elseif host.name and lease.hostname ~= host.name then + lease.host_hint = host.name + end + end + end + end + + return rv +end + +function interfaces_status() + local ut = require "luci.util" + --local mArray = ut.ubus("openmptcprouter", "status", {}) or {_=0} + local mArray = luci.json.decode(ut.trim(sys.exec("/bin/ubus -t 600 -S call openmptcprouter status 2>/dev/null"))) + + if mArray ~= nil and mArray.openmptcprouter ~= nil then + mArray.openmptcprouter["remote_addr"] = luci.http.getenv("REMOTE_ADDR") or "" + mArray.openmptcprouter["remote_from_lease"] = false + local leases=dhcp_leases_common(4) + for _, value in pairs(leases) do + if value["ipaddr"] == mArray.openmptcprouter["remote_addr"] then + mArray.openmptcprouter["remote_from_lease"] = true + mArray.openmptcprouter["remote_hostname"] = value["hostname"] + end + end + end + + luci.http.prepare_content("application/json") + luci.http.write_json(mArray) +end diff --git a/luci-app-status/luasrc/view/status/wan.htm b/luci-app-status/luasrc/view/status/wan.htm new file mode 100755 index 000000000..e09a8ecb2 --- /dev/null +++ b/luci-app-status/luasrc/view/status/wan.htm @@ -0,0 +1,648 @@ +<%+header%> + +<% + local uci = require("luci.model.uci").cursor() + local net = require "luci.model.network".init() + local fs = require "nixio.fs" + local sys = require "luci.sys" + local ut = require "luci.util" + local ifaces = sys.net:devices() + local ifttyu = nixio.fs.glob("/dev/ttyUSB*") + local ifttyc = nixio.fs.glob("/dev/cdc-wdm*") + local sqm = luci.sys.exec("opkg list-installed | grep -q luci-app-sqm && echo -n '1' || echo -n '0'") + local qos = luci.sys.exec("opkg list-installed | grep -q luci-app-qos && echo -n '1' || echo -n '0'") + menuentry = "status" + function device_notvirtual(dev) + if dev:match("^eth.*") or dev:match("^wwan.*") or dev:match("^tun.*") or dev:match("/") then + return true + end + networks = net:get_networks() + for _, iface in ipairs(networks) do + local ifacen = iface:name() + local ifacename = uci:get("network",ifacen,"device") + local ifacetype = uci:get("network",ifacen,"type") or "" + local ifaceproto = uci:get("network",ifacen,"proto") or "" + --if ifacename == dev and (ifacetype == "macvlan" or ifacetype == "bridge" or ifaceproto == "6in4") then + if ifacename == dev and (ifacetype == "macvlan" or ifaceproto == "6in4") then + return false + end + end + return true + end + function splitstring(inputstr, sep) + if inputstr == nil then + return "" + end + if sep == nil then + sep = "%s" + end + local t={} + for str in string.gmatch(inputstr, "([^"..sep.."]+)") do + table.insert(t, str) + end + return t + end + + alltty = {} + iftty = luci.sys.exec("timeout 1 /usr/bin/mmcli -L") + for listtty in iftty:gmatch("([^\r\n]*)[\r\n]") do + modemid = luci.util.trim(luci.sys.exec("echo '" .. listtty .. "' | awk -F' ' '{print $1}' | awk -F/ '{print $6}'")) + if modemid ~= '' then + modeminfo = luci.sys.exec("timeout 1 /usr/bin/mmcli -m " .. modemid .. " --output-keyvalue") + tty = luci.util.trim(luci.sys.exec("echo '" .. modeminfo .. "' | grep 'modem.generic.device ' | awk -F': ' '{print $2}'")) + table.insert(alltty, tty) + end + end + + +%> + + + +<% if stderr and #stderr > 0 then %>
<%=pcdata(stderr)%>
<% end %> +
+
+

<%:Wizard%>

+
+ <%:Interfaces settings%> + +<% + for _, iface in ipairs(net:get_networks()) do + local ifname = iface:name() + local firewall_wan = luci.util.trim(luci.sys.exec("uci -q get firewall.zone_wan.network | tr ' ' '\n' | grep \'^" .. ifname .. "$\'")) + if firewall_wan ~= "" and ifname:match("^wan.*") then + +-- local multipath = uci:get("network",ifname,"multipath") +-- local multipathvpn = uci:get("openmptcprouter",ifname,"multipathvpn") +-- local vpn = uci:get("openmptcprouter",ifname,"vpn") +-- if (multipath ~= nil and multipath ~= "off" and vpn ~= "1") or multipathvpn == "1" then +%> +
+ +
+

<%=ifname%>

+
+ +
+ +
+ "> +
+
+ <%:Label for the interface%> +
+
+
+ + + +
+ +
+ <% findproto = 0 %> + +
+
+ <%:You can use DHCP if you have multiple real ethernet ports. Select other if you want to use another protocol available in Network Interfaces page.%> +
+
+
+ + +
+ +
+ " data-type="ip4addr"> +
+
+ <%:Set an IP in the same network as the modem%> +
+
+
+
+ +
+ " data-type="ip4addr"> +
+
+
+ +
+ " data-type="ip4addr"> +
+
+ <%:Set here IP of the modem%> +
+
+
+ <% + ipv6list = uci:get_list("network",ifname,"ip6addr") + for key, value in pairs(ipv6list) do + %> +
+ +
+ +
+
+ <%:Set an IP in the same network as the modem%> +
+
+
+ <% + end + if table.getn(ipv6list) ~= 0 then + %> +
+ +
+ " data-type="ip6addr" data-optional="true"> +
+
+ <%:Set here IP of the modem%> +
+
+
+ <% + else + %> +
+ +
+ +
+
+ <%:Set an IP in the same network as the modem%> +
+
+
+
+ +
+ " data-type="ip6addr"> +
+
+ <%:Set here IP of the modem%> +
+
+
+ <% + end + %> + + +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ "> +
+
+
+ +
+ "> +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ "> +
+
+
+ +
+ "> +
+
+
+ +
+ "> +
+
+ <% + if uci:get("openmptcprouter",ifname,"multipathvpn") == "1" then + %> +
+ +
+ +
+
+ <%:Only one interface must be set as "Master", this should be the most stable interface.%> +
+
+
+ <% else %> +
+ +
+ +
+
+ <% + end + %> + + + <% + local download = "0" + local upload = "0" + download = uci:get("network",ifname,"downloadspeed") or "0" + upload = uci:get("network",ifname,"uploadspeed") or "0" + cake = uci:get("sqm",ifname,"qdisc") or "cake" + --if download == "0" or upload == "0" then + -- if nixio.fs.access("/etc/init.d/sqm") then + -- download = uci:get("sqm",ifname,"download") + -- upload = uci:get("sqm",ifname,"upload") + -- else + -- download = uci:get("qos",ifname,"download") + -- upload = uci:get("qos",ifname,"upload") + -- end + --end + %> + + <% + if sqm == "1" then + %> + + + <% + end + %> + <% + if qos == "1" then + %> + + <% + end + %> + + +
+<% + end + end +%> + + + +
+
+<% + if uci:get('wireless','default_radio0') == 'wifi-iface' and uci:get('wireless','default_radio0','disabled') ~= '1' then +%> +
+

<%:Wifi%>

+ <% + uci:foreach("wireless","wifi-device", function(s) + %> +

<%=s[".name"]%> - <%=uci:get("wireless",s[".name"],"band")%>

+ " value="<%=s[".name"]%>" /> +
+
+ +
+ +
+
+
+ +
+ .name" class="cbi-input-text" value="<%=uci:get('wireless','default_' .. s[".name"],'ssid')%>"> +
+
+
+ +
+ .key" class="cbi-input-text" value="<%=uci:get('wireless','default_' .. s[".name"],'key')%>"> +
+
+ +
+ <% + end) + %> +
+<% + end +%> +
+ + + +
+
+ + +<%+footer%> diff --git a/luci-app-zerotier/Makefile b/luci-app-zerotier/Makefile index 11c26ca4f..5c070ef7c 100755 --- a/luci-app-zerotier/Makefile +++ b/luci-app-zerotier/Makefile @@ -12,7 +12,7 @@ LUCI_PKGARCH:=all PKG_NAME:=luci-app-zerotier PKG_VERSION:=1.0 -PKG_RELEASE:=20 +PKG_RELEASE:=21 include $(TOPDIR)/feeds/luci/luci.mk diff --git a/luci-app-zerotier/luasrc/controller/zerotier.lua b/luci-app-zerotier/luasrc/controller/zerotier.lua old mode 100755 new mode 100644 index 0b9d55178..f2973c65e --- a/luci-app-zerotier/luasrc/controller/zerotier.lua +++ b/luci-app-zerotier/luasrc/controller/zerotier.lua @@ -1,22 +1,24 @@ module("luci.controller.zerotier", package.seeall) function index() - if not nixio.fs.access("/etc/config/zerotier") then - return - end + if not nixio.fs.access("/etc/config/zerotier") then + return + end - entry({"admin", "vpn"}, firstchild(), "VPN", 45).dependent = false + entry({"admin", "vpn"}, firstchild(), "VPN", 45).dependent = false - entry({"admin", "vpn", "zerotier"}, alias("admin", "vpn", "zerotier", "general"), _("ZeroTier"), 99) - entry({"admin", "vpn", "zerotier", "general"}, cbi("zerotier/settings"), _("Base Setting"), 1) - entry({"admin", "vpn", "zerotier", "log"}, form("zerotier/info"), _("Interface Info"), 2) + entry({"admin", "vpn", "zerotier"}, alias("admin", "vpn", "zerotier", "general"), _("ZeroTier"), 99) - entry({"admin", "vpn", "zerotier", "status"}, call("act_status")) + entry({"admin", "vpn", "zerotier", "general"}, cbi("zerotier/settings"), _("Base Setting"), 1) + entry({"admin", "vpn", "zerotier", "log"}, form("zerotier/info"), _("Interface Info"), 2) + entry({"admin", "vpn", "zerotier", "manual"}, cbi("zerotier/manual"), _("Manual Config"), 3) + + entry({"admin", "vpn", "zerotier", "status"}, call("act_status")) end function act_status() - local e = {} - e.running = luci.sys.call("pgrep /usr/bin/zerotier-one >/dev/null") == 0 - luci.http.prepare_content("application/json") - luci.http.write_json(e) + local e = {} + e.running = luci.sys.call("pgrep /usr/bin/zerotier-one >/dev/null") == 0 + luci.http.prepare_content("application/json") + luci.http.write_json(e) end diff --git a/luci-app-zerotier/luasrc/model/cbi/zerotier/info.lua b/luci-app-zerotier/luasrc/model/cbi/zerotier/info.lua old mode 100755 new mode 100644 index e392cb0f4..bb8fc3769 --- a/luci-app-zerotier/luasrc/model/cbi/zerotier/info.lua +++ b/luci-app-zerotier/luasrc/model/cbi/zerotier/info.lua @@ -7,8 +7,8 @@ t = f:field(TextValue, "conf") t.rmempty = true t.rows = 19 function t.cfgvalue() - luci.sys.exec("for i in $(ifconfig | grep 'zt' | awk '{print $1}'); do ifconfig $i; done > /tmp/zero.info") - return fs.readfile(conffile) or "" + luci.sys.exec("for i in $(ifconfig | grep 'zt' | awk '{print $1}'); do ifconfig $i; done > /tmp/zero.info") + return fs.readfile(conffile) or "" end t.readonly = "readonly" diff --git a/luci-app-zerotier/luasrc/model/cbi/zerotier/manual.lua b/luci-app-zerotier/luasrc/model/cbi/zerotier/manual.lua new file mode 100644 index 000000000..71ae2bb37 --- /dev/null +++ b/luci-app-zerotier/luasrc/model/cbi/zerotier/manual.lua @@ -0,0 +1,26 @@ +local m, s, o +local fs = require "nixio.fs" +local jsonc = require "luci.jsonc" or nil +m = Map("zerotier") +s = m:section(NamedSection, "sample_config", "zerotier") +s.anonymous = true +s.addremove = false +o = s:option(TextValue, "manualconfig") +o.rows = 20 +o.wrap = "soft" +o.rmempty = true +o.cfgvalue = function(self, section) + return fs.readfile("/etc/config/zero/local.conf") +end +o.write = function(self, section, value) + fs.writefile("/etc/config/zero/local.conf", value:gsub("\r\n", "\n")) +end +o.validate = function(self, value) + if jsonc == nil or jsonc.parse(value) ~= nil then + return value + end + return nil +end +o.description = + 'https://www.zerotier.com/manual/
https://github.com/zerotier/ZeroTierOne/blob/dev/service/README.md' +return m diff --git a/luci-app-zerotier/luasrc/model/cbi/zerotier/settings.lua b/luci-app-zerotier/luasrc/model/cbi/zerotier/settings.lua old mode 100755 new mode 100644 index 8e6102cc5..fc70a8581 --- a/luci-app-zerotier/luasrc/model/cbi/zerotier/settings.lua +++ b/luci-app-zerotier/luasrc/model/cbi/zerotier/settings.lua @@ -2,7 +2,7 @@ a = Map("zerotier") a.title = translate("ZeroTier") a.description = translate("Zerotier is an open source, cross-platform and easy to use virtual LAN") -a:section(SimpleSection).template = "zerotier/zerotier_status" +a:section(SimpleSection).template = "zerotier/zerotier_status" t = a:section(NamedSection, "sample_config", "zerotier") t.anonymous = true @@ -21,7 +21,17 @@ e.description = translate("Allow zerotier clients access your LAN network") e.default = 0 e.rmempty = false -e = t:option(DummyValue, "opennewwindow", translate("")) +e = t:option(MultiValue, "access", translate("Zerotier Access Control")) +e.default = "lanfwzt ztfwwan ztfwlan" +e.rmempty = false +e:value("lanfwzt", translate("LAN Access Zerotier")) +e:value("wanfwzt", translate("WAN Access Zerotier")) +e:value("ztfwwan", translate("Remote Access WAN")) +e:value("ztfwlan", translate("Remote Access LAN")) +e.widget = "checkbox" + +e = t:option(DummyValue, "opennewwindow", translate( + "")) e.description = translate("Create or manage your zerotier network, and auth clients who could access") return a diff --git a/luci-app-zerotier/luasrc/view/zerotier/zerotier_status.htm b/luci-app-zerotier/luasrc/view/zerotier/zerotier_status.htm old mode 100755 new mode 100644 index 9d216c5d9..b2faf8e44 --- a/luci-app-zerotier/luasrc/view/zerotier/zerotier_status.htm +++ b/luci-app-zerotier/luasrc/view/zerotier/zerotier_status.htm @@ -1,22 +1,29 @@ - +

- <%:Collecting data...%> + + <%:Collecting data...%> +

-
+ \ No newline at end of file diff --git a/luci-app-zerotier/po/zh-cn/zerotier.po b/luci-app-zerotier/po/zh-cn/zerotier.po old mode 100755 new mode 100644 index dd3cd714a..07adfeabc --- a/luci-app-zerotier/po/zh-cn/zerotier.po +++ b/luci-app-zerotier/po/zh-cn/zerotier.po @@ -15,3 +15,21 @@ msgstr "基本设置" msgid "Interface Info" msgstr "接口信息" + +msgid "Zerotier Access Control" +msgstr "Zerotier 准入控制" + +msgid "LAN Access Zerotier" +msgstr "LAN 可接入 Zerotier" + +msgid "WAN Access Zerotier" +msgstr "WAN 可接入 Zerotier" + +msgid "Remote Access WAN" +msgstr "外部访问可接入 WAN" + +msgid "Remote Access LAN" +msgstr "外部访问可接入 LAN" + +msgid "Manual Config" +msgstr "手动设置" diff --git a/luci-app-zerotier/po/zh_Hans b/luci-app-zerotier/po/zh_Hans new file mode 100644 index 000000000..41451e4a1 --- /dev/null +++ b/luci-app-zerotier/po/zh_Hans @@ -0,0 +1 @@ +zh-cn \ No newline at end of file diff --git a/luci-app-zerotier/root/etc/init.d/zerotier b/luci-app-zerotier/root/etc/init.d/zerotier old mode 100755 new mode 100644 diff --git a/luci-app-zerotier/root/etc/uci-defaults/40_luci-zerotier b/luci-app-zerotier/root/etc/uci-defaults/40_luci-zerotier old mode 100755 new mode 100644 diff --git a/luci-app-zerotier/root/etc/zerotier.start b/luci-app-zerotier/root/etc/zerotier.start old mode 100755 new mode 100644 diff --git a/luci-app-zerotier/root/etc/zerotier.stop b/luci-app-zerotier/root/etc/zerotier.stop old mode 100755 new mode 100644 diff --git a/luci-app-zerotier/root/etc/zerotier/zerotier.log b/luci-app-zerotier/root/etc/zerotier/zerotier.log old mode 100755 new mode 100644 diff --git a/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js b/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js index 6bd6c7d8e..6c6163c7c 100755 --- a/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js +++ b/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js @@ -519,7 +519,7 @@ return view.extend({ so.value(ipv4, ipaddrs[ipv4] ? '%s (%s)'.format(ipv4, ipaddrs[ipv4]) : ipv4); }); - so = ss.option(form.value, 'gw', _('Gateway IPv4 Address')); + so = ss.option(form.Value, 'gw', _('Gateway IPv4 Address')); so.rmempty = true; so.datatype = 'or(ip4addr,"ignore")'; Object.keys(hosts).forEach(function(mac) { diff --git a/luci-theme-ezengreen/luasrc/view/themes/ezengreen/header.htm b/luci-theme-ezengreen/luasrc/view/themes/ezengreen/header.htm index e5c4c9f97..f4a63ebaf 100755 --- a/luci-theme-ezengreen/luasrc/view/themes/ezengreen/header.htm +++ b/luci-theme-ezengreen/luasrc/view/themes/ezengreen/header.htm @@ -70,7 +70,7 @@ <%- if current_omr_version ~= "" and latest_omr_version ~= "" and current_omr_version < latest_omr_version then -%>

<%=translatef("你的蚂蚁聚合openmptcprouter of china商业版 版本号 %s 最新 版本号 %s 现在可以升级",current_omr_version,latest_omr_version)%>

- +
<%- end -%>
diff --git a/modemmanager/Config.in b/modemmanager/Config.in index 283a9e10a..ebcb60dbc 100755 --- a/modemmanager/Config.in +++ b/modemmanager/Config.in @@ -1,21 +1,30 @@ menu "Configuration" -depends on PACKAGE_modemmanager + depends on PACKAGE_modemmanager - config MODEMMANAGER_WITH_MBIM - bool "Include MBIM support" - default y - help - Compile ModemManager with MBIM support +config MODEMMANAGER_WITH_MBIM + bool "Include MBIM support" + default y + help + Compile ModemManager with MBIM support - config MODEMMANAGER_WITH_QMI - bool "Include QMI support" - default y - help - Compile ModemManager with QMI support +config MODEMMANAGER_WITH_QMI + bool "Include QMI support" + default y + help + Compile ModemManager with QMI support + +config MODEMMANAGER_WITH_QRTR + bool "Include QRTR support" + default y + depends on MODEMMANAGER_WITH_QMI + select LIBQMI_WITH_QRTR_GLIB + help + Compile ModemManager with QRTR support + +config MODEMMANAGER_WITH_AT_COMMAND_VIA_DBUS + bool "Allow AT commands via DBus" + default n + help + Compile ModemManager allowing AT commands without debug flag - config MODEMMANAGER_WITH_AT_COMMAND_VIA_DBUS - bool "Allow AT commands via DBus" - default n - help - Compile ModemManager allowing AT commands without debug flag endmenu diff --git a/modemmanager/Makefile b/modemmanager/Makefile index 9d6e1174a..30e17b657 100755 --- a/modemmanager/Makefile +++ b/modemmanager/Makefile @@ -8,23 +8,25 @@ include $(TOPDIR)/rules.mk PKG_NAME:=modemmanager -PKG_VERSION:=1.18.12 +PKG_SOURCE_VERSION:=1.20.4 PKG_RELEASE:=1 -PKG_SOURCE:=ModemManager-$(PKG_VERSION).tar.xz -PKG_SOURCE_URL:=https://www.freedesktop.org/software/ModemManager -PKG_HASH:=b464e4925d955a6ca86dd08616e763b26ae46d7fd37dbe281678e34065b1e430 -PKG_BUILD_DIR:=$(BUILD_DIR)/ModemManager-$(PKG_VERSION) +PKG_SOURCE_PROTO:=git +PKG_SOURCE_URL:=https://gitlab.freedesktop.org/mobile-broadband/ModemManager.git +#PKG_MIRROR_HASH:=f138effc693456c5040ec22e17c0a8b41143c3b17b62437462995c297a9150dc -PKG_MAINTAINER:=Nicholas Smith +PKG_MAINTAINER:=Nicholas Smith PKG_LICENSE:=GPL-2.0-or-later PKG_LICENSE_FILES:=COPYING -PKG_INSTALL:=1 -PKG_BUILD_PARALLEL:=1 +PKG_BUILD_DEPENDS:=glib2/host libxslt/host include $(INCLUDE_DIR)/package.mk include $(INCLUDE_DIR)/nls.mk +include $(INCLUDE_DIR)/meson.mk + +TARGET_CFLAGS += -ffunction-sections -fdata-sections -fno-merge-all-constants -fmerge-constants +TARGET_LDFLAGS += -Wl,--gc-sections define Package/modemmanager/config source "$(SOURCE)/Config.in" @@ -41,7 +43,8 @@ define Package/modemmanager +dbus \ +ppp \ +MODEMMANAGER_WITH_MBIM:libmbim \ - +MODEMMANAGER_WITH_QMI:libqmi + +MODEMMANAGER_WITH_QMI:libqmi \ + +MODEMMANAGER_WITH_QRTR:libqrtr-glib endef define Package/modemmanager/description @@ -50,35 +53,22 @@ define Package/modemmanager/description Select Utilities/usb-modeswitch if needed. endef -CONFIGURE_ARGS += \ - --without-polkit \ - --without-udev \ - --without-systemdsystemunitdir \ - --disable-rpath \ - --disable-gtk-doc - -ifeq ($(CONFIG_MODEMMANAGER_WITH_AT_COMMAND_VIA_DBUS),y) - CONFIGURE_ARGS += --with-at-command-via-dbus -endif - -ifdef CONFIG_MODEMMANAGER_WITH_MBIM - CONFIGURE_ARGS += --with-mbim -else - CONFIGURE_ARGS += --without-mbim -endif - -ifdef CONFIG_MODEMMANAGER_WITH_QMI - CONFIGURE_ARGS += --with-qmi -else - CONFIGURE_ARGS += --without-qmi -endif - -define Build/Prepare - $(call Build/Prepare/Default) - ( cd "$(PKG_BUILD_DIR)"; \ - printf "all:\ninstall:\n" >po/Makefile.in.in; \ - ) -endef +MESON_ARGS += \ + -Dudev=false \ + -Dudevdir=/lib/udev \ + -Dtests=false \ + -Dsystemdsystemunitdir=no \ + -Dsystemd_suspend_resume=false \ + -Dsystemd_journal=false \ + -Dpolkit=no \ + -Dintrospection=false \ + -Dman=false \ + -Dbash_completion=false \ + -Db_lto=true \ + -Dmbim=$(if $(CONFIG_MODEMMANAGER_WITH_MBIM),true,false) \ + -Dqmi=$(if $(CONFIG_MODEMMANAGER_WITH_QMI),true,false) \ + -Dqrtr=$(if $(CONFIG_MODEMMANAGER_WITH_QRTR),true,false) \ + -Dat_command_via_dbus=$(if $(CONFIG_MODEMMANAGER_WITH_AT_COMMAND_VIA_DBUS),true,false) define Build/InstallDev $(INSTALL_DIR) $(1)/usr/include/ModemManager @@ -110,6 +100,9 @@ define Package/modemmanager/install $(CP) $(PKG_INSTALL_DIR)/usr/lib/ModemManager/libmm-shared-*.so* $(1)/usr/lib/ModemManager $(CP) $(PKG_INSTALL_DIR)/usr/lib/ModemManager/libmm-plugin-*.so* $(1)/usr/lib/ModemManager + $(INSTALL_DIR) $(1)/usr/lib/ModemManager/connection.d + $(INSTALL_BIN) ./files/10-report-down $(1)/usr/lib/ModemManager/connection.d + $(INSTALL_DIR) $(1)/etc/dbus-1/system.d $(INSTALL_CONF) $(PKG_INSTALL_DIR)/etc/dbus-1/system.d/org.freedesktop.ModemManager1.conf $(1)/etc/dbus-1/system.d @@ -120,6 +113,9 @@ define Package/modemmanager/install $(INSTALL_DATA) $(PKG_INSTALL_DIR)/usr/share/ModemManager/*.conf $(1)/usr/share/ModemManager $(INSTALL_DATA) ./files/modemmanager.common $(1)/usr/share/ModemManager + $(INSTALL_DIR) $(1)/usr/share/ModemManager/fcc-unlock.available.d + $(INSTALL_DATA) $(PKG_INSTALL_DIR)/usr/share/ModemManager/fcc-unlock.available.d/* $(1)/usr/share/ModemManager/fcc-unlock.available.d + $(INSTALL_DIR) $(1)/etc/init.d $(INSTALL_BIN) ./files/modemmanager.init $(1)/etc/init.d/modemmanager diff --git a/modemmanager/files/10-report-down b/modemmanager/files/10-report-down new file mode 100644 index 000000000..a3e5fb4ba --- /dev/null +++ b/modemmanager/files/10-report-down @@ -0,0 +1,35 @@ +#!/bin/sh + +# SPDX-License-Identifier: CC0-1.0 +# 2022 Aleksander Morgado +# +# Automatically report to netifd that the underlying modem +# is really disconnected +# +# require program name and at least 4 arguments +[ $# -lt 4 ] && exit 1 + +MODEM_PATH="$1" +BEARER_PATH="$2" +INTERFACE="$3" +STATE="$4" + +[ "${STATE}" = "disconnected" ] || exit 0 + +. /usr/share/ModemManager/modemmanager.common +. /lib/netifd/netifd-proto.sh +INCLUDE_ONLY=1 . /lib/netifd/proto/modemmanager.sh + +MODEM_STATUS=$(mmcli --modem="${MODEM_PATH}" --output-keyvalue) +[ -n "${MODEM_STATUS}" ] || exit 1 + +MODEM_DEVICE=$(modemmanager_get_field "${MODEM_STATUS}" "modem.generic.device") +[ -n "${MODEM_DEVICE}" ] || exit 2 + +CFG=$(mm_get_modem_config "${MODEM_DEVICE}") +[ -n "${CFG}" ] || exit 3 + +logger -t "modemmanager" "interface ${CFG} (network device ${INTERFACE}) ${STATE}" +proto_init_update $INTERFACE 0 +proto_send_update $CFG +exit 0 diff --git a/modemmanager/files/25-modemmanager-net b/modemmanager/files/25-modemmanager-net index e87231e31..ff4642019 100755 --- a/modemmanager/files/25-modemmanager-net +++ b/modemmanager/files/25-modemmanager-net @@ -12,7 +12,7 @@ mkdir -m 0755 -p "${MODEMMANAGER_RUNDIR}" # Report network interface -mm_log "${ACTION} network interface ${INTERFACE}: event processed" +mm_log "info" "${ACTION} network interface ${INTERFACE}: event processed" mm_report_event "${ACTION}" "${INTERFACE}" "net" "/sys${DEVPATH}" # Look for an associated cdc-wdm interface @@ -26,6 +26,6 @@ esac # Report cdc-wdm device, if any [ -n "${cdcwdm}" ] && { - mm_log "${ACTION} cdc interface ${cdcwdm}: custom event processed" + mm_log "info" "${ACTION} cdc interface ${cdcwdm}: custom event processed" mm_report_event "${ACTION}" "${cdcwdm}" "usbmisc" "/sys${DEVPATH}" } diff --git a/modemmanager/files/25-modemmanager-tty b/modemmanager/files/25-modemmanager-tty index c13148a33..5d1042cdd 100755 --- a/modemmanager/files/25-modemmanager-tty +++ b/modemmanager/files/25-modemmanager-tty @@ -12,5 +12,5 @@ mkdir -m 0755 -p "${MODEMMANAGER_RUNDIR}" # Report TTY -mm_log "${ACTION} serial interface ${DEVNAME}: event processed" +mm_log "info" "${ACTION} serial interface ${DEVNAME}: event processed" mm_report_event "${ACTION}" "${DEVNAME}" "tty" "/sys${DEVPATH}" diff --git a/modemmanager/files/25-modemmanager-wwan b/modemmanager/files/25-modemmanager-wwan index c4dc6b897..b36ade478 100755 --- a/modemmanager/files/25-modemmanager-wwan +++ b/modemmanager/files/25-modemmanager-wwan @@ -11,5 +11,5 @@ mkdir -m 0755 -p "${MODEMMANAGER_RUNDIR}" # Report wwan -mm_log "${ACTION} wwan control port ${DEVNAME}: event processed" +mm_log "info" "${ACTION} wwan control port ${DEVNAME}: event processed" mm_report_event "${ACTION}" "${DEVNAME}" "wwan" "/sys${DEVPATH}" diff --git a/modemmanager/files/modemmanager.proto b/modemmanager/files/modemmanager.proto old mode 100755 new mode 100644 index 0e050e7b9..e6ec9e203 --- a/modemmanager/files/modemmanager.proto +++ b/modemmanager/files/modemmanager.proto @@ -156,8 +156,8 @@ modemmanager_connected_method_ppp_ipv4() { novj \ noauth \ $authopts \ - ${username:+ user $username} \ - ${password:+ password $password} \ + ${username:+ user "$username"} \ + ${password:+ password "$password"} \ lcp-echo-failure 5 \ lcp-echo-interval 15 \ lock \ @@ -197,7 +197,6 @@ modemmanager_connected_method_dhcp_ipv4() { local interface="$1" local wwan="$2" local metric="$3" - local defaultroute="$4" proto_init_update "${wwan}" 1 proto_set_keep 1 @@ -223,7 +222,6 @@ modemmanager_connected_method_static_ipv4() { local dns1="$7" local dns2="$8" local metric="$9" - local defaultroute="$10" local mask="" @@ -244,9 +242,8 @@ modemmanager_connected_method_static_ipv4() { proto_set_keep 1 echo "adding IPv4 address ${address}, netmask ${mask}" proto_add_ipv4_address "${address}" "${mask}" - [ -n "${gateway}" ] && [ "${defaultroute}" != 0 ] && { + [ -n "${gateway}" ] && { echo "adding default IPv4 route via ${gateway}" - logger -t "modemmanager.proto" "adding default IPv4 route via ${gateway} ${address}" proto_add_ipv4_route "0.0.0.0" "0" "${gateway}" "${address}" } [ -n "${dns1}" ] && { @@ -265,7 +262,6 @@ modemmanager_connected_method_dhcp_ipv6() { local interface="$1" local wwan="$2" local metric="$3" - local defaultroute="$4" proto_init_update "${wwan}" 1 proto_set_keep 1 @@ -292,7 +288,6 @@ modemmanager_connected_method_static_ipv6() { local dns1="$7" local dns2="$8" local metric="$9" - local defaultroute="$10" [ -n "${address}" ] || { proto_notify_error "${interface}" ADDRESS_MISSING @@ -311,7 +306,7 @@ modemmanager_connected_method_static_ipv6() { echo "adding IPv6 address ${address}, prefix ${prefix}" proto_add_ipv6_address "${address}" "128" proto_add_ipv6_prefix "${address}/${prefix}" - [ -n "${gateway}" ] && [ "$defaultroute" != 0 ] && { + [ -n "${gateway}" ] && { echo "adding default IPv6 route via ${gateway}" proto_add_ipv6_route "${gateway}" "128" proto_add_ipv6_route "::0" "0" "${gateway}" "" "" "${address}/${prefix}" @@ -362,9 +357,9 @@ proto_modemmanager_setup() { local device apn allowedauth username password pincode iptype metric signalrate - local address prefix gateway mtu dns1 dns2 defaultroute + local address prefix gateway mtu dns1 dns2 - json_get_vars device apn allowedauth username password pincode iptype metric signalrate defaultroute + json_get_vars device apn allowedauth username password pincode iptype metric signalrate # validate sysfs path given in config [ -n "${device}" ] || { @@ -452,7 +447,7 @@ proto_modemmanager_setup() { echo "IPv4 connection setup required in interface ${interface}: ${bearermethod_ipv4}" case "${bearermethod_ipv4}" in "dhcp") - modemmanager_connected_method_dhcp_ipv4 "${interface}" "${beareriface}" "${metric}" "${defaultroute}" + modemmanager_connected_method_dhcp_ipv4 "${interface}" "${beareriface}" "${metric}" ;; "static") address=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv4-config.address") @@ -461,7 +456,7 @@ proto_modemmanager_setup() { mtu=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv4-config.mtu") dns1=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv4-config.dns.value\[1\]") dns2=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv4-config.dns.value\[2\]") - modemmanager_connected_method_static_ipv4 "${interface}" "${beareriface}" "${address}" "${prefix}" "${gateway}" "${mtu}" "${dns1}" "${dns2}" "${metric}" "${defaultroute}" + modemmanager_connected_method_static_ipv4 "${interface}" "${beareriface}" "${address}" "${prefix}" "${gateway}" "${mtu}" "${dns1}" "${dns2}" "${metric}" ;; "ppp") modemmanager_connected_method_ppp_ipv4 "${interface}" "${beareriface}" "${username}" "${password}" "${allowedauth}" @@ -479,7 +474,7 @@ proto_modemmanager_setup() { echo "IPv6 connection setup required in interface ${interface}: ${bearermethod_ipv6}" case "${bearermethod_ipv6}" in "dhcp") - modemmanager_connected_method_dhcp_ipv6 "${interface}" "${beareriface}" "${metric}" "${defaultroute}" + modemmanager_connected_method_dhcp_ipv6 "${interface}" "${beareriface}" "${metric}" ;; "static") address=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv6-config.address") @@ -488,7 +483,7 @@ proto_modemmanager_setup() { mtu=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv6-config.mtu") dns1=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv6-config.dns.value\[1\]") dns2=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv6-config.dns.value\[2\]") - modemmanager_connected_method_static_ipv6 "${interface}" "${beareriface}" "${address}" "${prefix}" "${gateway}" "${mtu}" "${dns1}" "${dns2}" "${metric}" "${defaultroute}" + modemmanager_connected_method_static_ipv6 "${interface}" "${beareriface}" "${address}" "${prefix}" "${gateway}" "${mtu}" "${dns1}" "${dns2}" "${metric}" ;; "ppp") proto_notify_error "${interface}" "unsupported method" @@ -520,7 +515,8 @@ proto_modemmanager_teardown() { modemstatus=$(mmcli --modem="${device}" --output-keyvalue) bearerpath=$(modemmanager_get_field "${modemstatus}" "modem.generic.bearers.value\[1\]") [ -n "${bearerpath}" ] || { - echo "couldn't load bearer path" + echo "couldn't load bearer path: disconnecting anyway" + mmcli --modem="${device}" --simple-disconnect >/dev/null 2>&1 return } diff --git a/mptcp/files/etc/init.d/mptcp b/mptcp/files/etc/init.d/mptcp index 99cd7d0db..0e3a0b391 100755 --- a/mptcp/files/etc/init.d/mptcp +++ b/mptcp/files/etc/init.d/mptcp @@ -172,6 +172,8 @@ interface_multipath_settings() { [ "$(echo $iface | grep '^if')" != "" ] && return 0 [ "$iface" = "lo" ] && return 0 + #echo "îface: $iface" + if [ "$mode" = "master" ]; then multipath "$iface" "on" else @@ -214,6 +216,7 @@ interface_multipath_settings() { fi if [ -z "$gateway" ] || [ "$( valid_subnet4 $gateway )" != "ok" ]; then gateway=$(traceroute -m1 -i $iface 8.8.8.8 2>/dev/null | awk 'FNR==2{ print $2 }') + [ "$gateway" = "*" ] && gateway="" fi network_get_subnet netmask $config [ -n "$netmask" ] && [ "$(echo $netmask | grep '/')" != "" ] && netmask="" @@ -262,13 +265,12 @@ interface_multipath_settings() { commit network EOF else - #echo "Add routes for $ipaddr table $id" [ -n "$ipaddr" ] && [ -z "$(ip rule show from $ipaddr table $id)" ] && ip rule add from $ipaddr table $id pref 0 [ -z "$(ip rule show oif $iface table $id)" ] && ip rule add oif $iface table $id pref 0 - ip route replace $network/$netmask dev $iface scope link metric $id 2>&1 >/dev/null - ip route replace $network/$netmask dev $iface scope link table $id 2>&1 >/dev/null - ip route replace default via $gateway dev $iface table $id 2>&1 >/dev/null - [ "$(uci -q get openmptcprouter.settings.defaultgw)" != "0" ] && ip route replace default via $gateway dev $iface metric $id 2>&1 >/dev/null + ip route replace $network/$netmask dev $iface scope link metric $id initcwnd 10 initrwnd 10 2>&1 >/dev/null + ip route replace $network/$netmask dev $iface scope link table $id initcwnd 10 initrwnd 10 2>&1 >/dev/null + ip route replace default via $gateway dev $iface table $id initcwnd 10 initrwnd 10 2>&1 >/dev/null + [ "$(uci -q get openmptcprouter.settings.defaultgw)" != "0" ] && ip route replace default via $gateway dev $iface metric $id initcwnd 10 initrwnd 10 2>&1 >/dev/null #ip route flush $id fi @@ -281,7 +283,7 @@ interface_multipath_settings() { if [ "$txqueuelen" != "" ]; then ifconfig $iface txqueuelen $txqueuelen > /dev/null 2>&1 else - ifconfig $iface txqueuelen 100 > /dev/null 2>&1 + ifconfig $iface txqueuelen 1000 > /dev/null 2>&1 fi fi if [ "$(uci -q get openmptcprouter.settings.disable_ipv6)" != "1" ] && [ "$config" != "omr6in4" ]; then @@ -294,25 +296,27 @@ interface_multipath_settings() { config_get ipaddr6 $config ip6addr config_get gateway6 $config ip6gw if [ -n "$ipaddr6" ]; then - ip6addr=`echo $ip6addr | cut -d/ -f1` - netmask6=`ipcalc $ipaddr6 | sed -n '/PREFIX=/{;s/.*=//;s/ .*//;p;}'` - network6=`ipcalc $ip6addr | sed -n '/NETWORK=/{;s/.*=//;s/ .*//;p;}'` + ip6addr=`echo "$ipaddr6" | cut -d/ -f1 | tr -d "\n"` + netmask6=`ipcalc -p $ipaddr6 | sed -n '/PREFIX=/{;s/.*=//;s/ .*//;p;}'` + network6=`ipcalc -n $ip6addr | sed -n '/NETWORK=/{;s/.*=//;s/ .*//;p;}'` fi - if [ -z "$ip6addr" ] || [ -z "$network6" ]; then + if [ -z "$ip6addr" ] || [ -z "$gateway6" ]; then [ -z "$ip6addr" ] && network_get_ipaddr6 ip6addr $config [ -z "$ip6addr" ] && ip6addr=$(ip -6 addr show dev $iface | grep -v 'scope link' | grep inet6 | awk '{print $2}' | cut -d/ -f1 | tr -d "\n") [ -z "$gateway6" ] && network_get_gateway6 gateway6 $config true [ -z "$gateway6" ] && gateway6=$(ip -6 r list dev $iface | grep -v default | awk '/proto static/ {print $1}' | tr -d "\n") [ -z "$gateway6" ] && gateway6=$(uci -q get "network.$config.ip6gw") [ -z "$gateway6" ] && gateway6=$(ubus call network.interface.$config status | jsonfilter -q -l 1 -e '@.inactive.route[@.target="::"].nexthop' | tr -d "\n") - if [ -z "$gateway6" ] || [ "$( valid_subnet6 $gateway6 )" != "ok" ]; then - gateway6=$(ubus call network.interface.$config status | jsonfilter -q -l 1 -e '@.route[@.target="::"].nexthop' | tr -d "\n") - fi - if [ -z "$gateway6" ] || [ "$( valid_subnet6 $gateway6 )" != "ok" ]; then - gateway6=$(ubus call network.interface.${config}_6 status 2>/dev/null | jsonfilter -q -l 1 -e '@.inactive.route[@.target="::"].nexthop' | tr -d "\n") - fi - netmask6=$(ip -6 addr show dev $iface | grep -v 'scope link' | grep inet6 | awk '{print $2}' | cut -d/ -f2 | tr -d "\n") - [ -n "$ip6addr" ] && network6=`ipcalc $ip6addr | sed -n '/NETWORK=/{;s/.*=//;s/ .*//;p;}'` + #if [ -z "$gateway6" ] || [ "$( valid_subnet6 $gateway6 )" != "ok" ]; then + # gateway6=$(ubus call network.interface.$config status | jsonfilter -q -l 1 -e '@.route[@.target="::"].nexthop' | tr -d "\n") + #fi + #if [ -z "$gateway6" ] || [ "$( valid_subnet6 $gateway6 )" != "ok" ]; then + # echo "ipv6 not ok" + # gateway6=$(ubus call network.interface.${config}_6 status 2>/dev/null | jsonfilter -q -l 1 -e '@.inactive.route[@.target="::"].nexthop' | tr -d "\n") + #fi + gateway6=$(echo $gateway6 | cut -d/ -f1 | tr -d "\n") + netmask6=$(ip -6 addr show dev $iface | sort -r | grep -m 1 inet6 | awk '{print $2}' | cut -d/ -f2 | tr -d "\n") + [ -n "$ip6addr" ] && network6=`ipcalc -n $ip6addr | sed -n '/NETWORK=/{;s/.*=//;s/ .*//;p;}'` fi if [ "$(uci -q get openmptcprouter.settings.uci_route)" = "1" ]; then uci -q batch <<-EOF >/dev/null @@ -326,6 +330,7 @@ interface_multipath_settings() { ip -6 route flush 6$id > /dev/null 2>&1 fi if [ -n "$gateway6" ] && [ -n "$network6" ]; then + #echo "gateway6: $gateway6 - network6: $network6 -> ok" if [ "$(uci -q get openmptcprouter.settings.uci_route)" = "1" ]; then uci -q batch <<-EOF >/dev/null delete network.${config}_rule6 @@ -351,11 +356,11 @@ interface_multipath_settings() { EOF else [ -n "$ip6addr" ] && ip -6 rule add from $ip6addr table 6$id pref 0 2>&1 >/dev/null - ip rule add oif $iface table 6$id pref 0 - ip -6 route replace $network6/$netmask6 dev $iface scope link metric 6$id 2>&1 >/dev/null - ip -6 route replace $network6/$netmask6 dev $iface scope link table 6$id 2>&1 >/dev/null - ip -6 route replace default via $gateway6 dev $iface table 6$id 2>&1 >/dev/null - [ "$(uci -q get openmptcprouter.settings.defaultgw)" != "0" ] && ip -6 route replace default via $gateway6 dev $iface metric 6$id 2>&1 >/dev/null + [ -z "$(ip rule show pref 0 table 6$id oif $iface)" ] && ip rule add oif $iface table 6$id pref 0 + ip -6 route replace $network6/$netmask6 dev $iface scope link metric 6$id initcwnd 10 initrwnd 10 2>&1 >/dev/null + ip -6 route replace $network6/$netmask6 dev $iface scope link table 6$id initcwnd 10 initrwnd 10 2>&1 >/dev/null + ip -6 route replace default via $gateway6 dev $iface table 6$id initcwnd 10 initrwnd 10 2>&1 >/dev/null + [ "$(uci -q get openmptcprouter.settings.defaultgw)" != "0" ] && ip -6 route replace default via $gateway6 dev $iface metric 6$id initcwnd 10 initrwnd 10 2>&1 >/dev/null #ip -6 route flush 6$id 2>&1 >/dev/null fi diff --git a/mptcp/files/etc/uci-defaults/mptcp-defaults b/mptcp/files/etc/uci-defaults/mptcp-defaults index 594086874..21d2678a5 100755 --- a/mptcp/files/etc/uci-defaults/mptcp-defaults +++ b/mptcp/files/etc/uci-defaults/mptcp-defaults @@ -22,7 +22,7 @@ fi #fi if [ "$(uci -q get network.globals.mptcp_syn_retries)" = "1" ]; then uci -q batch <<-EOF >/dev/null - set network.globals.mptcp_syn_retries=2 + set network.globals.mptcp_syn_retries=4 commit network EOF fi diff --git a/mptcp/files/usr/bin/multipath b/mptcp/files/usr/bin/multipath index 4f74c3d83..85ff3dda9 100755 --- a/mptcp/files/usr/bin/multipath +++ b/mptcp/files/usr/bin/multipath @@ -131,31 +131,47 @@ if [ -f /proc/sys/net/mptcp/mptcp_enabled ]; then printf "0x%02x" $(($(($IFF^$(($IFF&$IFF_MASK))))|$FLAG)) > $FLAG_PATH else - ID=$(ip mptcp endpoint show | grep "dev $DEVICE" | awk '{print $3}') - IFF=$(ip mptcp endpoint show | grep "dev $DEVICE" | awk '{print $4}') + ID=$(ip mptcp endpoint show | grep -m 1 "dev $DEVICE" | awk '{print $3}') + IFF=$(ip mptcp endpoint show | grep -m 1 "dev $DEVICE" | awk '{print $4}') IP=$(ip a show $DEVICE | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p') [ -z "$ID" ] && [ -n "$IP" ] && ID=$(ip mptcp endpoint show | grep "$IP" | awk '{print $3}') RMID=$(ip mptcp endpoint show | grep '::ffff' | awk '{ print $3 }') [ -n "$RMID" ] && ip mptcp endpoint delete id $RMID 2>&1 >/dev/null case $TYPE in "off") - [ -n "$ID" ] && ip mptcp endpoint delete id $ID 2>&1 >/dev/null + [ -n "$ID" ] && { + for i in $ID; do + ip mptcp endpoint delete id $i 2>&1 >/dev/null + done + } exit 0;; "on") - [ -n "$ID" ] && ip mptcp endpoint delete id $ID 2>&1 >/dev/null + [ -n "$ID" ] && { + for i in $ID; do + ip mptcp endpoint delete id $i 2>&1 >/dev/null + done + } for i in $IP; do ip mptcp endpoint add $i dev $DEVICE subflow fullmesh done exit 0;; "signal") - [ -n "$ID" ] && ip mptcp endpoint delete id $ID 2>&1 >/dev/null + [ -n "$ID" ] && { + for i in $ID; do + ip mptcp endpoint delete id $i 2>&1 >/dev/null + done + } for i in $IP; do #ip mptcp endpoint add $i dev $DEVICE signal subflow fullmesh ip mptcp endpoint add $i dev $DEVICE signal done exit 0;; "backup") - [ -n "$ID" ] && ip mptcp endpoint delete id $ID 2>&1 >/dev/null + [ -n "$ID" ] && { + for i in $ID; do + ip mptcp endpoint delete id $i 2>&1 >/dev/null + done + } for i in $IP; do ip mptcp endpoint add $i dev $DEVICE backup fullmesh done diff --git a/mptcp/files/usr/share/omr/post-tracking.d/post-tracking b/mptcp/files/usr/share/omr/post-tracking.d/post-tracking index b436b2b91..df45d8bd1 100755 --- a/mptcp/files/usr/share/omr/post-tracking.d/post-tracking +++ b/mptcp/files/usr/share/omr/post-tracking.d/post-tracking @@ -57,8 +57,8 @@ set_route() { if [ "$interface_gw" != "" ] && [ "$interface_if" != "" ]; then [ "$(uci -q get openmptcprouter.settings.debug)" = "true" ] && [ "$SETDEFAULT" = "yes" ] && _log "$PREVINTERFACE down. Replace default route by $interface_gw dev $interface_if" [ "$(uci -q get openmptcprouter.settings.debug)" = "true" ] && [ "$SETDEFAULT" != "yes" ] && _log "$PREVINTERFACE down. Replace default in table 991337 route by $interface_gw dev $interface_if" - [ "$SETDEFAULT" = "yes" ] && [ "$(uci -q openmptcprouter.settings.defaultgw)" != "0" ] && ip route replace default scope global metric 1 nexthop via $interface_gw dev $interface_if 2>&1 >/dev/null - ip route replace default via $interface_gw dev $interface_if table 991337 2>&1 >/dev/null && SETROUTE=true + [ "$SETDEFAULT" = "yes" ] && [ "$(uci -q openmptcprouter.settings.defaultgw)" != "0" ] && ip route replace default scope global metric 1 via $interface_gw dev $interface_if initcwnd 10 initrwnd 10 2>&1 >/dev/null + ip route replace default via $interface_gw dev $interface_if table 991337 initcwnd 10 initrwnd 10 2>&1 >/dev/null && SETROUTE=true fi fi } @@ -123,7 +123,7 @@ set_server_default_route() { if [ "$(ip r show $serverip | grep nexthop)" != "" ]; then ip r delete $serverip >/dev/null 2>&1 fi - ip route replace $serverip via $OMR_TRACKER_DEVICE_GATEWAY dev $OMR_TRACKER_DEVICE metric 1 2>&1 >/dev/null + ip route replace $serverip via $OMR_TRACKER_DEVICE_GATEWAY dev $OMR_TRACKER_DEVICE metric 1 initcwnd 10 initrwnd 10 2>&1 >/dev/null fi } config_list_foreach $server ip server_route @@ -561,12 +561,12 @@ set_server_route() { #if [ "$serverip" != "" ] && [ "$OMR_TRACKER_DEVICE_GATEWAY" != "" ] && [ "$(ip route show dev $OMR_TRACKER_DEVICE metric $metric | grep $serverip | grep $OMR_TRACKER_DEVICE_GATEWAY)" = "" ] && [ "$multipath_config_route" != "off" ] && [ "$multipath_current_config" = "" ]; then if [ "$serverip" != "" ] && [ -n "$OMR_TRACKER_DEVICE" ] && [ "$OMR_TRACKER_DEVICE_GATEWAY" != "" ] && [ "$(ip route show dev $OMR_TRACKER_DEVICE metric $metric | grep $serverip | grep $OMR_TRACKER_DEVICE_GATEWAY)" = "" ] && [ "$multipath_config_route" != "off" ] && [ "$interface_current_config" = "up" ] && [ "$interface_up" = "true" ]; then [ "$(uci -q get openmptcprouter.settings.debug)" = "true" ] && _log "Set server $server ($serverip) route via $OMR_TRACKER_DEVICE_GATEWAY metric $metric" - ip route replace $serverip via $OMR_TRACKER_DEVICE_GATEWAY dev $OMR_TRACKER_DEVICE metric $metric 2>&1 >/dev/null + ip route replace $serverip via $OMR_TRACKER_DEVICE_GATEWAY dev $OMR_TRACKER_DEVICE metric $metric initcwnd 10 initrwnd 10 2>&1 >/dev/null fi } config_list_foreach $server ip server_route if [ "$(uci -q get openmptcprouter.settings.defaultgw)" != "0" ] && [ -n "$metric" ] && [ "$OMR_TRACKER_DEVICE_GATEWAY" != "" ] && [ -n "$OMR_TRACKER_DEVICE" ] && [ "$(ip route show dev $OMR_TRACKER_DEVICE metric $metric | grep default | grep $OMR_TRACKER_DEVICE_GATEWAY)" = "" ] && [ "$multipath_config_route" != "off" ] && [ "$interface_current_config" = "up" ] && [ "$interface_up" = "true" ]; then - ip route replace default via $OMR_TRACKER_DEVICE_GATEWAY dev $OMR_TRACKER_DEVICE metric $metric 2>&1 >/dev/null + ip route replace default via $OMR_TRACKER_DEVICE_GATEWAY dev $OMR_TRACKER_DEVICE metric $metric initcwnd 10 initrwnd 10 2>&1 >/dev/null fi } @@ -991,7 +991,7 @@ if [ "$OMR_TRACKER_INTERFACE" = "glorytun" ] || [ "$OMR_TRACKER_INTERFACE" = "om if ([ "$default_gw" != "$OMR_TRACKER_DEVICE_GATEWAY" ] || [ "$default_gw" = "" ]) && [ "$OMR_TRACKER_DEVICE_GATEWAY" != "" ]; then _log "Tunnel up : Replace default route by $OMR_TRACKER_DEVICE_GATEWAY dev $OMR_TRACKER_DEVICE" [ "$(uci -q get openmptcprouter.settings.debug)" = "true" ] && _log "Default gw : $default_gw - Current route: $(ip r)" - ip route replace default scope global nexthop via $OMR_TRACKER_DEVICE_GATEWAY dev $OMR_TRACKER_DEVICE 2>&1 >/dev/null + ip route replace default scope global via $OMR_TRACKER_DEVICE_GATEWAY dev $OMR_TRACKER_DEVICE initcwnd 10 initrwnd 10 2>&1 >/dev/null if [ "$(pgrep -f openmptcprouter-vps)" = "" ]; then /etc/init.d/openmptcprouter-vps restart >/dev/null 2>&1 & fi @@ -1092,6 +1092,13 @@ if [ -n "$OMR_TRACKER_INTERFACE" ]; then } fi +if [ -n "$OMR_TRACKER_INTERFACE" ] && [ -n "$OMR_TRACKER_DEVICE" ]; then + metric="$(uci -q get network.$OMR_TRACKER_INTERFACE.metric)" + if [ -z "$metric" ] || [ -z "$(ip route show table $metric | grep $OMR_TRACKER_DEVICE)" ]; then + /etc/init.d/mptcp reload "$OMR_TRACKER_DEVICE" + fi +fi + if [ "$multipath_config" = "master" ]; then #if ([ "$default_gw" != "$OMR_TRACKER_DEVICE_GATEWAY" ] || [ "$default_gw" = "" ]) && [ "$OMR_TRACKER_DEVICE_GATEWAY" != "" ] && ([ "$(uci -q get openmptcprouter.settings.master)" != "balancing" ] || [ "$(uci -q get openmptcprouter.settings.vpn)" = "mlvpn" ]); then if ([ "$default_gw" != "$OMR_TRACKER_DEVICE_GATEWAY" ] || [ "$default_gw" = "" ]) && [ "$OMR_TRACKER_DEVICE_GATEWAY" != "" ] && [ "$(uci -q get openmptcprouter.settings.master)" != "balancing" ]; then @@ -1099,13 +1106,14 @@ if [ "$multipath_config" = "master" ]; then [ -z "$omrvpn_intf" ] && omrvpn_intf=$(uci -q get "network.omrvpn.ifname" || echo "tun0") if [ -n "$omrvpn_intf" ] && [ "$(ip route show default | grep -v metric | awk '/default/ {print $5}' | grep $omrvpn_intf)" = "" ] && [ "$(uci -q get openmptcprouter.settings.defaultgw)" != "0" ]; then _log "Master up : Replace default route by $OMR_TRACKER_DEVICE_GATEWAY dev $OMR_TRACKER_DEVICE" - ip route replace default scope global metric 1 nexthop via $OMR_TRACKER_DEVICE_GATEWAY dev $OMR_TRACKER_DEVICE 2>&1 >/dev/null + ip route replace default scope global metric 1 via $OMR_TRACKER_DEVICE_GATEWAY dev $OMR_TRACKER_DEVICE initcwnd 10 initrwnd 10 2>&1 >/dev/null fi config_load openmptcprouter #if [ "$(uci -q get openmptcprouter.settings.master)" = "balancing" ]; then # config_foreach set_server_all_routes server if [ "$(uci -q get openmptcprouter.settings.master)" != "failover" ]; then config_foreach set_server_default_route server + #config_foreach set_server_default_route6 server fi fi if ([ "$default_gw6" != "$OMR_TRACKER_DEVICE_GATEWAY6" ] || [ "$default_gw6" = "" ]) && [ "$OMR_TRACKER_DEVICE_GATEWAY6" != "" ] && [ "$(uci -q get openmptcprouter.settings.master)" != "balancing" ]; then @@ -1119,12 +1127,12 @@ if [ "$multipath_config" = "master" ]; then #if [ "$(uci -q get openmptcprouter.settings.master)" = "balancing" ]; then # config_foreach set_server_all_routes server if [ "$(uci -q get openmptcprouter.settings.master)" != "failover" ]; then - config_foreach set_server_default_route server + #config_foreach set_server_default_route server config_foreach set_server_default_route6 server fi fi #if [ "$(uci -q get openmptcprouter.settings.master)" = "balancing" ] && [ "$(ip route show default | grep weight)" = "" ] && [ "$(uci -q get openmptcprouter.settings.defaultgw)" != "0" ] && [ "$(uci -q get openmptcprouter.settings.vpn)" != "mlvpn" ]; then - if [ "$(uci -q get openmptcprouter.settings.master)" = "balancing" ] && [ "$(ip route show default | grep weight)" = "" ] && [ "$(uci -q get openmptcprouter.settings.defaultgw)" != "0" ]; then + if [ "$(uci -q get openmptcprouter.settings.master)" = "balancing" ] && ([ "$(ip route show default | grep weight)" = "" ] || [ "$(ip -6 route show default | grep weight)" = "" ]) && [ "$(uci -q get openmptcprouter.settings.defaultgw)" != "0" ]; then omrvpn_intf=$(uci -q get "network.omrvpn.device" || echo "tun0") [ -z "$omrvpn_intf" ] && omrvpn_intf=$(uci -q get "network.omrvpn.ifname" || echo "tun0") if [ -n "$omrvpn_intf" ] && [ "$(ip route show default | grep -v metric | awk '/default/ {print $5}' | grep $omrvpn_intf)" = "" ]; then @@ -1132,11 +1140,11 @@ if [ "$multipath_config" = "master" ]; then routesbalancingbackup="" nbintf=0 nbintfb=0 - nbintf6=0 - nbintfb6=0 + #nbintf6=0 + #nbintfb6=0 config_load network config_foreach set_route_balancing interface - config_foreach set_route_balancing6 interface + #config_foreach set_route_balancing6 interface [ -n "$routesbalancing" ] && { ([ "$nbintf" -gt "1" ] && [ "$(ip r show default metric 1 | tr -d '\t' | tr -d '\n')" != "default via $routesbalancing " ]) || ([ "$nbintf" = "1" ] && ([ "$(ip r show default metric 1 | grep $OMR_TRACKER_DEVICE)" = "" ] || ([ -n "$OMR_TRACKER_INTERFACE" ] && [ "$(uci -q get openmptcprouter.$OMR_TRACKER_INTERFACE.vpn)" = "1" ])) && [ -n "$OMR_TRACKER_DEVICE_IP" ]) && { _log "Change in routes, set ip route replace default scope global $routesbalancing (omrvpn_intf: $omrvpn_intf)" @@ -1145,18 +1153,37 @@ if [ "$multipath_config" = "master" ]; then [ "$(uci -q get openmptcprouter.settings.debug)" = "true" ] && _log "New route: $(ip r)" } } - [ -n "$routesbalancing6" ] && { - ([ "$nbintf6" -gt "1" ] && [ "$(ip -6 r show default metric 1 | tr -d '\t' | tr -d '\n')" != "default via $routesbalancing6 " ]) || ([ "$nbintf6" = "1" ] && [ "$(ip -6 r show default metric 1 | grep $OMR_TRACKER_DEVICE)" = "" ] && [ -n "$OMR_TRACKER_DEVICE_IP6" ]) && { - _log "Set ip -6 route replace default scope global metric 1 $routesbalancing6" - ip -6 route replace default scope global metric 1 $routesbalancing6 2>&1 >/dev/null - } - } + #[ -n "$routesbalancing6" ] && { + # ([ "$nbintf6" -gt "1" ] && [ "$(ip -6 r show default metric 1 | tr -d '\t' | tr -d '\n')" != "default via $routesbalancing6 " ]) || ([ "$nbintf6" = "1" ] && [ "$(ip -6 r show default metric 1 | grep $OMR_TRACKER_DEVICE)" = "" ] && [ -n "$OMR_TRACKER_DEVICE_IP6" ]) && { + # _log "Set ip -6 route replace default scope global metric 1 $routesbalancing6" + # ip -6 route replace default scope global metric 1 $routesbalancing6 2>&1 >/dev/null + # } + #} [ -n "$routesbalancingbackup" ] && { ([ "$nbintfb" -gt "1" ] && [ "$(ip r show default metric 999 | tr -d '\t' | tr -d '\n')" != "default via $routesbalancingbackup " ]) || ([ "$nbintf" = "1" ] && ([ "$(ip r show default metric 999 | grep $OMR_TRACKER_DEVICE)" = "" ] || ([ -n "$OMR_TRACKER_INTERFACE" ] && [ "$(uci -q get openmptcprouter.$OMR_TRACKER_INTERFACE.vpn)" = "1" ])) && [ -n "$OMR_TRACKER_DEVICE_IP" ]) && { _log "Set backup ip route replace default scope global metric 999 $routesbalancingbackup" ip route replace default scope global metric 999 $routesbalancingbackup 2>&1 >/dev/null } } + #[ -n "$routesbalancingbackup6" ] && { + # ([ "$nbintfb6" -gt "1" ] && [ "$(ip -6 r show default metric 999 | tr -d '\t' | tr -d '\n')" != "default via $routesbalancingbackup6 " ]) || ([ "$nbintf6" = "1" ] && [ "$(ip -6 r show default metric 999 | grep $OMR_TRACKER_DEVICE)" = "" ] && [ -n "$OMR_TRACKER_DEVICE_IP6" ]) && { + # _log "Set backup ip -6 route replace default scope global $routesbalancingbackup6" + # ip -6 route replace default scope global metric 999 $routesbalancingbackup6 2>&1 >/dev/null + # } + #} + elif [ -n "$omrvpn_intf" ] && [ "$(ip -6 route show default | grep -v metric | awk '/default/ {print $5}' | grep $omrvpn_intf)" = "" ]; then + routesbalancing6="" + routesbalancingbackup6="" + nbintf6=0 + nbintfb6=0 + config_load network + config_foreach set_route_balancing6 interface + [ -n "$routesbalancing6" ] && { + ([ "$nbintf6" -gt "1" ] && [ "$(ip -6 r show default metric 1 | tr -d '\t' | tr -d '\n')" != "default via $routesbalancing6 " ]) || ([ "$nbintf6" = "1" ] && [ "$(ip -6 r show default metric 1 | grep $OMR_TRACKER_DEVICE)" = "" ] && [ -n "$OMR_TRACKER_DEVICE_IP6" ]) && { + _log "Set ip -6 route replace default scope global metric 1 $routesbalancing6" + ip -6 route replace default scope global metric 1 $routesbalancing6 2>&1 >/dev/null + } + } [ -n "$routesbalancingbackup6" ] && { ([ "$nbintfb6" -gt "1" ] && [ "$(ip -6 r show default metric 999 | tr -d '\t' | tr -d '\n')" != "default via $routesbalancingbackup6 " ]) || ([ "$nbintf6" = "1" ] && [ "$(ip -6 r show default metric 999 | grep $OMR_TRACKER_DEVICE)" = "" ] && [ -n "$OMR_TRACKER_DEVICE_IP6" ]) && { _log "Set backup ip -6 route replace default scope global $routesbalancingbackup6" @@ -1166,10 +1193,10 @@ if [ "$multipath_config" = "master" ]; then fi fi if [ -n "$OMR_TRACKER_DEVICE_GATEWAY" ] && [ -n "$OMR_TRACKER_DEVICE" ] && [ "$(ip r show table 991337)" != "default via $OMR_TRACKER_DEVICE_GATEWAY dev $OMR_TRACKER_DEVICE " ]; then - ip route replace default via $OMR_TRACKER_DEVICE_GATEWAY dev $OMR_TRACKER_DEVICE table 991337 2>&1 >/dev/null + ip route replace default via $OMR_TRACKER_DEVICE_GATEWAY dev $OMR_TRACKER_DEVICE table 991337 initcwnd 10 initrwnd 10 2>&1 >/dev/null fi if [ -n "$OMR_TRACKER_DEVICE_GATEWAY6" ] && [ -n "$OMR_TRACKER_DEVICE" ] && [ "$(ip -6 r show table 991337)" != "default via $OMR_TRACKER_DEVICE_GATEWAY6 dev $OMR_TRACKER_DEVICE " ]; then - ip -6 route replace default via $OMR_TRACKER_DEVICE_GATEWAY6 dev $OMR_TRACKER_DEVICE table 991337 2>&1 >/dev/null + ip -6 route replace default via $OMR_TRACKER_DEVICE_GATEWAY6 dev $OMR_TRACKER_DEVICE table 991337 initcwnd 10 initrwnd 10 2>&1 >/dev/null fi if ([ -n "$OMR_TRACKER_INTERFACE" ] && [ "$(uci -q get openmptcprouter.$OMR_TRACKER_INTERFACE.lc)" = "" ]) || [ $(($(date +"%s") + $((10 + RANDOM % 31)) - $(uci -q get openmptcprouter.$OMR_TRACKER_INTERFACE.lc))) -gt 3600 ] || [ "$(uci -q show openmptcprouter | grep get_config=\'1\')" != "" ] || [ "$(uci -q show openmptcprouter | grep admin_error=\'1\')" != "" ]; then [ "$(pgrep -f openmptcprouter-vps)" = "" ] && /etc/init.d/openmptcprouter-vps restart >/dev/null 2>&1 & @@ -1266,6 +1293,10 @@ fi [ "$(uci -q get openmptcprouter.$OMR_TRACKER_INTERFACE.multipathvpn)" != "1" ] && { [ "$multipath_status" = "$multipath_config" ] || { if [ "$(sysctl -qen net.mptcp.mptcp_enabled | tr -d '\n')" = "1" ] || [ "$(sysctl -qen net.mptcp.enabled | tr -d '\n')" = "1" ]; then + if [ "$(uci -q get network.$OMR_TRACKER_INTERFACE.force_link)" != "0" ]; then + _log "Reload MPTCP config for $OMR_TRACKER_DEVICE" + /etc/init.d/mptcp reload "$OMR_TRACKER_DEVICE" + fi _log "Multipath $OMR_TRACKER_DEVICE switched to $multipath_config" multipath "$OMR_TRACKER_DEVICE" "$multipath_config" fi diff --git a/mptcpd/Makefile b/mptcpd/Makefile index cb6f95f6d..1b67f08fd 100755 --- a/mptcpd/Makefile +++ b/mptcpd/Makefile @@ -25,7 +25,7 @@ include $(INCLUDE_DIR)/package.mk define Package/$(PKG_NAME) SECTION:=net CATEGORY:=Network -DEPENDS:=+libell @LINUX_5_15 +DEPENDS:=+libell @(LINUX_5_15||LINUX_6_1) TITLE:=mptcpd URL:=https://github.com/intel/mptcpd endef diff --git a/ndpi-netfilter2/Makefile b/ndpi-netfilter2/Makefile index 13f73dc14..8983cff1b 100755 --- a/ndpi-netfilter2/Makefile +++ b/ndpi-netfilter2/Makefile @@ -11,7 +11,7 @@ include $(INCLUDE_DIR)/kernel.mk PKG_NAME:=ndpi-netfilter2 PKG_RELEASE:=4 -PKG_REV:=cf017cc2fecee644d0b2ff633a17377f776d1505 +PKG_REV:=ac0ad66b3644a43cbaa821a6135d3e610f8b2c1a PKG_VERSION:=4-$(PKG_REV) PKG_SOURCE_PROTO:=git diff --git a/ndpi-netfilter2/patches/fcommon.patch b/ndpi-netfilter2/patches/fcommon.patch deleted file mode 100644 index faa8c57cf..000000000 --- a/ndpi-netfilter2/patches/fcommon.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/ndpi-netfilter/ipt/Makefile.anc 2022-09-05 09:34:30.579787766 +0200 -+++ b/ndpi-netfilter/ipt/Makefile 2022-09-05 09:34:42.555588398 +0200 -@@ -2,14 +2,14 @@ - NDPI_PRO := ${NDPI_SRC}/lib/protocols - XTBL := $(shell pkg-config --variable=xtlibdir xtables) - --CFLAGS = -fPIC -I../.. -I${NDPI_SRC}/include -I${NDPI_SRC}/lib -I../src -I../libre -DOPENDPI_NETFILTER_MODULE -O2 -Wall -+CFLAGS = -fPIC -I../.. -I${NDPI_SRC}/include -I${NDPI_SRC}/lib -I../src -I../libre -DOPENDPI_NETFILTER_MODULE -O2 -Wall -fcommon - - all: libxt_ndpi.so - install: libxt_ndpi.so - if [ -n "$(DESTDIR)$(XTBL)" -a -d "$(DESTDIR)$(XTBL)" ]; then install -v libxt_ndpi.so $(DESTDIR)$(XTBL); ln -fs libxt_ndpi.so $(DESTDIR)$(XTBL)/libxt_NDPI.so ; else echo "No pkg-config --variable=xtlibdir xtables"; fi - - lib%.so: lib%.o -- $(CC) -shared -o $@ $^; -+ $(CC) -shared -nostartfiles -o $@ $^; - lib%.o: lib%.c ../src/xt_ndpi.h ${NDPI_SRC}/include/ndpi_config.h ../libre/regexp.h ../libre/regexp.c - $(CC) ${CFLAGS} -D_INIT=lib$*_init -c -o $@ $<; - clean: diff --git a/netmaker-openwrt/LICENSE b/netmaker-openwrt/LICENSE new file mode 100644 index 000000000..472ac2361 --- /dev/null +++ b/netmaker-openwrt/LICENSE @@ -0,0 +1,8 @@ +MIT License +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/netmaker-openwrt/README.md b/netmaker-openwrt/README.md new file mode 100644 index 000000000..60b189cce --- /dev/null +++ b/netmaker-openwrt/README.md @@ -0,0 +1,111 @@ +# Netmaker-OpenWRT + +[Netmaker](https://github.com/gravitl/netmaker) is a platform for creating and managing fast, secure, and dynamic virtual overlay networks using WireGuard. This project offers OpenWRT packages for Netmaker. + +## Installing package + +Download the prebuild package and copy it onto your OpenWRT installation, preferably into the `/tmp` folder. + +Then install the ipk package file: + +```bash +opkg install netmaker_*.ipk +``` + +Now start `netclient` of Netmaker: + +```bash +/etc/init.d/netclient start +``` + +## Compiling from Sources + +To include Netmaker into your OpenWRT image or to create an `.ipk` package (equivalent to Debians .deb files), you have to build an OpenWRT image. + +Now prepare OpenWRT: + +```bash +git clone https://github.com/openwrt/openwrt +cd openwrt + +./scripts/feeds update -a +./scripts/feeds install -a +``` + +To build Netmaker for OpenWRT, you need to have Golang with OpenWRT build envirment. Then, you can insert the Netmaker package using a package feed or add the package manually. + +### Add package by feed + +A feed is the standard way packages are made available to the OpenWRT build system. + +Put this line in your feeds list file (e.g. feeds.conf.default) + +```bash +src-git netmaker http://github.com/sbilly/netmaker-openwrt.git +``` + +Update and install the new feed + +```bash +./scripts/feeds update netmaker +./scripts/feeds install netmaker +``` + +Now continue with the building packages section. + +## Building Packages + +Configure packages: + +```bash +make menuconfig +``` + +Now select the appropiate "Target System" and "Target Profile" depending on what target chipset/router you want to build for. Also mark the Netmaker package under `Network ---> VPN ---> <*> netmaker`. + +Now compile/build everything: + +```bash +make +``` + +The images and all *.ipk packages are now inside the bin/ folder, including the netmaker package. You can install the Netmaker .ipk on the target device using opkg install . + +For details please check the OpenWRT documentation. + +## Build bulk packages + +For a release, it is useful the build packages at a bulk for multiple targets: + +```shell +#!/bin/sh + +# dump-target-info.pl is used to get all targets configurations: +# https://git.openwrt.org/?p=openwrt/openwrt.git;a=blob;f=scripts/dump-target-info.pl + +./scripts/dump-target-info.pl architectures | while read pkgarch target1 rest; do + echo "CONFIG_TARGET_${target1%/*}=y" > .config + echo "CONFIG_TARGET_${target1%/*}_${target1#*/}=y" >> .config + echo "CONFIG_PACKAGE_example1=y" >> .config + + # Debug output + echo "pkgarch: $pkgarch, target1: $target1" + + make defconfig + make -j4 tools/install + make -j4 toolchain/install + + # Build package + make package/netmaker/{clean,compile} + + # Free space (optional) + rm -rf build_dir/target-* + rm -rf build_dir/toolchain-* +done +``` + +## Thanks + +- [netmaker](https://github.com/gravitl/netmaker) +- [zerotier-openwrt](https://github.com/mwarning/zerotier-openwrt) +- [openwrt-golang-package-test-feed](https://github.com/jefferyto/openwrt-golang-package-test-feed) diff --git a/netmaker-openwrt/netmaker/Makefile b/netmaker-openwrt/netmaker/Makefile new file mode 100644 index 000000000..903dd2beb --- /dev/null +++ b/netmaker-openwrt/netmaker/Makefile @@ -0,0 +1,93 @@ +# +# Copyright (C) 2019 sbilly +# +# This is free software, licensed under the MIT License. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=netmaker +PKG_VERSION:=0.9.4 +PKG_RELEASE:=1 + +PKG_SOURCE_PROTO:=git +PKG_SOURCE_URL:=https://github.com/gravitl/netmaker.git +PKG_SOURCE_VERSION:=e9bce264719f88c30e252ecc754d08f422f4c080 +PKG_SOURCE_DATE:=20220117 +PKG_MIRROR_HASH:=skip + +PKG_LICENSE:=MIT +PKG_LICENSE_FILES:=LICENSE +PKG_MAINTAINER:=sbilly + +PKG_BUILD_DEPENDS:=golang/host +PKG_BUILD_PARALLEL:=1 +PKG_USE_MIPS16:=0 + +GO_PKG:=github.com/gravitl/netmaker +GO_PKG_INSTALL_EXTRA:=extra/file extra/dir +GO_PKG_EXCLUDES:=excluded +GO_PKG_LDFLAGS:=-s -w + +include $(INCLUDE_DIR)/package.mk +include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk + +define Package/netmaker +$(call Package/netmaker/Default) +$(call GoPackage/GoSubMenu) + SECTION:=net + CATEGORY:=Network + SUBMENU:=VPN +endef + +define Package/netmaker/Default + TITLE:=Netmaker for OpenWRT + URL:=https://github.com/gravitl/netmaker + DEPENDS:=$(GO_ARCH_DEPENDS) + MAINTAINER:=sbilly +endef + +define Package/netmaker/Default/description +Netmaker is a platform for creating and managing fast, secure, and +dynamic virtual overlay networks using WireGuard. This project offers +OpenWRT packages for Netmaker. +endef + +define Package/netmaker/description +$(call Package/netmaker/Default/description) + +This package contains the binaries. +endef + +define Package/netmaker-dev + TITLE+= (source files) + SECTION:=net + CATEGORY:=Network + SUBMENU:=VPN + PKGARCH:=all +endef + +define Package/netmaker-dev/description +$(call Package/netmaker/Default/description) + +This package provides the source files. +endef + +define Package/netmaker/install + $(INSTALL_DIR) $(1)/etc/netclient/ + $(INSTALL_DIR) $(1)/etc/netclient/config + $(INSTALL_DIR) $(1)/etc/systemd/ + $(INSTALL_DIR) $(1)/etc/systemd/system + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) $(GO_PKG_BUILD_BIN_DIR)/netmaker $(1)/usr/bin/ + $(INSTALL_BIN) $(GO_PKG_BUILD_BIN_DIR)/netclient $(1)/usr/bin/ + $(CP) ./root/* $(1)/ + $(LN) netclient $(1)/etc/netclient/netclient +endef + +$(eval $(call GoBinPackage,netmaker)) +$(eval $(call BuildPackage,netmaker)) + +$(eval $(call GoSrcPackage,netmaker-dev)) +$(eval $(call BuildPackage,netmaker-dev)) diff --git a/netmaker-openwrt/netmaker/root/etc/init.d/netclient b/netmaker-openwrt/netmaker/root/etc/init.d/netclient new file mode 100644 index 000000000..c04977f24 --- /dev/null +++ b/netmaker-openwrt/netmaker/root/etc/init.d/netclient @@ -0,0 +1,42 @@ +#!/bin/sh /etc/rc.common +#Created by oycol + +EXTRA_COMMANDS="status" +EXTRA_HELP=" status Check service is running" +START=99 + +LOG_FILE="/tmp/netclient.logs" + +start() { + mkdir -p /etc/netclient/config + mkdir -p /etc/systemd/system + + if [ ! -f "${LOG_FILE}" ];then + touch "${LOG_FILE}" + fi + local PID=$(ps|grep "netclient checkin -n all"|grep -v grep|awk '{print $1}') + if [ "${PID}" ];then + echo "service is running" + return + fi + /bin/sh -c "while [ 1 ]; do netclient checkin -n all >> ${LOG_FILE} 2>&1;sleep 15;\ + if [ $(ls -l ${LOG_FILE}|awk '{print $5}') -gt 10240000 ];then tar zcf "${LOG_FILE}.tar" -C / "tmp/netclient.logs" && > $LOG_FILE;fi;done &" + echo "start" +} + +stop() { + local PID=$(ps|grep "netclient checkin -n all"|grep -v grep|awk '{print $1}') + if [ "${PID}" ];then + kill "${PID}" + fi + echo "stop" +} + +status() { + local PID=$(ps|grep "netclient checkin -n all"|grep -v grep|awk '{print $1}') + if [ "${PID}" ];then + echo -e "netclient[${PID}] is running \n" + else + echo -e "netclient is not running \n" + fi +} diff --git a/netmaker-openwrt/scripts/build_ipk.sh b/netmaker-openwrt/scripts/build_ipk.sh new file mode 100644 index 000000000..1ee25de06 --- /dev/null +++ b/netmaker-openwrt/scripts/build_ipk.sh @@ -0,0 +1,177 @@ +#!/bin/bash + +# setting working directory +WORK_DIR="/home/user" + +# setting branch +if [ "${OPENWRT_BRANCH}" = "" ] +then + DEFAULT_OPENWRT_BRANCH="openwrt-21.02" +else + DEFAULT_OPENWRT_BRANCH="${OPENWRT_BRANCH}" +fi + +download_openwrt() { + cd ${WORK_DIR} + + # pull code + if [ ! -d "openwrt" ]; then + git clone https://git.openwrt.org/openwrt/openwrt.git + fi +} + +change_openwrt_branch() { + cd ${WORK_DIR}/openwrt + + if [ "${1}" = "" ] + then + echo "Building ${DEFAULT_OPENWRT_BRANCH}" + git checkout -B ${DEFAULT_OPENWRT_BRANCH} origin/${DEFAULT_OPENWRT_BRANCH} + else + echo "Building ${1}" + git checkout -B ${1} origin/${1} + fi +} + +init_openwrt_branch() { + cd ${WORK_DIR}/openwrt + + git stash + git pull --all + git pull --tags +} + +init_openwrt_link() { + cd ${WORK_DIR}/openwrt + + sudo chown 1000:1000 /src -R + + mkdir -p /src/dl + mkdir -p /src/staging_dir + mkdir -p /src/build_dir + mkdir -p /src/tmp + mkdir -p /src/bin + + ln -s /src/dl ${WORK_DIR}/openwrt/dl + ln -s /src/staging_dir ${WORK_DIR}/openwrt/staging_dir + ln -s /src/build_dir ${WORK_DIR}/openwrt/build_dir + ln -s /src/tmp ${WORK_DIR}/openwrt/tmp +} + +update_install_openwrt_feeds() { + cd ${WORK_DIR}/openwrt + + ./scripts/feeds update -a + ./scripts/feeds install -a +} + +openwrt_init_config() { + cd ${WORK_DIR}/openwrt + + echo "CONFIG_TARGET_x86=y" > ${WORK_DIR}/openwrt/.config + echo "CONFIG_TARGET_x86_64=y" >> ${WORK_DIR}/openwrt/.config +} + +openwrt_make_build_env() { + cd ${WORK_DIR}/openwrt + + make defconfig + make -j4 download + make -j4 tools/install + make -j4 toolchain/install +} + +openwrt_make() { + cd ${WORK_DIR}/openwrt + + make -j4 +} + +openwrt_install_netmaker_feeds() { + cd ${WORK_DIR}/openwrt + + echo "src-git netmaker http://github.com/sbilly/netmaker-openwrt.git" >> feeds.conf.default + + ./scripts/feeds update netmaker + ./scripts/feeds install netmaker +} + +openwrt_install_package_netmaker_config() { + cd ${WORK_DIR}/openwrt + + echo "CONFIG_FEED_netmaker=y" >> ${WORK_DIR}/openwrt/.config + echo "CONFIG_PACKAGE_netmaker=m" >> ${WORK_DIR}/openwrt/.config + echo "CONFIG_PACKAGE_netmaker-dev=m" >> ${WORK_DIR}/openwrt/.config +} + + +openwrt_patch_golang_host() { + cd ${WORK_DIR}/openwrt + echo "patching ${1}" + + if [ "${1}" = "openwrt-19.07" ] + then + sed -i 's/5fb43171046cf8784325e67913d55f88a683435071eef8e9da1aa8a1588fcf5d/2255eb3e4e824dd7d5fcdc2e7f84534371c186312e546fb1086a34c17752f431/g' ${WORK_DIR}/openwrt/feeds/packages/lang/golang/golang/Makefile + sed -i 's/1.13/1.17/g' ${WORK_DIR}/openwrt/feeds/packages/lang/golang/golang-version.mk + sed -i 's/15/2/g' ${WORK_DIR}/openwrt/feeds/packages/lang/golang/golang-version.mk + fi + + if [ "${1}" = "openwrt-18.06" ] + then + sed -i 's/6faf74046b5e24c2c0b46e78571cca4d65e1b89819da1089e53ea57539c63491/2255eb3e4e824dd7d5fcdc2e7f84534371c186312e546fb1086a34c17752f431/g' ${WORK_DIR}/openwrt/feeds/packages/lang/golang/golang/Makefile + sed -i 's/1.10/1.17/g' ${WORK_DIR}/openwrt/feeds/packages/lang/golang/golang-version.mk + sed -i 's/8/2/g' ${WORK_DIR}/openwrt/feeds/packages/lang/golang/golang-version.mk + fi +} + +openwrt_make_netmaker_package() { + cd ${WORK_DIR}/openwrt + + make defconfig + make toolchain/gcc/final/compile + make package/netmaker/clean + find ./ -type d | xargs -n1 sudo chmod 755 -R + make package/netmaker/compile V=s +} + + +openwrt_copy_pacage() { + echo ${1} + echo > /tmp/copy.sh + + cd ${WORK_DIR}/openwrt/bin/packages/x86_64/netmaker/ + + for ipk in ./*.ipk + do + if [ -f "$ipk" ] + then + echo ${ipk} | gawk -F".ipk" -v BRANCH=${1} '{ print "cp -rfv "$0" /src/bin/"$1"-"BRANCH".ipk" }' >> /tmp/copy.sh + fi + done + + /bin/bash /tmp/copy.sh +} + +download_openwrt + +change_openwrt_branch ${DEFAULT_OPENWRT_BRANCH} + +init_openwrt_branch + +init_openwrt_link + +openwrt_install_netmaker_feeds + +update_install_openwrt_feeds + +openwrt_init_config + +openwrt_install_package_netmaker_config + +openwrt_patch_golang_host ${DEFAULT_OPENWRT_BRANCH} + +openwrt_make_netmaker_package + +openwrt_copy_pacage ${DEFAULT_OPENWRT_BRANCH} + +ls -alF ${WORK_DIR}/openwrt/bin/ /src/bin diff --git a/omr-tracker/files/bin/omr-tracker b/omr-tracker/files/bin/omr-tracker index fe21c7473..72decbe1a 100755 --- a/omr-tracker/files/bin/omr-tracker +++ b/omr-tracker/files/bin/omr-tracker @@ -289,10 +289,10 @@ while true; do OMR_TRACKER_DEVICE_GATEWAY=$(ip -4 r list dev "$OMR_TRACKER_DEVICE" | grep kernel | awk '/proto kernel/ {print $1}' | tr -d "\n") fi fi - if [ "$OMR_TRACKER_INTERFACE_IPV6" = "1" ] || [ "$OMR_TRACKER_INTERFACE_IPV6" = "auto" ]; then + if [ "$OMR_TRACKER_IPV6" = "1" ] || [ "$OMR_TRACKER_IPV6" = "auto" ]; then #OMR_TRACKER_DEVICE_IP6=$(ip -6 -br addr ls dev "$OMR_TRACKER_DEVICE" | awk -F'[ /]+' '{print $3}') #if [ -z "$OMR_TRACKER_DEVICE_IP6" ]; then - OMR_TRACKER_DEVICE_IP6=$(ip -6 addr show dev "$OMR_TRACKER_DEVICE" | grep -v 'inet6 f' | grep -m 1 inet | awk '{print $2}' | cut -d'/' -s -f1) + OMR_TRACKER_DEVICE_IP6=$(ip -6 addr show dev "$OMR_TRACKER_DEVICE" | sort -r | grep -m 1 inet6 | awk '{print $2}' | cut -d'/' -s -f1) #fi if [ -z "$OMR_TRACKER_DEVICE_GATEWAY6" ]; then OMR_TRACKER_DEVICE_GATEWAY6=$(uci -q get "network.$OMR_TRACKER_INTERFACE.ip6gw") diff --git a/omr-tracker/files/etc/config/omr-tracker b/omr-tracker/files/etc/config/omr-tracker index f07da7b35..8d17aaa37 100755 --- a/omr-tracker/files/etc/config/omr-tracker +++ b/omr-tracker/files/etc/config/omr-tracker @@ -26,7 +26,7 @@ config defaults 'defaults' option type 'ping' option wait_test '0' option server_http_test '0' - option restart_down '0' + option restart_down '1' option mail_alert '0' config proxy 'proxy' diff --git a/openmptcprouter-full/Makefile b/openmptcprouter-full/Makefile index 9a41f555a..480b8c1b3 100755 --- a/openmptcprouter-full/Makefile +++ b/openmptcprouter-full/Makefile @@ -21,7 +21,7 @@ MY_DEPENDS := \ mc \ f2fs-tools \ openmptcprouter \ - dnsmasq-full \ + dnsmasq-full dnsmasq_full_ipset \ uhttpd \ uhttpd-mod-ubus \ curl \ @@ -46,7 +46,7 @@ MY_DEPENDS := \ luci-app-openvpn \ shadowsocks-libev-ss-server shadowsocks-libev-ss-tunnel \ omr-6in4 ip6tables-mod-nat luci-proto-ipv6 6to4 6in4 6rd ip6tables \ - speedtestcpp \ + !TARGET_mvebu:speedtestcpp \ iftop \ htop \ nano \ @@ -69,20 +69,19 @@ MY_DEPENDS := \ ca-bundle openssl-util \ dejavu-fonts-ttf-DejaVuSerif dejavu-fonts-ttf-DejaVuSerif-Bold dejavu-fonts-ttf-DejaVuSerif-Italic dejavu-fonts-ttf-DejaVuSerif-BoldItalic \ luci-app-snmpd \ - iputils-tracepath v2ray-plugin netcat simple-obfs \ + iputils-tracepath !TARGET_mvebu:v2ray-plugin netcat simple-obfs \ (TARGET_x86||TARGET_x86_64):kmod-iwlwifi (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl1000 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl100 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl105 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl135 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl2000 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl2030 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl3160 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl3168 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl5000 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl5150 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl6000g2 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl6000g2a (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl6000g2b (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl6050 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl7260 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl7265 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl7265d (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl8260c (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl8265 \ (TARGET_x86||TARGET_x86_64):kmod-e1000 (TARGET_x86||TARGET_x86_64):kmod-e1000e (TARGET_x86||TARGET_x86_64):kmod-igb (TARGET_x86||TARGET_x86_64):kmod-ne2k-pci (TARGET_x86||TARGET_x86_64):kmod-r8169 (TARGET_x86||TARGET_x86_64):kmod-8139too (TARGET_x86||TARGET_x86_64):kmod-bnx2 \ TARGET_mvebu:kmod-mwlwifi TARGET_mvebu:mwlwifi-firmware-88w8864 TARGET_mvebu:mwlwifi-firmware-88w8897 TARGET_mvebu:mwlwifi-firmware-88w8964 TARGET_mvebu:mwlwifi-firmware-88w8997 \ !TARGET_mvebu:kmod-usb-serial !TARGET_mvebu:kmod-usb-serial-option !TARGET_mvebu:kmod-usb-serial-wwan !TARGET_mvebu:usb-modeswitch !TARGET_mvebu:uqmi \ !TARGET_mvebu:umbim !TARGET_mvebu:kmod-mii !TARGET_mvebu:kmod-usb-net !TARGET_mvebu:kmod-usb-wdm !TARGET_mvebu:kmod-usb-net-qmi-wwan !TARGET_mvebu:kmod-usb-net-cdc-mbim !TARGET_mvebu:umbim \ !TARGET_mvebu:kmod-usb-net-huawei-cdc-ncm !TARGET_mvebu:kmod-usb-net-rndis !TARGET_mvebu:kmod-usb-net-cdc-ether !TARGET_mvebu:kmod-usb-net-ipheth !TARGET_mvebu:usbmuxd !TARGET_mvebu:libusbmuxd \ - kmod-rt2800-usb kmod-rtl8xxxu kmod-rtl8192cu kmod-net-rtl8192su kmod-rtl8812au-ct \ - !TARGET_mvebu:luci-proto-qmi wpad-basic kmod-mt7601u kmod-rtl8187 TARGET_r4s:kmod-r8168 (TARGET_x86||TARGET_x86_64):kmod-usb-net-rtl8152 \ - luci-app-mlvpn mlvpn 464xlat kmod-zram kmod-swconfig swconfig kmod-ipt-nat kmod-ipt-nat6 luci-app-https-dns-proxy kmod-tcp-nanqinlang (TARGET_x86_64||aarch64):kmod-tcp-bbr2 iptables-mod-ipopt igmpproxy ss iptraf-ng \ - luci-app-acl block-mount blockd fstools luci-app-shutdown libwebp luci-proto-gre tcptraceroute luci-proto-mbim kmod-rtl8xxxu kmod-ath9k-htc luci-app-ttyd luci-mod-dashboard (TARGET_x86||TARGET_x86_64):rtl8192eu-firmware kmod-usb2 libustream-openssl (TARGET_x86||TARGET_x86_64):kmod-ixgbevf (TARGET_x86||TARGET_x86_64):kmod-igbvf \ + !TARGET_mvebu:luci-proto-qmi wpad-basic kmod-mt7601u kmod-rtl8187 \ + !TARGET_mvebu:luci-app-mlvpn !TARGET_mvebu:mlvpn 464xlat kmod-zram kmod-swconfig swconfig kmod-ipt-nat kmod-ipt-nat6 kmod-ipt-fullconenat luci-app-https-dns-proxy kmod-tcp-nanqinlang (TARGET_x86_64||aarch64):kmod-tcp-bbr2 iptables-mod-ipopt igmpproxy ss iptraf-ng \ + luci-app-acl block-mount blockd fstools luci-app-shutdown libwebp luci-proto-gre tcptraceroute luci-proto-mbim kmod-rtl8xxxu kmod-ath9k-htc luci-app-ttyd luci-mod-dashboard kmod-usb2 libustream-openssl (TARGET_x86||TARGET_x86_64):kmod-ixgbevf (TARGET_x86||TARGET_x86_64):kmod-igbvf \ hwinfo (TARGET_x86||TARGET_x86_64):dmidecode luci-app-packet-capture kmod-bonding luci-proto-bonding luci-app-sysupgrade \ luci-theme-openwrt-2020 luci-proto-wireguard luci-app-wireguard kmod-crypto-lib-blake2s (TARGET_x86||TARGET_x86_64):kmod-r8125 TARGET_x86_64:kmod-atlantic \ - LINUX_5_15:mptcpd (TARGET_x86||TARGET_x86_64):kmod-igc kmod-mmc-spi kmod-macsec usbutils + (LINUX_5_15||LINUX_6_1):mptcpd (TARGET_x86||TARGET_x86_64):kmod-igc !TARGET_mvebu:kmod-mmc-spi kmod-macsec usbutils # !TARGET_mvebu:kmod-usb-net-smsc75xx # libnetfilter-conntrack ebtables ebtables-utils ip-full nstat \ diff --git a/openmptcprouter-zuixiao/Makefile b/openmptcprouter-zuixiao/Makefile index c5d329737..44e5c502e 100755 --- a/openmptcprouter-zuixiao/Makefile +++ b/openmptcprouter-zuixiao/Makefile @@ -1,5 +1,5 @@ # -# Copyright (C) 2018-2019 Ycarus (Yannick Chabanois) +# Copyright (C) suyunfan # # This is free software, licensed under the GNU General Public License v2. # See /LICENSE for more information. @@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=openmptcprouter-zuixiao -PKG_VERSION:=0.1 +PKG_VERSION:=0.14 PKG_RELEASE:=1 include $(INCLUDE_DIR)/package.mk @@ -17,7 +17,6 @@ MY_DEPENDS := \ mptcp \ unbound-daemon unbound-control unbound-anchor \ netifd \ - luci-app-dsvpn \ mc \ f2fs-tools \ openmptcprouter \ @@ -28,26 +27,24 @@ MY_DEPENDS := \ iperf3-ssl luci-app-iperf \ arptables \ bind-dig \ - libnetfilter-conntrack ebtables ebtables-utils ip-full \ + libnetfilter-conntrack ip-full nstat \ iptables-mod-iface iptables-mod-ipmark iptables-mod-hashlimit iptables-mod-condition iptables-mod-trace iptables-mod-conntrack-extra iptables-mod-account \ - kmod-nf-nat kmod-nf-nathelper kmod-nf-nathelper-extra iptables-mod-extra kmod-ledtrig-activity kmod-ledtrig-gpio kmod-ledtrig-oneshot kmod-ledtrig-transient kmod-ledtrig-disk kmod-ledtrig-mtd kmod-ledtrig-heartbeat kmod-ledtrig-backlight kmod-ledtrig-cpu kmod-ledtrig-panic kmod-ledtrig-netdev kmod-ledtrig-pattern kmod-ipt-led \ - iptables-mod-ipsec kmod-crypto-authenc kmod-ipsec kmod-ipsec4 kmod-ipsec6 kmod-ipt-ipsec \ + kmod-nf-nat kmod-nf-nathelper kmod-nf-nathelper-extra iptables-mod-extra conntrack kmod-ipt-offload \ + iptables-mod-ipsec kmod-crypto-authenc kmod-ipsec kmod-ipsec4 kmod-ipt-ipsec \ wireless-tools \ libiwinfo-lua \ ca-bundle ca-certificates \ - luci-mod-admin-full luci-app-firewall luci-app-glorytun-tcp luci-app-glorytun-udp luci-app-shadowsocks-libev luci-app-unbound luci-theme-openmptcprouter luci-theme-argon luci-theme-bootstrap luci-base \ + luci-mod-admin-full luci-app-firewall luci-app-glorytun-tcp luci-app-glorytun-udp luci-app-shadowsocks-libev luci-app-unbound luci-theme-openmptcprouter luci-theme-argon luci-base \ luci-app-omr-tracker luci-app-omr-dscp \ - luci-app-sqm sqm-scripts-extra luci-app-status luci-mod-status frpc rtty-nossl minicom irqbalance mtr \ + luci-app-sqm-autorate sqm-scripts-extra \ luci-app-vnstat2 omr-quota luci-app-omr-quota \ - luci-app-mptcp luci-app-openmptcprouter luci-app-omr-bypass luci-app-mail luci-app-upnp \ + luci-app-mptcp luci-app-openmptcprouter luci-app-upnp \ luci-app-wol luci-app-opkg \ luci-app-uhttpd \ luci-mod-rpc rpcd-mod-rpcsys rpcd-mod-file rpcd-mod-iwinfo \ luci-app-openvpn \ - macvlan \ shadowsocks-libev-ss-server shadowsocks-libev-ss-tunnel \ - omr-6in4 ip6tables-mod-nat luci-proto-ipv6 6to4 6in4 6rd ip6tables \ - speedtestc \ + speedtestcpp \ iftop \ htop \ nano \ @@ -63,28 +60,41 @@ MY_DEPENDS := \ rng-tools \ openvpn-openssl \ mmc-utils \ - libimobiledevice \ + libimobiledevice libimobiledevice-utils \ comgt \ kmod-random-core \ kmod-netem \ ca-bundle openssl-util \ dejavu-fonts-ttf-DejaVuSerif dejavu-fonts-ttf-DejaVuSerif-Bold dejavu-fonts-ttf-DejaVuSerif-Italic dejavu-fonts-ttf-DejaVuSerif-BoldItalic \ luci-app-snmpd \ - iputils-tracepath netcat adb-enablemodem simple-obfs \ - (TARGET_x86||TARGET_x86_64):kmod-iwlwifi (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl1000 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl100 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl105 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl135 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl2000 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl2030 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl3160 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl3168 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl5000 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl5150 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl6000g2 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl6000g2a (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl6000g2b (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl6050 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl7260 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl7265 (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl7265d (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl8260c (TARGET_x86||TARGET_x86_64):iwlwifi-firmware-iwl8265 \ - (TARGET_x86||TARGET_x86_64):kmod-e1000 (TARGET_x86||TARGET_x86_64):kmod-e1000e (TARGET_x86||TARGET_x86_64):kmod-igb (TARGET_x86||TARGET_x86_64):kmod-ne2k-pci (TARGET_x86||TARGET_x86_64):kmod-r8169 (TARGET_x86||TARGET_x86_64):kmod-8139too (TARGET_x86||TARGET_x86_64):kmod-bnx2 \ + iputils-tracepath v2ray-plugin netcat simple-obfs \ TARGET_mvebu:kmod-mwlwifi TARGET_mvebu:mwlwifi-firmware-88w8864 TARGET_mvebu:mwlwifi-firmware-88w8897 TARGET_mvebu:mwlwifi-firmware-88w8964 TARGET_mvebu:mwlwifi-firmware-88w8997 \ !TARGET_mvebu:kmod-usb-serial !TARGET_mvebu:kmod-usb-serial-option !TARGET_mvebu:kmod-usb-serial-wwan !TARGET_mvebu:usb-modeswitch !TARGET_mvebu:uqmi \ !TARGET_mvebu:umbim !TARGET_mvebu:kmod-mii !TARGET_mvebu:kmod-usb-net !TARGET_mvebu:kmod-usb-wdm !TARGET_mvebu:kmod-usb-net-qmi-wwan !TARGET_mvebu:kmod-usb-net-cdc-mbim !TARGET_mvebu:umbim \ - !TARGET_mvebu:kmod-usb-net-huawei-cdc-ncm !TARGET_mvebu:kmod-usb-net-rndis !TARGET_mvebu:kmod-usb-net-cdc-ether !TARGET_mvebu:kmod-usb-net-ipheth !TARGET_mvebu:usbmuxd \ - kmod-rt2800-usb kmod-rtl8xxxu kmod-rtl8192cu kmod-net-rtl8192su \ - !TARGET_mvebu:luci-proto-qmi wpad-basic kmod-mt7601u kmod-rtl8187 \ - luci-app-mlvpn mlvpn 464xlat !TARGET_mvebu:kmod-usb-net-smsc75xx kmod-zram kmod-swconfig swconfig kmod-ipt-nat kmod-ipt-nat6 luci-app-https-dns-proxy kmod-tcp-nanqinlang (TARGET_x86_64||aarch64):kmod-tcp-bbr2 iptables-mod-ipopt igmpproxy ss iptraf-ng \ - luci-app-acl block-mount blockd fstools luci-app-shutdown libwebp luci-proto-gre tcptraceroute luci-proto-mbim kmod-rtl8xxxu kmod-ath9k-htc luci-app-ttyd luci-mod-dashboard (TARGET_x86||TARGET_x86_64):rtl8192eu-firmware kmod-usb2 libustream-wolfssl (TARGET_x86||TARGET_x86_64):kmod-ixgbevf \ - hwinfo (TARGET_x86||TARGET_x86_64):dmidecode luci-app-packet-capture kmod-bonding luci-proto-bonding \ - luci-theme-openwrt-2020 luci-proto-wireguard luci-app-wireguard (TARGET_x86||TARGET_x86_64):kmod-r8125 + !TARGET_mvebu:kmod-usb-net-huawei-cdc-ncm !TARGET_mvebu:kmod-usb-net-rndis !TARGET_mvebu:kmod-usb-net-cdc-ether !TARGET_mvebu:kmod-usb-net-ipheth !TARGET_mvebu:usbmuxd !TARGET_mvebu:libusbmuxd \ + !TARGET_mvebu:luci-proto-qmi kmod-rtl8187 TARGET_r4s:kmod-r8168 (TARGET_x86||TARGET_x86_64):kmod-usb-net-rtl8152 \ + kmod-zram kmod-swconfig swconfig kmod-ipt-nat luci-app-https-dns-proxy kmod-tcp-nanqinlang (TARGET_x86_64||aarch64):kmod-tcp-bbr2 iptables-mod-ipopt igmpproxy ss iptraf-ng \ + luci-app-acl block-mount blockd fstools luci-app-shutdown libwebp luci-proto-gre tcptraceroute luci-proto-mbim luci-app-ttyd luci-mod-dashboard (TARGET_x86||TARGET_x86_64):rtl8192eu-firmware kmod-usb2 libustream-openssl (TARGET_x86||TARGET_x86_64):dmidecode kmod-bonding luci-proto-bonding \ + luci-theme-openwrt-2020 luci-proto-wireguard luci-app-wireguard kmod-crypto-lib-blake2s (TARGET_x86||TARGET_x86_64):kmod-r8125 TARGET_x86_64:kmod-atlantic \ + LINUX_5_15:mptcpd (TARGET_x86||TARGET_x86_64):kmod-igc kmod-mmc-spi kmod-macsec usbutils +# !TARGET_mvebu:kmod-usb-net-smsc75xx +# libnetfilter-conntrack ebtables ebtables-utils ip-full nstat \ -OMR_SUPPORTED_LANGS := zh-cn zh-tw oc +# luci-theme-bootstrap luci-theme-openwrt-2020 luci-theme-openwrt luci-app-status +# luci-proto-bonding luci-app-statistics luci-proto-gre +# softethervpn5-client softethervpn5-server luci-app-nginx-ha + +# luci-app-mlvpn ubond \ +# kmod-ath9k kmod-ath9k-htc + +# (TARGET_x86||TARGET_x86_64):open-vm-tools \ +# (!TARGET_mvebu&&!TARGET_mediatek):kmod-fb-tft-all \ +# lcd4linux-full +# kmod-spi-gpio-custom + +#OMR_SUPPORTED_LANGS := ar bg bn_BD ca en fi fr de el he hi hu it ja mr ms nb_NO pl pt_BR pt ro ru es sv uk vi zh_Hans zh_Hant +#OMR_SUPPORTED_LANGS := ar bg ca cs de en fi fr de el he hi hu it ja ko mr ms no oc pl pt-br pt ro ru es sk sv tr uk vi zh-cn zh-tw +OMR_SUPPORTED_LANGS := en zh-cn zh-tw oc define Package/$(PKG_NAME) SECTION:=OMR @@ -104,4 +114,4 @@ define Package/$(PKG_NAME)/install endef -$(eval $(call BuildPackage,$(PKG_NAME))) \ No newline at end of file +$(eval $(call BuildPackage,$(PKG_NAME))) diff --git a/openmptcprouter/files/bin/omr-test-speed b/openmptcprouter/files/bin/omr-test-speed index 431049e2c..ab8e1a929 100755 --- a/openmptcprouter/files/bin/omr-test-speed +++ b/openmptcprouter/files/bin/omr-test-speed @@ -19,9 +19,9 @@ bestping="9999" for pinghost in $HOSTLST; do domain=$(echo $pinghost | awk -F/ '{print $3}') if [ -z "$INTERFACE" ] || [ "$FORCEVPS" = true ]; then - ping=$(ping -c1 -w2 $domain | cut -d "/" -s -f5 | cut -d "." -f1 | tr -d '\n') + ping=$(ping -4 -c1 -w2 $domain | cut -d "/" -s -f5 | cut -d "." -f1 | tr -d '\n') else - ping=$(ping -c1 -w2 -I $INTERFACE -B $domain | cut -d "/" -s -f5 | cut -d "." -f1 | tr -d '\n') + ping=$(ping -4 -c1 -w2 -I $INTERFACE -B $domain | cut -d "/" -s -f5 | cut -d "." -f1 | tr -d '\n') fi echo "host: $domain - ping: $ping" if [ -n "$ping" ] && [ "$ping" -lt "$bestping" ]; then diff --git a/openmptcprouter/files/etc/firewall.gre-tunnel b/openmptcprouter/files/etc/firewall.gre-tunnel index f287a096b..59d161781 100755 --- a/openmptcprouter/files/etc/firewall.gre-tunnel +++ b/openmptcprouter/files/etc/firewall.gre-tunnel @@ -39,7 +39,7 @@ _setup_fw() { [ -n "$ifnames" ] && rule="$rule -i $(echo "${ifnames}" | sed 's/ /-i /g')" if [ -n "$rule" ] && [ -n "$lookup" ]; then $IPTABLESAVE --counters | grep -v "0x${lookup}" | $IPTABLERESTORE -w --counters - $IPTABLERESTORE -w --wait=60 --noflush <<-EOF + $IPTABLERESTORE --noflush <<-EOF *mangle -A omr-gre-tunnel ${rule} -j MARK --set-mark 0x${lookup} COMMIT @@ -48,7 +48,7 @@ _setup_fw() { } if [ -z "$($IPTABLESAVE | grep omr-gre-tunnel)" ]; then - $IPTABLERESTORE -w --wait=60 --noflush <<-EOF + $IPTABLERESTORE --noflush <<-EOF *mangle :omr-gre-tunnel - -I PREROUTING 1 -m addrtype ! --dst-type LOCAL -j omr-gre-tunnel diff --git a/openmptcprouter/files/etc/uci-defaults/1920-omr-network b/openmptcprouter/files/etc/uci-defaults/1920-omr-network new file mode 100755 index 000000000..0f1b7bc14 --- /dev/null +++ b/openmptcprouter/files/etc/uci-defaults/1920-omr-network @@ -0,0 +1,412 @@ +#!/bin/sh +. /lib/functions.sh + +_setup_macaddr() { + uci -q get "network.$1.macaddr" >/dev/null && return + uci -q set "network.$1.macaddr=$2" +} + +_setup_macvlan() { + uci -q get "network.$1_dev.ifname" >/dev/null && return + + # do not create macvlan for vlan + local _ifname + _ifname=$(uci -q get "network.$1.device") + case "$_ifname" in + eth*.*) return ;; + esac + + uci -q batch <<-EOF + set network.$1_dev=device + set network.$1_dev.name=$1 + set network.$1_dev.type=macvlan + set network.$1_dev.ifname=$_ifname + set network.$1_dev.mode='vepa' + set network.$1_dev.txqueuelen=1000 + set network.$1.device=$1 + set network.$1.type=macvlan + set network.$1.masterintf=$_ifname + EOF + _macaddr=$(uci -q get "network.$1.macaddr") + _setup_macaddr "$1_dev" "${_macaddr:-$(dd if=/dev/urandom bs=1024 count=1 2>/dev/null | md5sum | sed -e 's/^\(..\)\(..\)\(..\)\(..\)\(..\)\(..\).*$/\1:\2:\3:\4:\5:\6/' -e 's/^\(.\)[13579bdf]/\10/')}" +} + +#_setup_macvlan_update() { +# uci -q get "network.$1_dev.device" >/dev/null || return +# +# uci -q batch <<-EOF +# set macvlan.$1=macvlan +# set macvlan.$1.device=$_ifname +# commit macvlan +# EOF +#} + +_setup_mptcp_handover_to_on() { + if [ "$(uci -q get network.$1.multipath)" = "handover" ]; then + uci -q set network.$1.multipath=on + fi + if [ "$(uci -q get openmptcprouter.$1.multipath)" = "handover" ]; then + uci -q set openmptcprouter.$1.multipath=on + fi +} + +_setup_multipath_off() { + uci -q get "network.$1.multipath" >/dev/null && return + uci -q set "network.$1.multipath=off" +} + +_setup_wan_interface() { + uci -q batch <<-EOF + set network.$1=interface + set network.$1.device=$2 + set network.$1.proto=static + set network.$1.ip4table=wan + set network.$1.multipath=$3 + set network.$1.defaultroute=0 + set network.$1.addlatency=0 + set network.${1}_dev=device + set network.${1}_dev.name=$2 + set network.${1}_dev.txqueuelen=1000 + commit network + add_list firewall.@zone[1].network=$1 + commit firewall + EOF + [ -n "$4" ] && uci -q set network.$1.type=$4 +} + +config_load network +#config_foreach _setup_macvlan_update interface +config_foreach _setup_mptcp_handover_to_on interface + +if [ "$(uci -q show network.lan | grep multipath)" != "" ]; then + exit 0 +fi + +lanif="eth0" +if [ "$(grep rockchip /etc/os-release)" != "" ]; then + lanif="eth1" +elif [ -d /sys/class/net/lan0 -o -n "$(ip link | grep ' lan0')" ] && [ -d /sys/class/net/wan -o -n "$(ip link | grep ' wan@')" -o -n "$(ip link | grep ' wan:')" ]; then + lanif="wan" +elif [ -d /sys/class/net/lan1 -o -n "$(ip link | grep ' lan1')" ] && [ -d /sys/class/net/wan -o -n "$(ip link | grep ' wan@')" -o -n "$(ip link | grep ' wan:')" ]; then + lanif="wan" +elif [ -d /sys/class/net/lan ] || [ -n "$(ip link | grep ' lan')" ]; then + lanif="lan" +elif [ "$(swconfig list 2>&1 | grep switch0)" != "" ] && [ "$(cat /etc/board.json | jsonfilter -q -e '@.model.platform' | tr -d '\n')" = "RUTX" ]; then + lanif="eth1" + uci -q batch <<-EOF + set network.@switch_vlan[0]=switch_vlan + set network.@switch_vlan[0].device='switch0' + set network.@switch_vlan[0].vlan=1 + set network.@switch_vlan[0].ports='1t 2t 3t 4t 0t' + set network.@switch_vlan[1]=switch_vlan + set network.@switch_vlan[1].device='switch0' + set network.@switch_vlan[1].vlan=2 + set network.@switch_vlan[1].ports='0 5' + add network switch_vlan + set network.@switch_vlan[2].device='switch0' + set network.@switch_vlan[2].vlan=3 + set network.@switch_vlan[2].ports='0t 1' + add network switch_vlan + set network.@switch_vlan[3].device='switch0' + set network.@switch_vlan[3].vlan=4 + set network.@switch_vlan[3].ports='0t 2' + add network switch_vlan + set network.@switch_vlan[4].device='switch0' + set network.@switch_vlan[4].vlan=5 + set network.@switch_vlan[4].ports='0t 3' + add network switch_vlan + set network.@switch_vlan[5].device='switch0' + set network.@switch_vlan[5].vlan=6 + set network.@switch_vlan[5].ports='0t 4' + EOF +elif [ "$(swconfig list 2>&1 | grep switch0)" != "" ] && [ -d '/sys/class/net/eth1.5' ]; then + lanif="eth1.5" + uci -q batch <<-EOF + set network.@switch_vlan[0]=switch_vlan + set network.@switch_vlan[0].device='switch0' + set network.@switch_vlan[0].vlan=1 + set network.@switch_vlan[0].vid=1 + set network.@switch_vlan[0].ports='3 5t' + add network switch_vlan + set network.@switch_vlan[1].device='switch0' + set network.@switch_vlan[1].vlan=2 + set network.@switch_vlan[1].vid=2 + set network.@switch_vlan[1].ports='2 5t' + add network switch_vlan + set network.@switch_vlan[2].device='switch0' + set network.@switch_vlan[2].vlan=3 + set network.@switch_vlan[2].vid=3 + set network.@switch_vlan[2].ports='1 5t' + add network switch_vlan + set network.@switch_vlan[3].device='switch0' + set network.@switch_vlan[3].vlan=4 + set network.@switch_vlan[3].vid=4 + set network.@switch_vlan[3].ports='0 5t' + add network switch_vlan + set network.@switch_vlan[4].device='switch0' + set network.@switch_vlan[4].vlan=5 + set network.@switch_vlan[4].vid=5 + set network.@switch_vlan[4].ports='4 6t' + EOF +elif [ "$(swconfig list 2>&1 | grep switch0)" != "" ] && [ -d /sys/class/net/eth1 ] && [ "$(grep ipq806x /etc/os-release)" != "" ]; then + lanif="eth0.2" +elif [ "$(swconfig list 2>&1 | grep switch0)" != "" ] && [ -d /sys/class/net/eth1 ]; then + lanif="eth1" +elif [ ! -d /sys/class/net/eth1 ] && [ -d /sys/class/net/eth0 ]; then + lanif="eth0" +fi +uci -q batch <<-EOF +delete network.lan.type +set network.lan=interface +set network.lan.proto=static +set network.lan.ipaddr=192.168.100.1 +set network.lan.netmask=255.255.255.0 +set network.lan.device=${lanif} +set network.lan.ifname=${lanif} +set network.lan.metric=2048 +set network.lan.ipv6=0 +set network.lan.delegate=0 +set network.lan.addlatency=0 +set network.lan.txqueuelen=2000 +set dhcp.lan.dhcpv4='server' +EOF + +uci -q batch <<-EOF +delete network.none +delete network.wan +delete network.if6rd +reorder network.loopback=0 +reorder network.globals=1 +reorder network.lan=2 +set network.globals.multipath=enable +EOF + +# Set the ip rule for the lan with a pref of 100 +uci -q show network.lan_rule >/dev/null || \ + uci -q batch <<-EOF + set network.lan_rule=rule + set network.lan_rule.lookup=lan + set network.lan_rule.priority=100 + EOF + +if [ "$(uci -q get network.vpn0.proto)" = "none" ]; then + uci -q delete network.vpn0 +fi + +config_load network +config_foreach _setup_multipath_off interface + +# Add the lan as a named routing table +if ! grep -s -q "lan" /etc/iproute2/rt_tables; then + echo "50 lan" >> /etc/iproute2/rt_tables +fi +uci -q set network.lan.ip4table='lan' + +#uci -q set "network.lan.ip6assign=64" + +# Create WAN interfaces +if [ "$(uci -q show network.wan1 | grep multipath)" = "" ] && [ -z "$(uci -q get network.wan1.multipath)" ]; then + if [ "$(grep ipq806x /etc/os-release)" != "" ]; then + _setup_wan_interface wan1 eth1.1 master + _setup_wan_interface wan2 eth1.2 on + _setup_wan_interface wan3 eth1.3 on + _setup_wan_interface wan4 eth1.4 on + elif [ "$(grep rockchip /etc/os-release)" != "" ]; then + _setup_wan_interface wan1 eth0 master macvlan + _setup_wan_interface wan2 eth0 on macvlan + _setup_macvlan wan1 + _setup_macvlan wan2 + elif [ "$(cat /etc/board.json | jsonfilter -q -e '@.model.platform' | tr -d '\n')" = "RUTX" ]; then + _setup_wan_interface wan1 eth0.3 master + _setup_wan_interface wan2 eth0.4 on + _setup_wan_interface wan3 eth0.5 on + _setup_wan_interface wan4 eth0.6 on + elif [ "$(swconfig list 2>&1 | grep switch0)" != "" ]; then + _setup_wan_interface wan1 eth0.1 master + _setup_wan_interface wan2 eth0.2 on + _setup_wan_interface wan3 eth0.3 on + _setup_wan_interface wan4 eth0.4 on + elif [ -d /sys/class/net/wan ] || [ -n "$(ip link | grep ' wan:')" ] || [ -n "$(ip link | grep ' wan@')" ]; then + if [ -d /sys/class/net/lan0 -o -n "$(ip link | grep ' lan0')" ] && [ -d /sys/class/net/lan1 -o -n "$(ip link | grep ' lan1')" ]; then + _setup_wan_interface wan1 lan0 master + _setup_wan_interface wan2 lan1 on + + _macaddr=$(uci -q get "network.lan0.macaddr") + _setup_macaddr "wan1" "${_macaddr:-$(dd if=/dev/urandom bs=1024 count=1 2>/dev/null | md5sum | sed -e 's/^\(..\)\(..\)\(..\)\(..\)\(..\)\(..\).*$/\1:\2:\3:\4:\5:\6/' -e 's/^\(.\)[13579bdf]/\10/')}" + _macaddr=$(uci -q get "network.lan1.macaddr") + _setup_macaddr "wan2" "${_macaddr:-$(dd if=/dev/urandom bs=1024 count=1 2>/dev/null | md5sum | sed -e 's/^\(..\)\(..\)\(..\)\(..\)\(..\)\(..\).*$/\1:\2:\3:\4:\5:\6/' -e 's/^\(.\)[13579bdf]/\10/')}" + if [ -d /sys/class/net/lan2 ] || [ -n "$(ip link | grep ' lan2')" ]; then + _setup_wan_interface wan3 lan2 on + _macaddr=$(uci -q get "network.lan2.macaddr") + _setup_macaddr "wan3" "${_macaddr:-$(dd if=/dev/urandom bs=1024 count=1 2>/dev/null | md5sum | sed -e 's/^\(..\)\(..\)\(..\)\(..\)\(..\)\(..\).*$/\1:\2:\3:\4:\5:\6/' -e 's/^\(.\)[13579bdf]/\10/')}" + if [ -d /sys/class/net/lan3 ] || [ -n "$(ip link | grep ' lan3')" ]; then + _setup_wan_interface wan4 lan3 on + _macaddr=$(uci -q get "network.lan3.macaddr") + _setup_macaddr "wan4" "${_macaddr:-$(dd if=/dev/urandom bs=1024 count=1 2>/dev/null | md5sum | sed -e 's/^\(..\)\(..\)\(..\)\(..\)\(..\)\(..\).*$/\1:\2:\3:\4:\5:\6/' -e 's/^\(.\)[13579bdf]/\10/')}" + fi + fi + elif [ -d /sys/class/net/lan1 -o -n "$(ip link | grep ' lan1')" ] && [ -d /sys/class/net/lan2 -o -n "$(ip link | grep ' lan2')" ]; then + _setup_wan_interface wan1 lan1 master + _setup_wan_interface wan2 lan2 on + + _macaddr=$(uci -q get "network.lan1.macaddr") + _setup_macaddr "wan1" "${_macaddr:-$(dd if=/dev/urandom bs=1024 count=1 2>/dev/null | md5sum | sed -e 's/^\(..\)\(..\)\(..\)\(..\)\(..\)\(..\).*$/\1:\2:\3:\4:\5:\6/' -e 's/^\(.\)[13579bdf]/\10/')}" + _macaddr=$(uci -q get "network.lan2.macaddr") + _setup_macaddr "wan2" "${_macaddr:-$(dd if=/dev/urandom bs=1024 count=1 2>/dev/null | md5sum | sed -e 's/^\(..\)\(..\)\(..\)\(..\)\(..\)\(..\).*$/\1:\2:\3:\4:\5:\6/' -e 's/^\(.\)[13579bdf]/\10/')}" + if [ -d /sys/class/net/lan3 ] || [ -n "$(ip link | grep ' lan3')" ]; then + _setup_wan_interface wan3 lan3 on + _macaddr=$(uci -q get "network.lan3.macaddr") + _setup_macaddr "wan3" "${_macaddr:-$(dd if=/dev/urandom bs=1024 count=1 2>/dev/null | md5sum | sed -e 's/^\(..\)\(..\)\(..\)\(..\)\(..\)\(..\).*$/\1:\2:\3:\4:\5:\6/' -e 's/^\(.\)[13579bdf]/\10/')}" + if [ -d /sys/class/net/lan4 ] || [ -n "$(ip link | grep ' lan4')" ]; then + _setup_wan_interface wan4 lan4 on + _macaddr=$(uci -q get "network.lan4.macaddr") + _setup_macaddr "wan4" "${_macaddr:-$(dd if=/dev/urandom bs=1024 count=1 2>/dev/null | md5sum | sed -e 's/^\(..\)\(..\)\(..\)\(..\)\(..\)\(..\).*$/\1:\2:\3:\4:\5:\6/' -e 's/^\(.\)[13579bdf]/\10/')}" + fi + fi + else + _setup_wan_interface wan1 wan master macvlan + _setup_wan_interface wan2 wan on macvlan + _setup_macvlan wan1 + _setup_macvlan wan2 + fi + elif [ -d /sys/class/net/wan1 ] || [ -n "$(ip link | grep ' wan1')" ]; then + if [ -d /sys/class/net/wan2 ] || [ -n "$(ip link | grep ' wan2')" ]; then + _setup_wan_interface wan1 wan1 master + _setup_wan_interface wan2 wan2 on + + _macaddr=$(uci -q get "network.wan1.macaddr") + _setup_macaddr "wan1" "${_macaddr:-$(dd if=/dev/urandom bs=1024 count=1 2>/dev/null | md5sum | sed -e 's/^\(..\)\(..\)\(..\)\(..\)\(..\)\(..\).*$/\1:\2:\3:\4:\5:\6/' -e 's/^\(.\)[13579bdf]/\10/')}" + _macaddr=$(uci -q get "network.wan2.macaddr") + _setup_macaddr "wan2" "${_macaddr:-$(dd if=/dev/urandom bs=1024 count=1 2>/dev/null | md5sum | sed -e 's/^\(..\)\(..\)\(..\)\(..\)\(..\)\(..\).*$/\1:\2:\3:\4:\5:\6/' -e 's/^\(.\)[13579bdf]/\10/')}" + + if [ -d /sys/class/net/wan3 ] || [ -n "$(ip link | grep ' wan3')" ]; then + _setup_wan_interface wan3 wan3 on + _macaddr=$(uci -q get "network.wan3.macaddr") + _setup_macaddr "wan3" "${_macaddr:-$(dd if=/dev/urandom bs=1024 count=1 2>/dev/null | md5sum | sed -e 's/^\(..\)\(..\)\(..\)\(..\)\(..\)\(..\).*$/\1:\2:\3:\4:\5:\6/' -e 's/^\(.\)[13579bdf]/\10/')}" + if [ -d /sys/class/net/wan4 ] || [ -n "$(ip link | grep ' wan4')" ]; then + _setup_wan_interface wan4 wan4 on + _macaddr=$(uci -q get "network.wan4.macaddr") + _setup_macaddr "wan4" "${_macaddr:-$(dd if=/dev/urandom bs=1024 count=1 2>/dev/null | md5sum | sed -e 's/^\(..\)\(..\)\(..\)\(..\)\(..\)\(..\).*$/\1:\2:\3:\4:\5:\6/' -e 's/^\(.\)[13579bdf]/\10/')}" + fi + fi + else + _setup_wan_interface wan1 wan1 master macvlan + _setup_wan_interface wan2 wan1 on macvlan + _setup_macvlan wan1 + _setup_macvlan wan2 + fi + elif [ -d /sys/class/net/eth1 ] || [ -n "$(ip link | grep ' eth1:')" ]; then + if [ -d /sys/class/net/eth2 ] || [ -n "$(ip link | grep ' eth2:')" ]; then + _setup_wan_interface wan1 eth1 master + _setup_wan_interface wan2 eth2 on + if [ -d /sys/class/net/eth3 ] || [ -n "$(ip link | grep ' eth3:')" ]; then + _setup_wan_interface wan3 eth3 on + fi + if [ -d /sys/class/net/eth4 ] || [ -n "$(ip link | grep ' eth4:')" ]; then + _setup_wan_interface wan4 eth4 on + fi + if [ -d /sys/class/net/eth5 ] || [ -n "$(ip link | grep ' eth5:')" ]; then + _setup_wan_interface wan5 eth5 on + fi + if [ -d /sys/class/net/eth6 ] || [ -n "$(ip link | grep ' eth6:')" ]; then + _setup_wan_interface wan6 eth6 on + fi + if [ -d /sys/class/net/eth7 ] || [ -n "$(ip link | grep ' eth7:')" ]; then + _setup_wan_interface wan7 eth7 on + fi + if [ -d /sys/class/net/eth8 ] || [ -n "$(ip link | grep ' eth8:')" ]; then + _setup_wan_interface wan8 eth8 on + fi + else + _setup_wan_interface wan1 eth1 master macvlan + _setup_wan_interface wan2 eth1 on macvlan + _setup_macvlan wan1 + _setup_macvlan wan2 + fi + elif [ -d /sys/class/net/eth0.1 ] && [ -d /sys/class/net/eth0.2 ]; then + _setup_wan_interface wan1 eth0.1 master + _setup_wan_interface wan2 eth0.2 on + else + _setup_wan_interface wan1 eth0 master macvlan + _setup_wan_interface wan2 eth0 on macvlan + _setup_macvlan wan1 + _setup_macvlan wan2 + fi + #uci -q batch <<-EOF + #add network route6 + #set network.@route6[-1].interface='lan' + #set network.@route6[-1].target='::/0' + #EOF +fi + +# Replace omrip to oip in config for old config +sed -i 's/omrip/oip/g' /etc/config/* + +# Fix config from ifname to device for loopback +uci -q delete network.loopback.ifname +uci -q set network.loopback.device='lo' + +local board=$(board_name) +if [ "$board" = "teltonika,rutx" ] && [ -f /sbin/mnf_info ]; then + # Same part for RUTX12 and RUTX11, maybe other RUTX ? + uci -q batch <<-EOF + set network.modem1=interface + set network.modem1.proto='modemmanager' + set network.modem1.apn='' + set network.modem1.auth='none' + set network.modem1.iptype='ipv4v6' + set network.modem1.addlatency='0' + set network.modem1.force_link='1' + set network.modem1.device='/sys/devices/platform/soc/60f8800.usb2/6000000.dwc3/xhci-hcd.1.auto/usb3/3-1' + set network.modem1.peerdns='0' + set network.modem1.multipath='on' + set network.modem1.defaultroute='0' + commit network + add_list firewall.@zone[1].network='modem1' + commit firewall + EOF + if [ "$(mnf_info -n)" = "RUTX1200XXXX" ]; then + uci -q batch <<-EOF + set network.modem2=interface + set network.modem2.proto='modemmanager' + set network.modem2.apn='' + set network.modem2.auth='none' + set network.modem2.iptype='ipv4v6' + set network.modem2.addlatency='0' + set network.modem2.force_link='1' + set network.modem2.device='/sys/devices/platform/soc/8af8800.usb3/8a00000.dwc3/xhci-hcd.0.auto/usb1/1-1/1-1.2' + set network.modem2.peerdns='0' + set network.modem2.multipath='on' + set network.modem2.defaultroute='0' + set network.modem2.ip4table=wan + commit network + add_list firewall.@zone[1].network='modem2' + commit firewall + EOF + fi + uci -q batch <<-EOF + set network.wifi24=interface + set network.wifi24.proto='none' + set network.wifi5=interface + set network.wifi5.proto='none' + commit network + set wireless.radio0.cell_density='0' + set wireless.default_radio0.network='wifi24' + set wireless.radio1.cell_density='0' + set wireless.default_radio1.network='wifi5' + commit wireless + add ucitrack led + set ucitrack.@led[-1].init=led + del_list ucitrack.@firewall[0].affects=led + add_list ucitrack.@firewall[0].affects=led + commit ucitrack + EOF + +fi + + +uci -q commit macvlan +uci -q commit network +uci -q commit dhcp +rm -f /tmp/luci-indexcache +exit 0 diff --git a/openmptcprouter/files/etc/uci-defaults/1980-omr-firewall b/openmptcprouter/files/etc/uci-defaults/1980-omr-firewall index afde68d82..5bab51455 100755 --- a/openmptcprouter/files/etc/uci-defaults/1980-omr-firewall +++ b/openmptcprouter/files/etc/uci-defaults/1980-omr-firewall @@ -236,9 +236,12 @@ for intf in $allintf; do uci -q add_list firewall.zone_vpn.network="${intf}" done + uci -q batch <<-EOF >/dev/null set firewall.zone_lan.mtu_fix='1' set firewall.zone_vpn.mtu_fix='1' +EOF +uci -q batch <<-EOF >/dev/null set firewall.@include[0].reload='1' commit firewall EOF @@ -255,8 +258,8 @@ if [ "$(uci -q get openmptcprouter.settings.sipalg)" = "0" ]; then set firewall.zone_vpn.auto_helper='0' commit firewall EOF - rmmod nf_nat_sip 2>&1 >/dev/null - rmmod nf_conntrack_sip 2>&1 >/dev/null + [ -n "$(lsmod | grep nf_nat_sip)" ] && rmmod nf_nat_sip 2>&1 >/dev/null + [ -n "$(lsmod | grep nf_conntrack_sip)" ] && rmmod nf_conntrack_sip 2>&1 >/dev/null fi rm -f /tmp/luci-indexcache diff --git a/openmptcprouter/files/etc/uci-defaults/1990-omr-tracker b/openmptcprouter/files/etc/uci-defaults/1990-omr-tracker index 882e4b0a7..88e8394df 100755 --- a/openmptcprouter/files/etc/uci-defaults/1990-omr-tracker +++ b/openmptcprouter/files/etc/uci-defaults/1990-omr-tracker @@ -15,6 +15,10 @@ if [ "$(uci -q get omr-tracker.omrvpn)" = "" ]; then set omr-tracker.omrvpn.restart_down=0 add_list omr-tracker.omrvpn.hosts='4.2.2.1' add_list omr-tracker.omrvpn.hosts='8.8.8.8' + add_list omr-tracker.omrvpn.hosts='223.5.5.5' + add_list omr-tracker.omrvpn.hosts='223.6.6.6' + add_list omr-tracker.omrvpn.hosts='114.114.114.114' + add_list omr-tracker.omrvpn.hosts='180.76.76.76' commit omr-tracker EOF fi diff --git a/r8125/patches/010-config.patch b/r8125/patches/010-config.patch new file mode 100644 index 000000000..e7934c01c --- /dev/null +++ b/r8125/patches/010-config.patch @@ -0,0 +1,22 @@ +--- a/src/Makefile ++++ b/src/Makefile +@@ -35,16 +35,16 @@ ENABLE_REALWOW_SUPPORT = n + ENABLE_DASH_SUPPORT = n + ENABLE_DASH_PRINTER_SUPPORT = n + CONFIG_DOWN_SPEED_100 = n +-CONFIG_ASPM = y ++CONFIG_ASPM = n + ENABLE_S5WOL = y + ENABLE_S5_KEEP_CURR_MAC = n + ENABLE_EEE = y + ENABLE_S0_MAGIC_PACKET = n + ENABLE_TX_NO_CLOSE = y +-ENABLE_MULTIPLE_TX_QUEUE = n ++ENABLE_MULTIPLE_TX_QUEUE = y + ENABLE_PTP_SUPPORT = n + ENABLE_PTP_MASTER_MODE = n +-ENABLE_RSS_SUPPORT = n ++ENABLE_RSS_SUPPORT = y + ENABLE_LIB_SUPPORT = n + ENABLE_USE_FIRMWARE_FILE = n + DISABLE_PM_SUPPORT = n diff --git a/r8125/patches/020-5.19-support.patch b/r8125/patches/020-5.19-support.patch new file mode 100644 index 000000000..481c7739e --- /dev/null +++ b/r8125/patches/020-5.19-support.patch @@ -0,0 +1,18 @@ +--- a/src/r8125_n.c ++++ b/src/r8125_n.c +@@ -116,6 +116,15 @@ + #define FIRMWARE_8168FP_3 "rtl_nic/rtl8168fp-3.fw" + #define FIRMWARE_8168FP_4 "rtl_nic/rtl8168fp-4.fw" + ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) ++static inline void netif_set_gso_max_size(struct net_device *dev, ++ unsigned int size) ++{ ++ /* dev->gso_max_size is read locklessly from sk_setup_caps() */ ++ WRITE_ONCE(dev->gso_max_size, size); ++} ++#endif ++ + /* Maximum number of multicast addresses to filter (vs. Rx-all-multicast). + The RTL chips use a 64 element hash table based on the Ethernet CRC. */ + static const int multicast_filter_limit = 32; diff --git a/r8125/patches/021-6.1-suppot.patch b/r8125/patches/021-6.1-suppot.patch new file mode 100644 index 000000000..c460ce33b --- /dev/null +++ b/r8125/patches/021-6.1-suppot.patch @@ -0,0 +1,14 @@ +--- a/src/r8125.h ++++ b/src/r8125.h +@@ -633,7 +633,11 @@ + typedef struct napi_struct *napi_ptr; + typedef int napi_budget; + ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) ++#define RTL_NAPI_CONFIG(ndev, priv, function, weight) netif_napi_add_weight(ndev, &priv->napi, function, weight) ++#else + #define RTL_NAPI_CONFIG(ndev, priv, function, weight) netif_napi_add(ndev, &priv->napi, function, weight) ++#endif + #define RTL_NAPI_QUOTA(budget, ndev) min(budget, budget) + #define RTL_GET_PRIV(stuct_ptr, priv_struct) container_of(stuct_ptr, priv_struct, stuct_ptr) + #define RTL_GET_NETDEV(priv_ptr) struct net_device *dev = priv_ptr->dev; diff --git a/r8125/patches/030-add-LED-configuration-from-OF.patch b/r8125/patches/030-add-LED-configuration-from-OF.patch new file mode 100644 index 000000000..5f6f1d29b --- /dev/null +++ b/r8125/patches/030-add-LED-configuration-from-OF.patch @@ -0,0 +1,43 @@ +--- a/src/r8125_n.c ++++ b/src/r8125_n.c +@@ -43,6 +43,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -10407,6 +10408,23 @@ rtl8125_setup_mqs_reg(struct rtl8125_private *tp) + } + } + ++static int ++rtl8125_led_configuration(struct rtl8125_private *tp) ++{ ++ u32 led_data; ++ int ret; ++ ++ ret = of_property_read_u32(tp->pci_dev->dev.of_node, ++ "realtek,led-data", &led_data); ++ ++ if (ret) ++ return ret; ++ ++ RTL_W16(tp, CustomLED, led_data); ++ ++ return 0; ++} ++ + static void + rtl8125_init_software_variable(struct net_device *dev) + { +@@ -10838,6 +10856,8 @@ rtl8125_init_software_variable(struct net_device *dev) + if (tp->InitRxDescType == RX_DESC_RING_TYPE_3) + tp->rtl8125_rx_config |= EnableRxDescV3; + ++ rtl8125_led_configuration(tp); ++ + tp->NicCustLedValue = RTL_R16(tp, CustomLED); + + tp->wol_opts = rtl8125_get_hw_wol(tp); diff --git a/r8152/Makefile b/r8152/Makefile index 9e199e273..5921bdcc7 100755 --- a/r8152/Makefile +++ b/r8152/Makefile @@ -7,12 +7,12 @@ include $(TOPDIR)/rules.mk include $(INCLUDE_DIR)/kernel.mk PKG_NAME:=r8152 -PKG_VERSION:=2.15.20211119 -PKG_RELEASE:=1 +PKG_VERSION:=2.16.3.20220914 +PKG_RELEASE:=3 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/wget/realtek-r8152-linux/tar.gz/v$(PKG_VERSION)? -PKG_HASH:=b7926db3b4ca71d453ac1cf875d7a8ab409ece108edc6913e8bc1c0c3b99179d +PKG_HASH:=61ed7af34c8882c6028ddd1a27bb78fb5bfba41211f84dd7a06e4dc84dbe9a9a PKG_BUILD_DIR:=$(KERNEL_BUILD_DIR)/realtek-$(PKG_NAME)-linux-$(PKG_VERSION) @@ -23,13 +23,13 @@ PKG_MAINTAINER:=Tianling Shen include $(INCLUDE_DIR)/package.mk define KernelPackage/usb-net-rtl8152-vendor + VERSION:=$(LINUX_VERSION)+$(PKG_VERSION)-$(BOARD)-$(PKG_RELEASE) TITLE:=Kernel module for USB-to-Ethernet Realtek convertors SUBMENU:=USB Support - VERSION:=$(LINUX_VERSION)+$(PKG_VERSION)-$(BOARD)-$(PKG_RELEASE) DEPENDS:=+kmod-usb-net - CONFLICTS:=kmod-usb-net-rtl8152 - FILES:= $(PKG_BUILD_DIR)/r8152.ko + FILES:=$(PKG_BUILD_DIR)/r8152.ko AUTOLOAD:=$(call AutoProbe,r8152) + CONFLICTS:=kmod-usb-net-rtl8152 endef define KernelPackage/usb-net-rtl8152-vendor/description diff --git a/r8152/patches/010-5.19-support.patch b/r8152/patches/010-5.19-support.patch new file mode 100644 index 000000000..944e5bfcc --- /dev/null +++ b/r8152/patches/010-5.19-support.patch @@ -0,0 +1,19 @@ +--- a/r8152.c ++++ b/r8152.c +@@ -1026,6 +1026,16 @@ + #define RTL_ADVERTISED_1000_FULL BIT(5) + #define RTL_ADVERTISED_2500_FULL BIT(6) + ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) ++static inline void netif_set_gso_max_size(struct net_device *dev, ++ unsigned int size) ++{ ++ /* dev->gso_max_size is read locklessly from sk_setup_caps() */ ++ WRITE_ONCE(dev->gso_max_size, size); ++} ++#endif ++ ++ + /* Maximum number of multicast addresses to filter (vs. Rx-all-multicast). + * The RTL chips use a 64 element hash table based on the Ethernet CRC. + */ diff --git a/r8152/patches/020-6.1-support.patch b/r8152/patches/020-6.1-support.patch new file mode 100644 index 000000000..756aba51f --- /dev/null +++ b/r8152/patches/020-6.1-support.patch @@ -0,0 +1,38 @@ +--- a/compatibility.h ++++ b/compatibility.h +@@ -237,9 +237,15 @@ + #define napi_disable(napi_ptr) netif_poll_disable(container_of(napi_ptr, struct r8152, napi)->netdev) + #define napi_schedule(napi_ptr) netif_rx_schedule(container_of(napi_ptr, struct r8152, napi)->netdev) + #define napi_complete(napi_ptr) netif_rx_complete(container_of(napi_ptr, struct r8152, napi)->netdev) ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) ++ #define netif_napi_add_weight(ndev, napi_ptr, function, weight_t) \ ++ ndev->poll = function; \ ++ ndev->weight = weight_t; ++#else + #define netif_napi_add(ndev, napi_ptr, function, weight_t) \ + ndev->poll = function; \ + ndev->weight = weight_t; ++#endif + typedef unsigned long uintptr_t; + #define DMA_BIT_MASK(value) \ + (value < 64 ? ((1ULL << value) - 1) : 0xFFFFFFFFFFFFFFFFULL) +--- a/r8152.c ++++ b/r8152.c +@@ -20718,10 +20718,17 @@ + + usb_set_intfdata(intf, tp); + ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) ++ if (tp->support_2500full) ++ netif_napi_add_weight(netdev, &tp->napi, r8152_poll, 256); ++ else ++ netif_napi_add_weight(netdev, &tp->napi, r8152_poll, 64); ++#else + if (tp->support_2500full) + netif_napi_add(netdev, &tp->napi, r8152_poll, 256); + else + netif_napi_add(netdev, &tp->napi, r8152_poll, 64); ++#endif + + ret = register_netdev(netdev); + if (ret != 0) { diff --git a/r8168/Makefile b/r8168/Makefile index 6ec31651c..8f810c518 100755 --- a/r8168/Makefile +++ b/r8168/Makefile @@ -7,19 +7,16 @@ include $(TOPDIR)/rules.mk include $(INCLUDE_DIR)/kernel.mk PKG_NAME:=r8168 -PKG_VERSION:=8.050.03 -PKG_RELEASE:=$(AUTORELEAE) +PKG_VERSION:=8.051.02 +PKG_RELEASE:=1 PKG_SOURCE_PROTO:=git PKG_SOURCE_URL:=https://github.com/BROBIRD/openwrt-r8168.git -PKG_SOURCE_DATE:=2022-06-15 -PKG_SOURCE_VERSION:=ddfaceacd1b7ed2857fb995642a8ffb1fc37e989 -#PKG_MIRROR_HASH:=e4632c10d460f005eff76da8a183d7ff0c8819b0d099872589b7b06a9b8d9952 +PKG_SOURCE_VERSION:=4f6cfe1ca12fb772deed57f1d2d1062af041ad07 +PKG_MIRROR_HASH:=6b149f5eb3b9e1dc50867a694984d253aa58d97dd5fbab30eb405d2d7b2be587 PKG_BUILD_DIR:=$(KERNEL_BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION) -MAKE_PATH:=src - include $(INCLUDE_DIR)/package.mk define KernelPackage/r8168 @@ -35,20 +32,8 @@ define Package/r8168/description This package contains a driver for Realtek r8168 chipsets. endef -R8168_MAKEOPTS= -C $(PKG_BUILD_DIR)/src \ - PATH="$(TARGET_PATH)" \ - ARCH="$(LINUX_KARCH)" \ - CROSS_COMPILE="$(TARGET_CROSS)" \ - TARGET="$(HAL_TARGET)" \ - TOOLPREFIX="$(KERNEL_CROSS)" \ - TOOLPATH="$(KERNEL_CROSS)" \ - KERNELPATH="$(LINUX_DIR)" \ - KERNELDIR="$(LINUX_DIR)" \ - LDOPTS=" " \ - DOMULTI=1 - define Build/Compile - $(MAKE) $(R8168_MAKEOPTS) modules + +$(KERNEL_MAKE) M=$(PKG_BUILD_DIR)/src modules endef $(eval $(call KernelPackage,r8168)) diff --git a/r8168/patches/001-r8168-add-LED-configuration-from-OF.patch b/r8168/patches/001-r8168-add-LED-configuration-from-OF.patch index 62a352dd8..f49842442 100755 --- a/r8168/patches/001-r8168-add-LED-configuration-from-OF.patch +++ b/r8168/patches/001-r8168-add-LED-configuration-from-OF.patch @@ -1,42 +1,42 @@ ---- a/src/r8168_n.c -+++ b/src/r8168_n.c -@@ -47,6 +47,7 @@ - #include - #include - #include -+#include - #include - #include - #include -@@ -23482,6 +23483,22 @@ rtl8168_set_bios_setting(struct net_devi - } - } - -+static int rtl8168_led_configuration(struct rtl8168_private *tp) -+{ -+ u32 led_data; -+ int ret; -+ -+ ret = of_property_read_u32(tp->pci_dev->dev.of_node, -+ "realtek,led-data", &led_data); -+ -+ if (ret) -+ return ret; -+ -+ RTL_W16(tp, CustomLED, led_data); -+ -+ return 0; -+} -+ - static void - rtl8168_init_software_variable(struct net_device *dev) - { -@@ -24000,6 +24017,8 @@ rtl8168_init_software_variable(struct ne - tp->NotWrMcuPatchCode = TRUE; - } - -+ rtl8168_led_configuration(tp); -+ - tp->NicCustLedValue = RTL_R16(tp, CustomLED); - - rtl8168_get_hw_wol(dev); +--- a/src/r8168_n.c ++++ b/src/r8168_n.c +@@ -47,6 +47,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -24643,6 +24644,22 @@ rtl8168_set_bios_setting(struct net_devi + } + } + ++static int rtl8168_led_configuration(struct rtl8168_private *tp) ++{ ++ u32 led_data; ++ int ret; ++ ++ ret = of_property_read_u32(tp->pci_dev->dev.of_node, ++ "realtek,led-data", &led_data); ++ ++ if (ret) ++ return ret; ++ ++ RTL_W16(tp, CustomLED, led_data); ++ ++ return 0; ++} ++ + static void + rtl8168_init_software_variable(struct net_device *dev) + { +@@ -25206,6 +25223,8 @@ rtl8168_init_software_variable(struct ne + tp->NotWrMcuPatchCode = TRUE; + } + ++ rtl8168_led_configuration(tp); ++ + tp->NicCustLedValue = RTL_R16(tp, CustomLED); + + rtl8168_get_hw_wol(dev); diff --git a/r8168/patches/020-5.18-support.patch b/r8168/patches/020-5.18-support.patch new file mode 100644 index 000000000..499389274 --- /dev/null +++ b/r8168/patches/020-5.18-support.patch @@ -0,0 +1,47 @@ +--- a/src/r8168_n.c ++++ b/src/r8168_n.c +@@ -3715,7 +3715,11 @@ + txd->opts2 = 0; + while (1) { + memset(tmpAddr, pattern++, len - 14); ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5,18,0) + pci_dma_sync_single_for_device(tp->pci_dev, ++#else ++ dma_sync_single_for_device(tp_to_dev(tp), ++#endif + le64_to_cpu(mapping), + len, DMA_TO_DEVICE); + txd->opts1 = cpu_to_le32(DescOwn | FirstFrag | LastFrag | len); +@@ -3743,7 +3747,11 @@ + if (rx_len == len) { + dma_sync_single_for_cpu(tp_to_dev(tp), le64_to_cpu(rxd->addr), tp->rx_buf_sz, DMA_FROM_DEVICE); + i = memcmp(skb->data, rx_skb->data, rx_len); ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5,18,0) + pci_dma_sync_single_for_device(tp->pci_dev, le64_to_cpu(rxd->addr), tp->rx_buf_sz, DMA_FROM_DEVICE); ++#else ++ dma_sync_single_for_device(tp_to_dev(tp), le64_to_cpu(rxd->addr), tp->rx_buf_sz, DMA_FROM_DEVICE); ++#endif + if (i == 0) { + // dev_printk(KERN_INFO, tp_to_dev(tp), "loopback test finished\n",rx_len,len); + break; +@@ -26464,11 +26472,20 @@ + + if ((sizeof(dma_addr_t) > 4) && + use_dac && ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5,18,0) + !pci_set_dma_mask(pdev, DMA_BIT_MASK(64)) && + !pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64))) { ++#else ++ !dma_set_mask(&pdev->dev, DMA_BIT_MASK(64)) && ++ !dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(64))) { ++#endif + dev->features |= NETIF_F_HIGHDMA; + } else { ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5,18,0) + rc = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); ++#else ++ rc = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); ++#endif + if (rc < 0) { + #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0) + if (netif_msg_probe(tp)) diff --git a/r8168/patches/030-5.19-support.patch b/r8168/patches/030-5.19-support.patch new file mode 100644 index 000000000..d4dca3125 --- /dev/null +++ b/r8168/patches/030-5.19-support.patch @@ -0,0 +1,18 @@ +--- a/src/r8168_n.c ++++ b/src/r8168_n.c +@@ -116,6 +116,15 @@ + #define FIRMWARE_8168FP_3 "rtl_nic/rtl8168fp-3.fw" + #define FIRMWARE_8168FP_4 "rtl_nic/rtl8168fp-4.fw" + ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) ++static inline void netif_set_gso_max_size(struct net_device *dev, ++ unsigned int size) ++{ ++ /* dev->gso_max_size is read locklessly from sk_setup_caps() */ ++ WRITE_ONCE(dev->gso_max_size, size); ++} ++#endif ++ + /* Maximum number of multicast addresses to filter (vs. Rx-all-multicast). + The RTL chips use a 64 element hash table based on the Ethernet CRC. */ + static const int multicast_filter_limit = 32; diff --git a/r8168/patches/030-6.1-support.patch b/r8168/patches/030-6.1-support.patch new file mode 100644 index 000000000..44ab2be19 --- /dev/null +++ b/r8168/patches/030-6.1-support.patch @@ -0,0 +1,14 @@ +--- a/src/r8168.h +--- b/src/r8168.h +@@ -566,7 +566,11 @@ + typedef struct napi_struct *napi_ptr; + typedef int napi_budget; + ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) ++#define RTL_NAPI_CONFIG(ndev, priv, function, weight) netif_napi_add_weight(ndev, &priv->napi, function, weight) ++#else + #define RTL_NAPI_CONFIG(ndev, priv, function, weight) netif_napi_add(ndev, &priv->napi, function, weight) ++#endif + #define RTL_NAPI_QUOTA(budget, ndev) min(budget, budget) + #define RTL_GET_PRIV(stuct_ptr, priv_struct) container_of(stuct_ptr, priv_struct, stuct_ptr) + #define RTL_GET_NETDEV(priv_ptr) struct net_device *dev = priv_ptr->dev; diff --git a/shadowsocks-libev/files/shadowsocks-libev.config b/shadowsocks-libev/files/shadowsocks-libev.config index c9ba382fb..5855ac2b9 100755 --- a/shadowsocks-libev/files/shadowsocks-libev.config +++ b/shadowsocks-libev/files/shadowsocks-libev.config @@ -25,7 +25,7 @@ config ss_redir hi2 option mptcp 1 option ipv6_first 1 option no_delay 1 - + config ss_rules 'ss_rules' option disabled 0 option redir_tcp 'hi1' @@ -47,4 +47,32 @@ config ss_tunnel 'dns' option mode 'tcp_and_udp' option server 'sss0' option local_port '5353' - option tunnel_address '8.8.8.8:53' \ No newline at end of file + option tunnel_address '8.8.8.8:53' + +config ss_redir hi3 + option server 'sss0' + option local_address '0.0.0.0' + option local_port '1100' + option mode 'tcp_and_udp' + option timeout '1000' + option fast_open 1 + option verbose 0 + option syslog 1 + option reuse_port 1 + option mptcp 1 + option ipv6_first 1 + option no_delay 1 + +config ss_redir hi4 + option server 'sss0' + option local_address '0.0.0.0' + option local_port '1100' + option mode 'tcp_and_udp' + option timeout '1000' + option fast_open 1 + option verbose 0 + option syslog 1 + option reuse_port 1 + option mptcp 1 + option ipv6_first 1 + option no_delay 1 \ No newline at end of file diff --git a/shadowsocks-libev/files/shadowsocks.conf b/shadowsocks-libev/files/shadowsocks.conf index a80d14c85..070a876be 100755 --- a/shadowsocks-libev/files/shadowsocks.conf +++ b/shadowsocks-libev/files/shadowsocks.conf @@ -2,18 +2,18 @@ # max open files fs.file-max = 512000 # max read buffer -net.core.rmem_max = 67108864 +net.core.rmem_max = 16777216 # max write buffer -net.core.wmem_max = 67108864 +net.core.wmem_max = 16777216 net.core.optmem_max = 33554432 # default read buffer -#net.core.rmem_default = 131072 +#net.core.rmem_default = 16777216 # default write buffer -#net.core.wmem_default = 131072 +#net.core.wmem_default = 16777216 # max processor input queue -net.core.netdev_max_backlog = 4096 +net.core.netdev_max_backlog = 8192 # max backlog -net.core.somaxconn = 4096 +net.core.somaxconn = 8192 # resist SYN flood attacks net.ipv4.tcp_syncookies = 1 @@ -36,13 +36,13 @@ net.ipv4.tcp_max_tw_buckets = 10000 # turn on TCP Fast Open on both client and server side #net.ipv4.tcp_fastopen = 3 # TCP receive buffer -net.ipv4.tcp_rmem = 4096 87380 33554432 +net.ipv4.tcp_rmem = 4096 87380 16777216 # TCP write buffer -net.ipv4.tcp_wmem = 4096 65536 33554432 +net.ipv4.tcp_wmem = 4096 87380 16777216 # TCP buffer -net.ipv4.tcp_mem = 8092 131072 67108864 +net.ipv4.tcp_mem = 8192000 8192000 8192000 # UDP buffer -net.ipv4.udp_mem = 8092 131072 67108864 +net.ipv4.udp_mem = 4096 131072 67108864 # turn off path MTU discovery net.ipv4.tcp_mtu_probing = 0 @@ -54,3 +54,6 @@ net.netfilter.nf_conntrack_max = 131072 net.ipv4.tcp_ecn = 2 #net.ipv4.tcp_sack = 0 +#net.ipv4.tcp_dsack = 0 +#net.ipv4.tcp_fack = 0 +net.ipv4.tcp_slow_start_after_idle = 0 \ No newline at end of file diff --git a/shadowsocks-libev/files/ss-rules b/shadowsocks-libev/files/ss-rules index 9e6907fab..e945d9ef9 100755 --- a/shadowsocks-libev/files/ss-rules +++ b/shadowsocks-libev/files/ss-rules @@ -133,7 +133,7 @@ ss_rules_parse_args() { ss_rules_flush() { local setname - $IPTABLESSAVE --counters 2>/dev/null | grep -v ssr_ | $IPTABLESRESTORE -w --counters + $IPTABLESSAVE --counters 2>/dev/null | grep -v ssr_ | $IPTABLESRESTORE --counters while ip rule del fwmark 1 lookup 100 2>/dev/null; do true; done ip route flush table 100 || true for setname in $(ipset -n list | grep "ssr_${rule}"); do @@ -178,7 +178,7 @@ ss_rules_iptchains_init() { ss_rules_iptchains_init_mark() { if [ "$($IPTABLES -w -t mangle -L PREROUTING | grep ss_rules_dst_bypass_all)" = "" ]; then - $IPTABLESRESTORE -w --noflush <<-EOF + $IPTABLESRESTORE --noflush <<-EOF *mangle -A PREROUTING -m set --match-set ss_rules_dst_bypass_all dst -j MARK --set-mark 0x539 COMMIT @@ -199,7 +199,7 @@ ss_rules_iptchains_init_tcp() { bypass|*) return 0;; esac if [ "$($IPTABLESSAVE 2>/dev/null | grep ssr_${rule}_local_out | grep ssr_${rule}_dst_bypass)" = "" ]; then - $IPTABLESRESTORE -w --noflush <<-EOF + $IPTABLESRESTORE --noflush <<-EOF *nat :ssr_${rule}_local_out - -I OUTPUT 1 -p tcp -j ssr_${rule}_local_out @@ -255,7 +255,7 @@ ss_rules_iptchains_init_() { forward) dst_default_target=ssr_${rule}_forward ;; bypass|*) dst_default_target=RETURN ;; esac - sed -e '/^\s*$/d' -e 's/^\s\+//' <<-EOF | $IPTABLESRESTORE -w --noflush + sed -e '/^\s*$/d' -e 's/^\s\+//' <<-EOF | $IPTABLESRESTORE --noflush *$table :ssr_${rule}_pre_src - :ssr_${rule}_src - diff --git a/shadowsocks-libev/files/ss-rules6 b/shadowsocks-libev/files/ss-rules6 index 829dd124d..2a9485f0e 100755 --- a/shadowsocks-libev/files/ss-rules6 +++ b/shadowsocks-libev/files/ss-rules6 @@ -117,7 +117,7 @@ ss_rules6_parse_args() { ss_rules6_flush() { local setname - $IP6TABLESSAVE --counters 2>/dev/null | grep -v ssr6_ | $IP6TABLESRESTORE -w --counters + $IP6TABLESSAVE --counters 2>/dev/null | grep -v ssr6_ | $IP6TABLESRESTORE --counters while ip -f inet6 rule del fwmark 1 lookup 100 2>/dev/null; do true; done ip -f inet6 route flush table 100 || true for setname in $(ipset -n list | grep "ss_rules6_"); do @@ -162,7 +162,7 @@ ss_rules6_iptchains_init() { ss_rules6_iptchains_init_mark() { if [ "$($IP6TABLES -w -t mangle -L PREROUTING | grep ss_rules6_dst_bypass_all)" = "" ]; then - $IP6TABLESRESTORE -w --noflush <<-EOF + $IP6TABLESRESTORE --noflush <<-EOF *mangle -A PREROUTING -m set --match-set ss_rules6_dst_bypass_all dst -j MARK --set-mark 0x6539 COMMIT @@ -184,7 +184,7 @@ ss_rules6_iptchains_init_tcp() { bypass|*) return 0;; esac - $IP6TABLESRESTORE -w --noflush <<-EOF + $IP6TABLESRESTORE --noflush <<-EOF *nat :ssr6_${rule}_local_out - -I OUTPUT 1 -p tcp -j ssr6_${rule}_local_out @@ -239,7 +239,7 @@ ss_rules6_iptchains_init_() { forward) dst_default_target=ssr6_${rule}_forward ;; bypass|*) dst_default_target=RETURN ;; esac - sed -e '/^\s*$/d' -e 's/^\s\+//' <<-EOF | $IP6TABLESRESTORE -w --noflush + sed -e '/^\s*$/d' -e 's/^\s\+//' <<-EOF | $IP6TABLESRESTORE --noflush *$table :ssr6_${rule}_pre_src - :ssr6_${rule}_src - diff --git a/shadowsocks-libev/patches/020-FIX.patch b/shadowsocks-libev/patches/020-FIX.patch new file mode 100644 index 000000000..3edff9cae --- /dev/null +++ b/shadowsocks-libev/patches/020-FIX.patch @@ -0,0 +1,11 @@ +--- a/src/manager.c.old 2023-01-12 13:23:01.201603055 +0100 ++++ b/src/manager.c 2023-01-12 13:23:37.180961405 +0100 +@@ -684,7 +684,7 @@ + while ((entry = cork_hash_table_iterator_next(&iter)) != NULL) { + struct server *server = (struct server *)entry->value; + char *method = server->method ? server->method : manager->method; +- char *passkey = server->key ? server->key : server->password; ++ char *passkey = server->key[0] ? server->key : server->password; + size_t pos = strlen(buf); + size_t entry_len = strlen(server->port) + strlen(passkey) + strlen(method); + if (pos > BUF_SIZE - entry_len - 50) { diff --git a/fast-classifier/Makefile b/shortcut-fe/fast-classifier/Makefile old mode 100755 new mode 100644 similarity index 99% rename from fast-classifier/Makefile rename to shortcut-fe/fast-classifier/Makefile index 757d60e52..09c1174dd --- a/fast-classifier/Makefile +++ b/shortcut-fe/fast-classifier/Makefile @@ -16,7 +16,7 @@ include $(TOPDIR)/rules.mk include $(INCLUDE_DIR)/kernel.mk PKG_NAME:=fast-classifier -PKG_RELEASE:=3 +PKG_RELEASE:=6 include $(INCLUDE_DIR)/package.mk diff --git a/fast-classifier/src/Makefile b/shortcut-fe/fast-classifier/src/Makefile old mode 100755 new mode 100644 similarity index 100% rename from fast-classifier/src/Makefile rename to shortcut-fe/fast-classifier/src/Makefile diff --git a/fast-classifier/src/fast-classifier.c b/shortcut-fe/fast-classifier/src/fast-classifier.c old mode 100755 new mode 100644 similarity index 99% rename from fast-classifier/src/fast-classifier.c rename to shortcut-fe/fast-classifier/src/fast-classifier.c index 746a0d6f9..944dfae38 --- a/fast-classifier/src/fast-classifier.c +++ b/shortcut-fe/fast-classifier/src/fast-classifier.c @@ -451,7 +451,7 @@ static u32 fc_conn_hash(sfe_ip_addr_t *saddr, sfe_ip_addr_t *daddr, */ static int fast_classifier_update_protocol(struct sfe_connection_create *p_sic, struct nf_conn *ct) { - #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0) + #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0) struct net *net=NULL ; struct nf_tcp_net *tn=NULL; #endif @@ -465,7 +465,7 @@ static int fast_classifier_update_protocol(struct sfe_connection_create *p_sic, p_sic->dest_td_max_window = ct->proto.tcp.seen[1].td_maxwin; p_sic->dest_td_end = ct->proto.tcp.seen[1].td_end; p_sic->dest_td_max_end = ct->proto.tcp.seen[1].td_maxend; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0) net = nf_ct_net(ct); tn = nf_tcp_pernet(net); if ((tn&&tn->tcp_no_window_check) diff --git a/fast-classifier/src/fast-classifier.h b/shortcut-fe/fast-classifier/src/fast-classifier.h old mode 100755 new mode 100644 similarity index 100% rename from fast-classifier/src/fast-classifier.h rename to shortcut-fe/fast-classifier/src/fast-classifier.h diff --git a/fast-classifier/src/nl_classifier_test.c b/shortcut-fe/fast-classifier/src/nl_classifier_test.c old mode 100755 new mode 100644 similarity index 100% rename from fast-classifier/src/nl_classifier_test.c rename to shortcut-fe/fast-classifier/src/nl_classifier_test.c diff --git a/fast-classifier/src/userspace_example.c b/shortcut-fe/fast-classifier/src/userspace_example.c old mode 100755 new mode 100644 similarity index 100% rename from fast-classifier/src/userspace_example.c rename to shortcut-fe/fast-classifier/src/userspace_example.c diff --git a/shortcut-fe/Makefile b/shortcut-fe/shortcut-fe/Makefile old mode 100755 new mode 100644 similarity index 93% rename from shortcut-fe/Makefile rename to shortcut-fe/shortcut-fe/Makefile index 2baa86cbe..598e9d4a2 --- a/shortcut-fe/Makefile +++ b/shortcut-fe/shortcut-fe/Makefile @@ -16,7 +16,7 @@ include $(TOPDIR)/rules.mk include $(INCLUDE_DIR)/kernel.mk PKG_NAME:=shortcut-fe -PKG_RELEASE:=5 +PKG_RELEASE:=8 include $(INCLUDE_DIR)/package.mk @@ -43,8 +43,6 @@ Shortcut is an in-Linux-kernel IP packet forwarding engine. endef define KernelPackage/shortcut-fe/install - $(INSTALL_DIR) $(1)/etc/init.d - $(INSTALL_BIN) ./files/etc/init.d/shortcut-fe $(1)/etc/init.d $(INSTALL_DIR) $(1)/usr/bin $(INSTALL_BIN) ./files/usr/bin/sfe_dump $(1)/usr/bin endef @@ -81,8 +79,12 @@ endef ifneq ($(CONFIG_PACKAGE_kmod-shortcut-fe)$(CONFIG_PACKAGE_kmod-shortcut-fe-cm),) define Build/InstallDev + $(INSTALL_DIR) $(1)/usr/include $(INSTALL_DIR) $(1)/usr/include/shortcut-fe $(CP) -rf $(PKG_BUILD_DIR)/sfe.h $(1)/usr/include/shortcut-fe + $(CP) -rf $(PKG_BUILD_DIR)/sfe.h $(1)/usr/include + $(CP) -rf $(PKG_BUILD_DIR)/sfe_cm.h $(1)/usr/include + $(CP) -rf $(PKG_BUILD_DIR)/sfe_backport.h $(1)/usr/include endef endif diff --git a/shortcut-fe/files/etc/init.d/shortcut-fe b/shortcut-fe/shortcut-fe/files/etc/init.d/shortcut-fe old mode 100755 new mode 100644 similarity index 90% rename from shortcut-fe/files/etc/init.d/shortcut-fe rename to shortcut-fe/shortcut-fe/files/etc/init.d/shortcut-fe index 786e52df0..838512a36 --- a/shortcut-fe/files/etc/init.d/shortcut-fe +++ b/shortcut-fe/shortcut-fe/files/etc/init.d/shortcut-fe @@ -15,7 +15,7 @@ #SFE connection manager has a lower priority, it should be started after other connection manager #to detect the existence of connection manager with higher priority -START=99 +START=70 have_cm() { [ -d "/sys/kernel/debug/ecm" ] && echo 1 && return @@ -43,7 +43,9 @@ start() { } stop() { - [ -d "/sys/module/shortcut_fe_drv" ] && rmmod shortcut_fe_drv [ -d "/sys/module/shortcut_fe_cm" ] && rmmod shortcut_fe_cm + [ -d "/sys/module/shortcut_fe_ipv6" ] && rmmod shortcut_fe_ipv6 + [ -d "/sys/module/shortcut_fe" ] && rmmod shortcut_fe + [ -d "/sys/module/shortcut_fe_drv" ] && rmmod shortcut_fe_drv [ -d "/sys/module/fast_classifier" ] && rmmod fast_classifier } diff --git a/shortcut-fe/files/usr/bin/sfe_dump b/shortcut-fe/shortcut-fe/files/usr/bin/sfe_dump similarity index 100% rename from shortcut-fe/files/usr/bin/sfe_dump rename to shortcut-fe/shortcut-fe/files/usr/bin/sfe_dump diff --git a/shortcut-fe/src/Kconfig b/shortcut-fe/shortcut-fe/src/Kconfig old mode 100755 new mode 100644 similarity index 100% rename from shortcut-fe/src/Kconfig rename to shortcut-fe/shortcut-fe/src/Kconfig diff --git a/shortcut-fe/src/Makefile b/shortcut-fe/shortcut-fe/src/Makefile old mode 100755 new mode 100644 similarity index 100% rename from shortcut-fe/src/Makefile rename to shortcut-fe/shortcut-fe/src/Makefile diff --git a/shortcut-fe/src/sfe.h b/shortcut-fe/shortcut-fe/src/sfe.h old mode 100755 new mode 100644 similarity index 100% rename from shortcut-fe/src/sfe.h rename to shortcut-fe/shortcut-fe/src/sfe.h diff --git a/shortcut-fe/src/sfe_backport.h b/shortcut-fe/shortcut-fe/src/sfe_backport.h old mode 100755 new mode 100644 similarity index 100% rename from shortcut-fe/src/sfe_backport.h rename to shortcut-fe/shortcut-fe/src/sfe_backport.h diff --git a/shortcut-fe/src/sfe_cm.c b/shortcut-fe/shortcut-fe/src/sfe_cm.c old mode 100755 new mode 100644 similarity index 99% rename from shortcut-fe/src/sfe_cm.c rename to shortcut-fe/shortcut-fe/src/sfe_cm.c index 68304b388..2d3f79a04 --- a/shortcut-fe/src/sfe_cm.c +++ b/shortcut-fe/shortcut-fe/src/sfe_cm.c @@ -501,7 +501,7 @@ static unsigned int sfe_cm_post_routing(struct sk_buff *skb, int is_v4) sic.dest_td_max_window = ct->proto.tcp.seen[1].td_maxwin; sic.dest_td_end = ct->proto.tcp.seen[1].td_end; sic.dest_td_max_end = ct->proto.tcp.seen[1].td_maxend; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0) net = nf_ct_net(ct); tn = nf_tcp_pernet(net); if ((tn&&tn->tcp_no_window_check) diff --git a/shortcut-fe/src/sfe_cm.h b/shortcut-fe/shortcut-fe/src/sfe_cm.h old mode 100755 new mode 100644 similarity index 100% rename from shortcut-fe/src/sfe_cm.h rename to shortcut-fe/shortcut-fe/src/sfe_cm.h diff --git a/shortcut-fe/src/sfe_ipv4.c b/shortcut-fe/shortcut-fe/src/sfe_ipv4.c old mode 100755 new mode 100644 similarity index 100% rename from shortcut-fe/src/sfe_ipv4.c rename to shortcut-fe/shortcut-fe/src/sfe_ipv4.c diff --git a/shortcut-fe/src/sfe_ipv6.c b/shortcut-fe/shortcut-fe/src/sfe_ipv6.c old mode 100755 new mode 100644 similarity index 100% rename from shortcut-fe/src/sfe_ipv6.c rename to shortcut-fe/shortcut-fe/src/sfe_ipv6.c diff --git a/simulated-driver/Makefile b/shortcut-fe/simulated-driver/Makefile old mode 100755 new mode 100644 similarity index 96% rename from simulated-driver/Makefile rename to shortcut-fe/simulated-driver/Makefile index 0c710fe9d..ecf9c41bd --- a/simulated-driver/Makefile +++ b/shortcut-fe/simulated-driver/Makefile @@ -30,7 +30,7 @@ define KernelPackage/shortcut-fe-drv SECTION:=kernel CATEGORY:=Kernel modules SUBMENU:=Network Support - DEPENDS:=@TARGET_ipq60xx||@TARGET_ipq806x||TARGET_ipq807x +kmod-shortcut-fe + DEPENDS:=@TARGET_ipq806x||TARGET_ipq807x +kmod-shortcut-fe KCONFIG:= \ CONFIG_NET_CLS_ACT=y \ CONFIG_XFRM=y diff --git a/simulated-driver/patches/200-nss-qdisc-support.patch b/shortcut-fe/simulated-driver/patches/200-nss-qdisc-support.patch old mode 100755 new mode 100644 similarity index 100% rename from simulated-driver/patches/200-nss-qdisc-support.patch rename to shortcut-fe/simulated-driver/patches/200-nss-qdisc-support.patch diff --git a/upx/Makefile b/upx/Makefile new file mode 100644 index 000000000..9310bcf81 --- /dev/null +++ b/upx/Makefile @@ -0,0 +1,66 @@ +# +# Copyright (C) 2011-2020 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# +include $(TOPDIR)/rules.mk + +PKG_NAME:=upx +PKG_RELEASE:=1 + +PKG_MAINTAINER:=Xingwang Liao +PKG_LICENSE:=GPL-2.0-only +PKG_LICENSE_FILES:=COPYING LICENSE + +PKG_SOURCE_PROTO:=git +PKG_SOURCE_DATE:=2022-01-15 +PKG_SOURCE_VERSION:=1050de5171f70fd4ba113016e4db994e898c7be3 +PKG_SOURCE_URL:=https://github.com/upx/upx.git +PKG_SOURCE:=$(PKG_NAME)-$(PKG_SOURCE_DATE).tar.gz + +HOST_BUILD_DEPENDS:=ucl/host + +include $(INCLUDE_DIR)/host-build.mk +include $(INCLUDE_DIR)/package.mk + +define Host/Compile + UPX_UCLDIR=$(STAGING_DIR_HOST) \ + $(MAKE) -C $(HOST_BUILD_DIR)/src \ + CXXFLAGS_WERROR="" LDFLAGS="$(HOST_LDFLAGS)" \ + CXX="$(HOSTCXX)" +endef + +define Host/Install + $(CP) $(HOST_BUILD_DIR)/src/upx.out $(STAGING_DIR_HOST)/bin/upx +endef + +define Host/Clean + rm -f $(STAGING_DIR_HOST)/bin/upx +endef + +define Package/upx + SECTION:=utils + CATEGORY:=Utilities + DEPENDS:=+libucl +libstdcpp +zlib + TITLE:=The Ultimate Packer for eXecutables + URL:=https://upx.github.io/ +endef + +define Package/upx/description +UPX is a free, portable, extendable, high-performance executable packer for +several different executable formats. It achieves an excellent compression ratio +and offers very fast decompression. Your executables suffer no memory overhead +or other drawbacks for most of the formats supported, because of in-place +decompression. +endef + +MAKE_PATH := src + +define Package/upx/install + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) $(PKG_BUILD_DIR)/src/upx.out $(1)/usr/bin/upx +endef + +$(eval $(call HostBuild)) +$(eval $(call BuildPackage,upx)) diff --git a/v2ray-core/files/usr/bin/v2ray-rules b/v2ray-core/files/usr/bin/v2ray-rules index 27a1c3cf5..db0a5a5fd 100755 --- a/v2ray-core/files/usr/bin/v2ray-rules +++ b/v2ray-core/files/usr/bin/v2ray-rules @@ -134,7 +134,7 @@ v2r_rules_parse_args() { v2r_rules_flush() { local setname - $IPTABLESSAVE --counters 2>/dev/null | grep -v v2r_ | $IPTABLESRESTORE -w --counters + $IPTABLESSAVE --counters 2>/dev/null | grep -v v2r_ | $IPTABLESRESTORE --counters while ip rule del fwmark 1 lookup 100 2>/dev/null; do true; done ip route flush table 100 || true for setname in $(ipset -n list | grep "ss_rules_"); do @@ -179,7 +179,7 @@ v2r_rules_iptchains_init() { v2r_rules_iptchains_init_mark() { if [ "$($IPTABLES -w -t mangle -L PREROUTING | grep ss_rules_dst_bypass_all)" = "" ]; then - $IPTABLESRESTORE -w --noflush <<-EOF + $IPTABLESRESTORE --noflush <<-EOF *mangle -A PREROUTING -m set --match-set ss_rules_dst_bypass_all dst -j MARK --set-mark 0x539 COMMIT @@ -200,7 +200,7 @@ v2r_rules_iptchains_init_tcp() { bypass|*) return 0;; esac - $IPTABLESRESTORE -w --noflush <<-EOF + $IPTABLESRESTORE --noflush <<-EOF *nat :v2r_${rule}_local_out - -I OUTPUT 1 -p tcp -j v2r_${rule}_local_out @@ -255,7 +255,7 @@ v2r_rules_iptchains_init_() { forward) dst_default_target=v2r_${rule}_forward ;; bypass|*) dst_default_target=RETURN ;; esac - sed -e '/^\s*$/d' -e 's/^\s\+//' <<-EOF | $IPTABLESRESTORE -w --noflush + sed -e '/^\s*$/d' -e 's/^\s\+//' <<-EOF | $IPTABLESRESTORE --noflush *$table :v2r_${rule}_pre_src - :v2r_${rule}_src - diff --git a/v2ray-core/files/usr/bin/v2ray-rules6 b/v2ray-core/files/usr/bin/v2ray-rules6 index 48d7fcd52..ba72d966e 100755 --- a/v2ray-core/files/usr/bin/v2ray-rules6 +++ b/v2ray-core/files/usr/bin/v2ray-rules6 @@ -117,7 +117,7 @@ v2ray_rules6_parse_args() { v2ray_rules6_flush() { local setname - $IP6TABLESSAVE --counters 2>/dev/null | grep -v v2r6_ | $IP6TABLESRESTORE -w --counters + $IP6TABLESSAVE --counters 2>/dev/null | grep -v v2r6_ | $IP6TABLESRESTORE --counters while ip -f inet6 rule del fwmark 1 lookup 100 2>/dev/null; do true; done ip -f inet6 route flush table 100 || true for setname in $(ipset -n list | grep "ss_rules6_"); do @@ -161,7 +161,7 @@ v2ray_rules6_iptchains_init() { } v2ray_rules6_iptchains_init_mark() { - $IP6TABLESRESTORE -w --noflush <<-EOF + $IP6TABLESRESTORE --noflush <<-EOF *mangle -A PREROUTING -m set --match-set ss_rules6_dst_bypass_all dst -j MARK --set-mark 0x6539 COMMIT @@ -184,7 +184,7 @@ v2ray_rules6_iptchains_init_tcp() { esac # echo "tcp mangle" -# $IP6TABLESRESTORE -w --noflush <<-EOF +# $IP6TABLESRESTORE --noflush <<-EOF # *mangle # :v2r6_${rule}_local_out - # -I OUTPUT 1 -p tcp -j v2r6_${rule}_local_out @@ -244,7 +244,7 @@ v2ray_rules6_iptchains_init_() { forward) dst_default_target=v2r6_${rule}_forward ;; bypass|*) dst_default_target=RETURN ;; esac - sed -e '/^\s*$/d' -e 's/^\s\+//' <<-EOF | $IP6TABLESRESTORE -w --noflush + sed -e '/^\s*$/d' -e 's/^\s\+//' <<-EOF | $IP6TABLESRESTORE --noflush *$table :v2r6_${rule}_pre_src - :v2r6_${rule}_src - diff --git a/xtables-addons/Makefile b/xtables-addons/Makefile index fa7587589..cba09357d 100755 --- a/xtables-addons/Makefile +++ b/xtables-addons/Makefile @@ -9,9 +9,9 @@ include $(TOPDIR)/rules.mk include $(INCLUDE_DIR)/kernel.mk PKG_NAME:=xtables-addons -PKG_VERSION:=3.18 -PKG_RELEASE:=4 -PKG_HASH:=a77914a483ff381663f52120577e5e9355ca07cca73958b038e09d91247458d5 +PKG_VERSION:=3.22 +PKG_RELEASE:=$(AUTORELEASE) +PKG_HASH:=faa16a27166275afbfe8df605f55c3a81ac693bf19da674d45ceded4137ae217 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.xz PKG_SOURCE_URL:=https://inai.de/files/xtables-addons/ diff --git a/xtables-addons/patches/202-fix-lua-packetscript-kernel-5.16-no-isystem.patch b/xtables-addons/patches/202-fix-lua-packetscript-kernel-5.16-no-isystem.patch new file mode 100644 index 000000000..5279619da --- /dev/null +++ b/xtables-addons/patches/202-fix-lua-packetscript-kernel-5.16-no-isystem.patch @@ -0,0 +1,284 @@ +Linux 5.16 includes 04e85bbf71c9 ("isystem: delete global -isystem compile option") + +compile error on >=5.16 +xtables-addons-3.21/extensions/LUA/lua/lua.h:12:10: fatal error: stdarg.h: No such file or directory + 12 | #include + | ^~~~~~~~~~ + +Generated with coccinelle: + +cat <cocci-xtables-lua-linux-5.16.spatch +@include_arg@ +@@ + #include + +@include_def@ +@@ + #include + +@include_both depends on include_arg && include_def@ +@@ + #include + +@add_include_linux_stdheaders_both depends on include_both@ +@@ ++#include ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 16, 0) +?#include +?#include ++#else ++#include ++#include ++#endif + +@add_include_linux_stdheaders depends on !include_both@ +@@ +( ++#include ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 16, 0) + #include ++#else ++#include ++#endif +| ++#include ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 16, 0) + #include ++#else ++#include ++#endif +) +EOF +spatch --in-place --include-headers --no-includes --sp-file cocci-xtables-lua-linux-5.16.spatch --dir extensions/LUA/lua/ + +--- + +--- a/extensions/LUA/lua/lauxlib.c ++++ b/extensions/LUA/lua/lauxlib.c +@@ -4,7 +4,12 @@ + ** See Copyright Notice in lua.h + */ + ++#include ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 16, 0) + #include ++#else ++#include ++#endif + + #if !defined(__KERNEL__) + #include +--- a/extensions/LUA/lua/lauxlib.h ++++ b/extensions/LUA/lua/lauxlib.h +@@ -9,7 +9,12 @@ + #define lauxlib_h + + ++#include ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 16, 0) + #include ++#else ++#include ++#endif + #include /* for kmalloc and kfree when allocating luaL_Buffer */ + + #if !defined(__KERNEL__) +--- a/extensions/LUA/lua/ldebug.c ++++ b/extensions/LUA/lua/ldebug.c +@@ -5,8 +5,14 @@ + */ + + ++#include ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 16, 0) + #include + #include ++#else ++#include ++#include ++#endif + #include + + #define ldebug_c +--- a/extensions/LUA/lua/ldump.c ++++ b/extensions/LUA/lua/ldump.c +@@ -4,7 +4,12 @@ + ** See Copyright Notice in lua.h + */ + ++#include ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 16, 0) + #include ++#else ++#include ++#endif + + #define ldump_c + #define LUA_CORE +--- a/extensions/LUA/lua/lfunc.c ++++ b/extensions/LUA/lua/lfunc.c +@@ -5,7 +5,12 @@ + */ + + ++#include ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 16, 0) + #include ++#else ++#include ++#endif + + #define lfunc_c + #define LUA_CORE +--- a/extensions/LUA/lua/llimits.h ++++ b/extensions/LUA/lua/llimits.h +@@ -7,7 +7,12 @@ + #ifndef llimits_h + #define llimits_h + ++#include ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 16, 0) + #include ++#else ++#include ++#endif + + #include "lua.h" + +--- a/extensions/LUA/lua/lmem.c ++++ b/extensions/LUA/lua/lmem.c +@@ -5,7 +5,12 @@ + */ + + ++#include ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 16, 0) + #include ++#else ++#include ++#endif + + #define lmem_c + #define LUA_CORE +--- a/extensions/LUA/lua/lmem.h ++++ b/extensions/LUA/lua/lmem.h +@@ -8,7 +8,12 @@ + #define lmem_h + + ++#include ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 16, 0) + #include ++#else ++#include ++#endif + + #include "llimits.h" + #include "lua.h" +--- a/extensions/LUA/lua/lobject.c ++++ b/extensions/LUA/lua/lobject.c +@@ -4,7 +4,12 @@ + ** See Copyright Notice in lua.h + */ + ++#include ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 16, 0) + #include ++#else ++#include ++#endif + + #include + #include +--- a/extensions/LUA/lua/lobject.h ++++ b/extensions/LUA/lua/lobject.h +@@ -9,7 +9,12 @@ + #define lobject_h + + ++#include ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 16, 0) + #include ++#else ++#include ++#endif + + + #include "llimits.h" +--- a/extensions/LUA/lua/lstate.c ++++ b/extensions/LUA/lua/lstate.c +@@ -5,7 +5,12 @@ + */ + + ++#include ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 16, 0) + #include ++#else ++#include ++#endif + + #define lstate_c + #define LUA_CORE +--- a/extensions/LUA/lua/lstrlib.c ++++ b/extensions/LUA/lua/lstrlib.c +@@ -6,7 +6,12 @@ + + + #include ++#include ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 16, 0) + #include ++#else ++#include ++#endif + #include + #include + #include +--- a/extensions/LUA/lua/ltablib.c ++++ b/extensions/LUA/lua/ltablib.c +@@ -5,7 +5,12 @@ + */ + + ++#include ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 16, 0) + #include ++#else ++#include ++#endif + + #define ltablib_c + #define LUA_LIB +--- a/extensions/LUA/lua/lua.h ++++ b/extensions/LUA/lua/lua.h +@@ -9,8 +9,14 @@ + #ifndef lua_h + #define lua_h + ++#include ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 16, 0) + #include + #include ++#else ++#include ++#include ++#endif + + #include "luaconf.h" + +--- a/extensions/LUA/lua/luaconf.h ++++ b/extensions/LUA/lua/luaconf.h +@@ -8,7 +8,12 @@ + #ifndef lconfig_h + #define lconfig_h + ++#include ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 16, 0) + #include ++#else ++#include ++#endif + + #if !defined(__KERNEL__) + #include