mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-08-02 04:49:24 +08:00
update 2026-05-27 23:25:57
This commit is contained in:
parent
84f707b52b
commit
d0159b49a5
83
UnblockNeteaseMusic-Go/Makefile
Normal file
83
UnblockNeteaseMusic-Go/Makefile
Normal file
@ -0,0 +1,83 @@
|
||||
# SPDX-Identifier-License: GPL-3.0-only
|
||||
#
|
||||
# Copyright (C) 2021 ImmortalWrt.org
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=UnblockNeteaseMusic-Go
|
||||
PKG_VERSION:=0.2.13
|
||||
PKG_RELEASE:=$(AUTORELEASE)
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/cnsilvan/UnblockNeteaseMusic/tar.gz/$(PKG_VERSION)?
|
||||
PKG_HASH:=92201b7f04ab1015c806c672b98a29b97d0f137d9b60e6d35d279c2064ed86a4
|
||||
|
||||
PKG_LICENSE:=GPL-3.0
|
||||
PKG_LICENSE_FILE:=LICENSE
|
||||
PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
CONFIG_UNBLOCKNETEASEMUSIC_GO_COMPRESS_GOPROXY \
|
||||
CONFIG_UNBLOCKNETEASEMUSIC_GO_COMPRESS_UPX
|
||||
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(firstword $(subst -, ,$(PKG_NAME)))-$(PKG_VERSION)
|
||||
PKG_BUILD_DEPENDS:=golang/host upx/host
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_USE_MIPS16:=0
|
||||
|
||||
GO_PKG:=github.com/cnsilvan/UnblockNeteaseMusic
|
||||
GO_PKG_BUILD_PKG:=$$(GO_PKG)
|
||||
GO_PKG_LDFLAGS:=-s -w
|
||||
COMPILE_TIME:= $(shell TZ=UTC-8 date '+%Y-%m-%d %H:%M:%S')
|
||||
GO_PKG_LDFLAGS+= \
|
||||
-X '$(GO_PKG)/version.Version=$(PKG_VERSION)' \
|
||||
-X '$(GO_PKG)/version.BuildTime=$(COMPILE_TIME)' \
|
||||
-X '$(GO_PKG)/version.ExGoVersionInfo=$(GO_ARM) $(GO_MIPS)$(GO_MIPS64)'
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include ../../lang/golang/golang-package.mk
|
||||
|
||||
define Package/UnblockNeteaseMusic-Go/config
|
||||
config UNBLOCKNETEASEMUSIC_GO_COMPRESS_GOPROXY
|
||||
bool "Compiling with GOPROXY proxy"
|
||||
default n
|
||||
|
||||
config UNBLOCKNETEASEMUSIC_GO_COMPRESS_UPX
|
||||
bool "Compress executable files with UPX"
|
||||
depends on !mips64
|
||||
default n
|
||||
endef
|
||||
|
||||
ifeq ($(CONFIG_UNBLOCKNETEASEMUSIC_GO_COMPRESS_GOPROXY),y)
|
||||
export GO111MODULE=on
|
||||
export GOPROXY=https://goproxy.io
|
||||
endif
|
||||
|
||||
define Package/UnblockNeteaseMusic-Go
|
||||
SECTION:=multimedia
|
||||
CATEGORY:=Multimedia
|
||||
TITLE:=Revive Netease Cloud Music (Golang)
|
||||
URL:=https://github.com/cnsilvan/UnblockNeteaseMusic
|
||||
DEPENDS:=$(GO_ARCH_DEPENDS)
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
$(call GoPackage/Build/Compile)
|
||||
ifeq ($(CONFIG_UNBLOCKNETEASEMUSIC_GO_COMPRESS_UPX),y)
|
||||
$(STAGING_DIR_HOST)/bin/upx --lzma --best $(GO_PKG_BUILD_BIN_DIR)/UnblockNeteaseMusic
|
||||
endif
|
||||
endef
|
||||
|
||||
define Package/UnblockNeteaseMusic-Go/install
|
||||
$(call GoPackage/Package/Install/Bin,$(PKG_INSTALL_DIR))
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/UnblockNeteaseMusic $(1)/usr/bin/UnblockNeteaseMusic
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/share/UnblockNeteaseMusicGo
|
||||
$(CP) ./files/* $(1)/usr/share/UnblockNeteaseMusicGo/
|
||||
|
||||
endef
|
||||
|
||||
$(eval $(call GoBinPackage,UnblockNeteaseMusic-Go))
|
||||
$(eval $(call BuildPackage,UnblockNeteaseMusic-Go))
|
||||
14
UnblockNeteaseMusic-Go/files/ca.crt
Normal file
14
UnblockNeteaseMusic-Go/files/ca.crt
Normal file
@ -0,0 +1,14 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICIjCCAaigAwIBAgIUTc9HQDej5hLCQ74u436a5yE4MDcwCgYIKoZIzj0EAwMw
|
||||
SDELMAkGA1UEBhMCQ04xJDAiBgNVBAMMG1VuYmxvY2tOZXRlYXNlTXVzaWMgUm9v
|
||||
dCBDQTETMBEGA1UECgwKMTcxNTE3MzMyOTAeFw0yMTA0MzAwNzIzMDJaFw0yNjA0
|
||||
MjkwNzIzMDJaMEgxCzAJBgNVBAYTAkNOMSQwIgYDVQQDDBtVbmJsb2NrTmV0ZWFz
|
||||
ZU11c2ljIFJvb3QgQ0ExEzARBgNVBAoMCjE3MTUxNzMzMjkwdjAQBgcqhkjOPQIB
|
||||
BgUrgQQAIgNiAASIyI7lYgGlq49qWtY1O2/XNDeowYf7W/Z+l7C14bphxAJ9jSDo
|
||||
tLwbFPWy5VPENc0rB0/yeHA2z7LU67POL2gGgp+17y7scLkkBk3Q7wRMETrtP44Z
|
||||
ITBstZ0wzVyyQEKjUzBRMB0GA1UdDgQWBBQ2F7+t8cPHJaWuCD8RHTSdLugKYzAf
|
||||
BgNVHSMEGDAWgBQ2F7+t8cPHJaWuCD8RHTSdLugKYzAPBgNVHRMBAf8EBTADAQH/
|
||||
MAoGCCqGSM49BAMDA2gAMGUCMQDqaRX2e01e0U+f0As/KUKDhmG5ElkK5CjYK9jk
|
||||
kXLNGFXJLGta6CDvjtMLBPc20qkCMBvDs+JnJKVBEJNZVsRBBs+v2YxNU/u2aYJa
|
||||
dMwXuFveSDWOS7mBeRztX/geEggiSw==
|
||||
-----END CERTIFICATE-----
|
||||
15
UnblockNeteaseMusic-Go/files/server.crt
Normal file
15
UnblockNeteaseMusic-Go/files/server.crt
Normal file
@ -0,0 +1,15 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICRDCCAcqgAwIBAgIUeVqRrT2mHG5Mc8JD+ErphiAmlgkwCgYIKoZIzj0EAwMw
|
||||
SDELMAkGA1UEBhMCQ04xJDAiBgNVBAMMG1VuYmxvY2tOZXRlYXNlTXVzaWMgUm9v
|
||||
dCBDQTETMBEGA1UECgwKMTcxNTE3MzMyOTAeFw0yMTA0MzAwNzIzMDJaFw0yMjA0
|
||||
MzAwNzIzMDJaMHsxCzAJBgNVBAYTAkNOMREwDwYDVQQHDAhIYW5nemhvdTEsMCoG
|
||||
A1UECgwjTmV0RWFzZSAoSGFuZ3pob3UpIE5ldHdvcmsgQ28uLCBMdGQxETAPBgNV
|
||||
BAsMCElUIERlcHQuMRgwFgYDVQQDDA8qLm11c2ljLjE2My5jb20wdjAQBgcqhkjO
|
||||
PQIBBgUrgQQAIgNiAAQTPyU9RQ1pAFMLmozi+c4pEC1rrxAlPGwO9Em+qV+a5qLW
|
||||
gQjjsJeabMqJ/UQ7hDtdKVxWuXiAjMiDcXwL63I71MZKPTAEKXdCmNQwb4kXvRUn
|
||||
oOR4r7BMxEpGlf0CULWjQjBAMBMGA1UdJQQMMAoGCCsGAQUFBwMBMCkGA1UdEQQi
|
||||
MCCCDW11c2ljLjE2My5jb22CDyoubXVzaWMuMTYzLmNvbTAKBggqhkjOPQQDAwNo
|
||||
ADBlAjEAs5bdgnNP/DiK919RiWscC0kyuY0ugG1C8m8F2Yod4MI3oTyrkVcag21o
|
||||
NSzm802uAjBoPuKEbjjFP4ics0BQdICiVd6WCVAsE69FnlmqRteAJqxvdKGpVLi+
|
||||
Qi3arfomrrc=
|
||||
-----END CERTIFICATE-----
|
||||
9
UnblockNeteaseMusic-Go/files/server.key
Normal file
9
UnblockNeteaseMusic-Go/files/server.key
Normal file
@ -0,0 +1,9 @@
|
||||
-----BEGIN EC PARAMETERS-----
|
||||
BgUrgQQAIg==
|
||||
-----END EC PARAMETERS-----
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MIGkAgEBBDBfW3twxGaQmMzP9p0/UU5EvHFVCbBw4piVFJ+pm/uFY6CKZkC5LGMa
|
||||
Uc9vn/KiewGgBwYFK4EEACKhZANiAAQTPyU9RQ1pAFMLmozi+c4pEC1rrxAlPGwO
|
||||
9Em+qV+a5qLWgQjjsJeabMqJ/UQ7hDtdKVxWuXiAjMiDcXwL63I71MZKPTAEKXdC
|
||||
mNQwb4kXvRUnoOR4r7BMxEpGlf0CULU=
|
||||
-----END EC PRIVATE KEY-----
|
||||
144
UnblockNeteaseMusic-Go/patches/01-fix-block-ad.patch
Normal file
144
UnblockNeteaseMusic-Go/patches/01-fix-block-ad.patch
Normal file
@ -0,0 +1,144 @@
|
||||
From f4f5d11b578a1ab2c3d089bbe5453052b43892bb Mon Sep 17 00:00:00 2001
|
||||
From: tofuliang <tofuliang@gmail.com>
|
||||
Date: Mon, 24 Jan 2022 18:53:11 +0800
|
||||
Subject: [PATCH] fix block ad,add web traffic logs
|
||||
|
||||
---
|
||||
app.go | 1 +
|
||||
config/config.go | 1 +
|
||||
processor/processor.go | 55 +++++++++++++++++++++++++++++++++---------
|
||||
3 files changed, 45 insertions(+), 12 deletions(-)
|
||||
|
||||
diff --git a/app.go b/app.go
|
||||
index 73a6070..1018d75 100644
|
||||
--- a/app.go
|
||||
+++ b/app.go
|
||||
@@ -45,6 +45,7 @@ func main() {
|
||||
log.Println("EnableLocalVip=", *config.EnableLocalVip)
|
||||
log.Println("UnlockSoundEffects=", *config.UnlockSoundEffects)
|
||||
log.Println("QQCookieFile=", *config.QQCookieFile)
|
||||
+ log.Println("LogWebTraffic=", *config.LogWebTraffic)
|
||||
if host.InitHosts() == nil {
|
||||
//go func() {
|
||||
// // // terminal: $ go tool pprof -http=:8081 http://localhost:6060/debug/pprof/heap
|
||||
diff --git a/config/config.go b/config/config.go
|
||||
index 6c07873..a653cdf 100644
|
||||
--- a/config/config.go
|
||||
+++ b/config/config.go
|
||||
@@ -31,6 +31,7 @@ var (
|
||||
EnableLocalVip = flag.Bool("lv", false, "enable local vip")
|
||||
UnlockSoundEffects = flag.Bool("sef", false, "unlock SoundEffects")
|
||||
QQCookieFile = flag.String("qc", "./qq.cookie", "specify cookies file ,such as : \"qq.cookie\"")
|
||||
+ LogWebTraffic = flag.Bool("wl", false, "log request url and response")
|
||||
)
|
||||
|
||||
func ValidParams() bool {
|
||||
diff --git a/processor/processor.go b/processor/processor.go
|
||||
index 8d09dbf..d07b9d3 100644
|
||||
--- a/processor/processor.go
|
||||
+++ b/processor/processor.go
|
||||
@@ -6,14 +6,6 @@ import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
- "github.com/cnsilvan/UnblockNeteaseMusic/cache"
|
||||
- "github.com/cnsilvan/UnblockNeteaseMusic/common"
|
||||
- "github.com/cnsilvan/UnblockNeteaseMusic/config"
|
||||
- "github.com/cnsilvan/UnblockNeteaseMusic/network"
|
||||
- "github.com/cnsilvan/UnblockNeteaseMusic/processor/crypto"
|
||||
- "github.com/cnsilvan/UnblockNeteaseMusic/provider"
|
||||
- "github.com/cnsilvan/UnblockNeteaseMusic/utils"
|
||||
- "golang.org/x/text/width"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
@@ -22,6 +14,15 @@ import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
+
|
||||
+ "github.com/cnsilvan/UnblockNeteaseMusic/cache"
|
||||
+ "github.com/cnsilvan/UnblockNeteaseMusic/common"
|
||||
+ "github.com/cnsilvan/UnblockNeteaseMusic/config"
|
||||
+ "github.com/cnsilvan/UnblockNeteaseMusic/network"
|
||||
+ "github.com/cnsilvan/UnblockNeteaseMusic/processor/crypto"
|
||||
+ "github.com/cnsilvan/UnblockNeteaseMusic/provider"
|
||||
+ "github.com/cnsilvan/UnblockNeteaseMusic/utils"
|
||||
+ "golang.org/x/text/width"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -188,6 +189,9 @@ func RequestAfter(request *http.Request, response *http.Response, netease *Netea
|
||||
if ok {
|
||||
code = codeN.String()
|
||||
}
|
||||
+
|
||||
+ logResponse(netease)
|
||||
+
|
||||
if strings.EqualFold(netease.Path, "/api/osx/version") {
|
||||
modified = disableUpdate(netease)
|
||||
} else if strings.Contains(netease.Path, "/usertool/sound/") {
|
||||
@@ -197,9 +201,24 @@ func RequestAfter(request *http.Request, response *http.Response, netease *Netea
|
||||
for key, resp := range netease.JsonBody {
|
||||
if strings.Contains(key, "/usertool/sound/") {
|
||||
modified = unblockSoundEffects(resp.(map[string]interface{}))
|
||||
- } else if *config.BlockAds && strings.Contains(netease.Path, "api/ad/") {
|
||||
+ } else if *config.BlockAds && strings.Contains(key, "api/ad/") {
|
||||
+ log.Println("block Ad has been triggered(" + key + ").")
|
||||
resp = &common.MapType{}
|
||||
modified = true
|
||||
+ } else if *config.BlockAds && strings.EqualFold(key, "/api/v2/banner/get") {
|
||||
+ newInfo := make(common.SliceType, 0)
|
||||
+ info := netease.JsonBody[key]
|
||||
+ for _, data := range info.(common.MapType)["banners"].(common.SliceType) {
|
||||
+ if banner, ok := data.(common.MapType); ok {
|
||||
+ if banner["adid"] == nil {
|
||||
+ newInfo = append(newInfo, banner)
|
||||
+ } else {
|
||||
+ log.Println("block banner Ad has been triggered.")
|
||||
+ modified = true
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ info.(common.MapType)["banners"] = newInfo
|
||||
}
|
||||
}
|
||||
} else if !netease.Web && (code == "401" || code == "512") && strings.Contains(netease.Path, "manipulate") {
|
||||
@@ -220,7 +239,9 @@ func RequestAfter(request *http.Request, response *http.Response, netease *Netea
|
||||
// log.Println("NeedRepackage")
|
||||
modifiedJson, _ := json.Marshal(netease.JsonBody)
|
||||
// log.Println(netease)
|
||||
- // log.Println(string(modifiedJson))
|
||||
+ if *config.LogWebTraffic {
|
||||
+ log.Println("modified =>\n" + string(modifiedJson))
|
||||
+ }
|
||||
if netease.Encrypted {
|
||||
modifiedJson = crypto.AesEncryptECB(modifiedJson, []byte(aeskey))
|
||||
}
|
||||
@@ -258,14 +279,24 @@ func disableUpdate(netease *Netease) bool {
|
||||
if len(value.(common.SliceType)) > 0 {
|
||||
modified = true
|
||||
jsonBody["updateFiles"] = make(common.SliceType, 0)
|
||||
+ log.Println("disable update has been triggered.")
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
- // modifiedJson, _ := json.Marshal(jsonBody)
|
||||
- // log.Println(string(modifiedJson))
|
||||
return modified
|
||||
}
|
||||
+
|
||||
+func logResponse(netease *Netease) {
|
||||
+ if *config.LogWebTraffic {
|
||||
+ reqUrl := netease.Path
|
||||
+ jsonBody := netease.JsonBody
|
||||
+ modifiedJson, _ := json.Marshal(jsonBody)
|
||||
+ sep := "===================================\n"
|
||||
+ log.Println(sep + reqUrl + " => \n" + string(modifiedJson) + "\n")
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
func localVIP(netease *Netease) bool {
|
||||
if !*config.EnableLocalVip {
|
||||
return false
|
||||
@ -0,0 +1,29 @@
|
||||
From 6c009953d357d1cc03478cf65fc05701fb1966d6 Mon Sep 17 00:00:00 2001
|
||||
From: ameansone <ameansone@outlook.com>
|
||||
Date: Sun, 5 Dec 2021 19:18:20 +0800
|
||||
Subject: [PATCH] fix(processor): avoid unnecessary decryption
|
||||
|
||||
---
|
||||
processor/processor.go | 8 ++++++--
|
||||
1 file changed, 6 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/processor/processor.go b/processor/processor.go
|
||||
index 8d09dbf..011571b 100644
|
||||
--- a/processor/processor.go
|
||||
+++ b/processor/processor.go
|
||||
@@ -177,9 +177,13 @@ func RequestAfter(request *http.Request, response *http.Response, netease *Netea
|
||||
if netease.Forward {
|
||||
aeskey = linuxApiKey
|
||||
}
|
||||
- decryptECBBytes, encrypted := crypto.AesDecryptECB(decryptECBBytes, []byte(aeskey))
|
||||
- netease.Encrypted = encrypted
|
||||
result := utils.ParseJson(decryptECBBytes)
|
||||
+ netease.Encrypted = false;
|
||||
+ if result == nil {
|
||||
+ decryptECBBytes, encrypted := crypto.AesDecryptECB(decryptECBBytes, []byte(aeskey))
|
||||
+ netease.Encrypted = encrypted
|
||||
+ result = utils.ParseJson(decryptECBBytes)
|
||||
+ }
|
||||
netease.JsonBody = result
|
||||
|
||||
modified := false
|
||||
47
UnblockNeteaseMusic/Makefile
Normal file
47
UnblockNeteaseMusic/Makefile
Normal file
@ -0,0 +1,47 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=UnblockNeteaseMusic
|
||||
PKG_VERSION:=0.28.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/UnblockNeteaseMusic/server/tar.gz/v${PKG_VERSION}?
|
||||
PKG_HASH:=0a1e843ffaa7b8ce65cc221daec07b63754473f0430019acb228f5b254602318
|
||||
|
||||
PKG_SOURCE_SUBDIR:=$(PKG_NAME)
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR)
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/UnblockNeteaseMusic
|
||||
SECTION:=multimedia
|
||||
CATEGORY:=Multimedia
|
||||
TITLE:=Revive Netease Cloud Music (NodeJS)
|
||||
URL:=https://github.com/nondanee/UnblockNeteaseMusic
|
||||
DEPENDS:=+node
|
||||
PKGARCH:=all
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/description
|
||||
Revive Netease Cloud Music (NodeJS)
|
||||
endef
|
||||
|
||||
define Build/Prepare
|
||||
tar -xzvf $(DL_DIR)/$(PKG_SOURCE) -C $(PKG_BUILD_DIR)
|
||||
mkdir -p $(PKG_BUILD_DIR)/$(PKG_NAME)
|
||||
echo -e $(PKG_VERSION) > $(PKG_BUILD_DIR)/$(PKG_NAME)/core_ver
|
||||
endef
|
||||
|
||||
define Build/Configure
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_DIR) $(1)/usr/share/$(PKG_NAME)
|
||||
$(CP) $(PKG_BUILD_DIR)/$(PKG_NAME)/* $(1)/usr/share/$(PKG_NAME)
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
96
alist/Makefile
Normal file
96
alist/Makefile
Normal file
@ -0,0 +1,96 @@
|
||||
#
|
||||
# Copyright (C) 2015-2016 OpenWrt.org
|
||||
#
|
||||
# This is free software, licensed under the GNU General Public License v3.
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=alist
|
||||
PKG_VERSION:=3.60.0
|
||||
PKG_WEB_VERSION:=3.60.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/AlistGo/alist/tar.gz/v$(PKG_VERSION)?
|
||||
PKG_HASH:=beccffdfba65a168e796a1789b6bea11ca225101cbc4276451e02b9ad8751a82
|
||||
|
||||
PKG_LICENSE:=GPL-3.0
|
||||
PKG_LICENSE_FILE:=LICENSE
|
||||
PKG_MAINTAINER:=sbwml <admin@cooluc.com>
|
||||
|
||||
define Download/$(PKG_NAME)-web
|
||||
FILE:=$(PKG_NAME)-web-$(PKG_WEB_VERSION).tar.gz
|
||||
URL_FILE:=dist.tar.gz
|
||||
URL:=https://github.com/AlistGo/alist-web/releases/download/$(PKG_WEB_VERSION)/
|
||||
HASH:=35c4a218fce07c9ef5113b70ec93e385c57c2910de8852b2d0973129c12b0843
|
||||
endef
|
||||
|
||||
PKG_BUILD_DEPENDS:=golang/host
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_USE_MIPS16:=0
|
||||
PKG_BUILD_FLAGS:=no-mips16
|
||||
|
||||
GO_PKG:=github.com/AlistGo/alist
|
||||
GO_PKG_LDFLAGS:= \
|
||||
-X '$(GO_PKG)/v3/internal/conf.BuiltAt=$(shell date '+%Y-%m-%d %H:%M:%S %z')' \
|
||||
-X '$(GO_PKG)/v3/internal/conf.GoVersion=$(shell $(STAGING_DIR_HOSTPKG)/bin/go version | sed 's/go version //')' \
|
||||
-X '$(GO_PKG)/v3/internal/conf.GitAuthor=Xhofe <i@nn.ci>' \
|
||||
-X '$(GO_PKG)/v3/internal/conf.GitCommit=tarball/$(shell echo $(PKG_HASH) | cut -c 1-7)' \
|
||||
-X '$(GO_PKG)/v3/internal/conf.Version=v$(PKG_VERSION) (OpenWrt $(ARCH_PACKAGES))' \
|
||||
-X '$(GO_PKG)/v3/internal/conf.WebVersion=$(PKG_WEB_VERSION)'
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
|
||||
|
||||
define Package/$(PKG_NAME)
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=Web Servers/Proxies
|
||||
TITLE:=A file list program that supports multiple storage
|
||||
URL:=https://alist.nn.ci/
|
||||
DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/conffiles
|
||||
/etc/alist
|
||||
/etc/config/alist
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/description
|
||||
A file list program that supports multiple storage, powered by Gin and Solidjs.
|
||||
endef
|
||||
|
||||
ifeq ($(ARCH),arm)
|
||||
ARM_CPU_FEATURES:=$(word 2,$(subst +,$(space),$(call qstrip,$(CONFIG_CPU_TYPE))))
|
||||
ifeq ($(ARM_CPU_FEATURES),)
|
||||
TARGET_CFLAGS:=
|
||||
TARGET_LDFLAGS:=
|
||||
endif
|
||||
endif
|
||||
|
||||
ifneq ($(CONFIG_USE_MUSL),)
|
||||
TARGET_CFLAGS += -D_LARGEFILE64_SOURCE
|
||||
endif
|
||||
|
||||
define Build/Prepare
|
||||
$(call Build/Prepare/Default)
|
||||
$(TAR) --strip-components=1 -C $(PKG_BUILD_DIR)/public/dist -xzf $(DL_DIR)/$(PKG_NAME)-web-$(PKG_WEB_VERSION).tar.gz
|
||||
$(SED) 's_https://jsd.nn.ci/gh/alist-org/logo@main/logo.png_/assets/logo.png_g' $(PKG_BUILD_DIR)/public/dist/index.html
|
||||
ifneq ($(CONFIG_ARCH_64BIT),y)
|
||||
$(RM) -rf $(PKG_BUILD_DIR)/drivers/{lark,lark.go}
|
||||
endif
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/install
|
||||
$(call GoPackage/Package/Install/Bin,$(PKG_INSTALL_DIR))
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/alist $(1)/usr/bin
|
||||
$(INSTALL_DIR) $(1)/etc/config $(1)/etc/init.d $(1)/etc/alist
|
||||
$(INSTALL_CONF) $(CURDIR)/files/alist.config $(1)/etc/config/alist
|
||||
$(INSTALL_BIN) $(CURDIR)/files/alist.init $(1)/etc/init.d/alist
|
||||
$(INSTALL_DATA) $(CURDIR)/files/data.db $(1)/etc/alist/data.db
|
||||
endef
|
||||
|
||||
$(eval $(call Download,$(PKG_NAME)-web))
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
31
alist/files/alist.config
Normal file
31
alist/files/alist.config
Normal file
@ -0,0 +1,31 @@
|
||||
|
||||
config alist
|
||||
option enabled '0'
|
||||
option port '5244'
|
||||
option delayed_start '0'
|
||||
option allow_wan '0'
|
||||
option force '1'
|
||||
option token_expires_in '48'
|
||||
option max_connections '0'
|
||||
option tls_insecure_skip_verify '1'
|
||||
option data_dir '/etc/alist'
|
||||
option temp_dir '/tmp/alist'
|
||||
option log '1'
|
||||
option log_max_size '10'
|
||||
option log_max_backups '5'
|
||||
option log_max_age '28'
|
||||
option log_compress '0'
|
||||
option database_type 'sqlite3'
|
||||
option ssl '0'
|
||||
option download_workers '5'
|
||||
option download_max_retry '1'
|
||||
option transfer_workers '5'
|
||||
option transfer_max_retry '2'
|
||||
option upload_workers '5'
|
||||
option upload_max_retry '0'
|
||||
option copy_workers '5'
|
||||
option copy_max_retry '2'
|
||||
option cors_allow_origins '*'
|
||||
option cors_allow_methods '*'
|
||||
option cors_allow_headers '*'
|
||||
option s3 '0'
|
||||
246
alist/files/alist.init
Executable file
246
alist/files/alist.init
Executable file
@ -0,0 +1,246 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
. /usr/share/libubox/jshn.sh
|
||||
|
||||
START=99
|
||||
USE_PROCD=1
|
||||
PROG=/usr/bin/alist
|
||||
LOG_FILE=/var/log/alist.log
|
||||
|
||||
get_config() {
|
||||
config_get_bool enabled $1 enabled 1
|
||||
config_get port $1 port 5244
|
||||
config_get allow_wan $1 allow_wan 0
|
||||
config_get delayed_start $1 delayed_start 0
|
||||
|
||||
config_get force $1 force 1
|
||||
config_get site_url $1 site_url ""
|
||||
config_get cdn $1 cdn ""
|
||||
config_get jwt_secret $1 jwt_secret ""
|
||||
config_get data_dir $1 data_dir "/etc/alist"
|
||||
config_get temp_dir $1 temp_dir "/tmp/alist"
|
||||
config_get token_expires_in $1 token_expires_in 48
|
||||
config_get max_connections $1 max_connections 0
|
||||
config_get tls_insecure_skip_verify $1 tls_insecure_skip_verify 1
|
||||
|
||||
# log
|
||||
config_get log $1 log 1
|
||||
config_get log_max_size $1 log_max_size 10
|
||||
config_get log_max_backups $1 log_max_backups 5
|
||||
config_get log_max_age $1 log_max_age 28
|
||||
config_get log_compress $1 log_compress 0
|
||||
|
||||
# scheme
|
||||
config_get ssl $1 ssl 0
|
||||
config_get force_https $1 force_https 0
|
||||
config_get ssl_cert $1 ssl_cert ""
|
||||
config_get ssl_key $1 ssl_key ""
|
||||
|
||||
# database
|
||||
config_get database_type $1 database_type "sqlite3"
|
||||
config_get mysql_host $1 mysql_host ""
|
||||
config_get mysql_port $1 mysql_port "3306"
|
||||
config_get mysql_username $1 mysql_username ""
|
||||
config_get mysql_password $1 mysql_password ""
|
||||
config_get mysql_database $1 mysql_database ""
|
||||
config_get mysql_table_prefix $1 mysql_table_prefix "x_"
|
||||
config_get mysql_ssl_mode $1 mysql_ssl_mode ""
|
||||
config_get mysql_dsn $1 mysql_dsn ""
|
||||
|
||||
# tasks
|
||||
config_get download_workers $1 download_workers 5
|
||||
config_get download_max_retry $1 download_max_retry 1
|
||||
config_get transfer_workers $1 transfer_workers 5
|
||||
config_get transfer_max_retry $1 transfer_max_retry 2
|
||||
config_get upload_workers $1 upload_workers 5
|
||||
config_get upload_max_retry $1 upload_max_retry 0
|
||||
config_get copy_workers $1 copy_workers 5
|
||||
config_get copy_max_retry $1 copy_max_retry 2
|
||||
|
||||
# cors
|
||||
config_get cors_allow_origins $1 cors_allow_origins '*'
|
||||
config_get cors_allow_methods $1 cors_allow_methods '*'
|
||||
config_get cors_allow_headers $1 cors_allow_headers '*'
|
||||
|
||||
# s3
|
||||
config_get s3 $1 s3 0
|
||||
config_get s3_port $1 s3_port 5246
|
||||
config_get s3_ssl $1 s3_ssl 0
|
||||
|
||||
config_load network
|
||||
config_get lan_addr lan ipaddr "0.0.0.0"
|
||||
if echo "${lan_addr}" | grep -Fq ' '; then
|
||||
lan_addr="0.0.0.0"
|
||||
else
|
||||
lan_addr=${lan_addr%%/*}
|
||||
fi
|
||||
|
||||
# init jwt_secret
|
||||
[ -z "$jwt_secret" ] && jwt_secret=$(tr -cd "a-zA-Z0-9" < "/dev/urandom" | head -c16)
|
||||
uci -q set alist.@alist[0].jwt_secret="$jwt_secret"
|
||||
uci commit alist
|
||||
}
|
||||
|
||||
set_firewall() {
|
||||
if [ "$external_access" = "allow" ]; then
|
||||
uci -q delete firewall.alist
|
||||
uci set firewall.alist=rule
|
||||
uci set firewall.alist.name="alist"
|
||||
uci set firewall.alist.target="ACCEPT"
|
||||
uci set firewall.alist.src="wan"
|
||||
uci set firewall.alist.proto="tcp"
|
||||
uci set firewall.alist.dest_port="$port"
|
||||
uci set firewall.alist.enabled="1"
|
||||
uci commit firewall
|
||||
/etc/init.d/firewall reload >/dev/null 2>&1
|
||||
elif [ "$external_access" = "deny" ]; then
|
||||
uci -q delete firewall.alist
|
||||
uci commit firewall
|
||||
/etc/init.d/firewall reload >/dev/null 2>&1
|
||||
fi
|
||||
}
|
||||
|
||||
start_service() {
|
||||
config_load alist
|
||||
config_foreach get_config alist
|
||||
[ $enabled -ne 1 ] && return 1
|
||||
mkdir -p "$temp_dir" "$data_dir"
|
||||
[ "$ssl" -eq 1 ] && https_port=$port http_port="-1" || https_port="-1" http_port=$port
|
||||
if [ -e /proc/uptime ]; then
|
||||
[ $(awk -F. '{print $1}' /proc/uptime) -lt "120" ] && delayed_start=$delayed_start || delayed_start=0
|
||||
else
|
||||
delayed_start=$delayed_start
|
||||
fi
|
||||
if [ "$allow_wan" -eq "1" ]; then
|
||||
listen_addr="0.0.0.0"
|
||||
external_access="allow"
|
||||
else
|
||||
listen_addr=$lan_addr
|
||||
external_access="deny"
|
||||
fi
|
||||
|
||||
set_firewall
|
||||
> $LOG_FILE
|
||||
|
||||
# init config
|
||||
json_init
|
||||
json_add_boolean "force" "$force"
|
||||
json_add_string "site_url" "$site_url"
|
||||
json_add_string "cdn" "$cdn"
|
||||
json_add_string "jwt_secret" "$jwt_secret"
|
||||
json_add_int "token_expires_in" "$token_expires_in"
|
||||
|
||||
# database
|
||||
json_add_object 'database'
|
||||
json_add_string "type" "$database_type"
|
||||
json_add_string "host" "$mysql_host"
|
||||
json_add_int "port" "$mysql_port"
|
||||
json_add_string "user" "$mysql_username"
|
||||
json_add_string "password" "$mysql_password"
|
||||
json_add_string "name" "$mysql_database"
|
||||
json_add_string "db_file" "$data_dir/data.db"
|
||||
json_add_string "table_prefix" "$mysql_table_prefix"
|
||||
json_add_string "ssl_mode" "$mysql_ssl_mode"
|
||||
json_add_string "dsn" "$mysql_dsn"
|
||||
json_close_object
|
||||
|
||||
# meilisearch
|
||||
json_add_object "meilisearch"
|
||||
json_add_string "host" "http://localhost:7700"
|
||||
json_add_string "api_key" ""
|
||||
json_add_string "index_prefix" ""
|
||||
json_close_object
|
||||
|
||||
# scheme
|
||||
json_add_object "scheme"
|
||||
json_add_string "address" "$listen_addr"
|
||||
json_add_int "http_port" "$http_port"
|
||||
json_add_int "https_port" "$https_port"
|
||||
json_add_boolean "force_https" "$force_https"
|
||||
json_add_string "cert_file" "$ssl_cert"
|
||||
json_add_string "key_file" "$ssl_key"
|
||||
json_add_string "unix_file" ""
|
||||
json_add_string "unix_file_perm" ""
|
||||
json_close_object
|
||||
|
||||
json_add_string "temp_dir" "$temp_dir"
|
||||
json_add_string "bleve_dir" "$data_dir/bleve"
|
||||
json_add_string "dist_dir" ""
|
||||
|
||||
# log
|
||||
json_add_object "log"
|
||||
json_add_boolean "enable" "$log"
|
||||
json_add_string "name" "$LOG_FILE"
|
||||
json_add_int "max_size" "$log_max_size"
|
||||
json_add_int "max_backups" "$log_max_backups"
|
||||
json_add_int "max_age" "$log_max_age"
|
||||
json_add_boolean "compress" "$log_compress"
|
||||
json_close_object
|
||||
|
||||
json_add_int "delayed_start" "$delayed_start"
|
||||
json_add_int "max_connections" "$max_connections"
|
||||
json_add_boolean "tls_insecure_skip_verify" "$tls_insecure_skip_verify"
|
||||
|
||||
# tasks
|
||||
json_add_object "tasks"
|
||||
json_add_object "download"
|
||||
json_add_int "workers" "$download_workers"
|
||||
json_add_int "max_retry" "$download_max_retry"
|
||||
json_close_object
|
||||
json_add_object "transfer"
|
||||
json_add_int "workers" "$transfer_workers"
|
||||
json_add_int "max_retry" "$transfer_max_retry"
|
||||
json_close_object
|
||||
json_add_object "upload"
|
||||
json_add_int "workers" "$upload_workers"
|
||||
json_add_int "max_retry" "$upload_max_retry"
|
||||
json_close_object
|
||||
json_add_object "copy"
|
||||
json_add_int "workers" "$copy_workers"
|
||||
json_add_int "max_retry" "$copy_max_retry"
|
||||
json_close_object
|
||||
json_close_object
|
||||
|
||||
# cors
|
||||
json_add_object "cors"
|
||||
json_add_array "allow_origins"
|
||||
json_add_string "" "$cors_allow_origins"
|
||||
json_close_array
|
||||
json_add_array "allow_methods"
|
||||
json_add_string "" "$cors_allow_methods"
|
||||
json_close_array
|
||||
json_add_array "allow_headers"
|
||||
json_add_string "" "$cors_allow_headers"
|
||||
json_close_array
|
||||
json_close_object
|
||||
|
||||
# s3
|
||||
json_add_object "s3"
|
||||
json_add_boolean "enable" "$s3"
|
||||
json_add_int "port" "$s3_port"
|
||||
json_add_boolean "ssl" "$s3_ssl"
|
||||
json_close_object
|
||||
|
||||
json_dump > "$data_dir/config.json"
|
||||
|
||||
procd_open_instance alist
|
||||
procd_set_param command $PROG
|
||||
procd_append_param command server
|
||||
procd_append_param command --data "$data_dir"
|
||||
procd_set_param file "$data_dir/config.json"
|
||||
procd_set_param stdout 0
|
||||
procd_set_param stderr 0
|
||||
procd_set_param respawn
|
||||
procd_set_param limits core="unlimited"
|
||||
procd_set_param limits nofile="200000 200000"
|
||||
procd_close_instance alist
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger "alist"
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
external_access="deny"
|
||||
set_firewall
|
||||
}
|
||||
BIN
alist/files/data.db
Normal file
BIN
alist/files/data.db
Normal file
Binary file not shown.
10
alist/src/public/dist/assets/logo.svg
vendored
Normal file
10
alist/src/public/dist/assets/logo.svg
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
<svg width="1252" height="1252" xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
<g>
|
||||
<g id="#70c6beff">
|
||||
<path id="svg_2" d="m634.37,138.38c11.88,-1.36 24.25,1.3 34.18,8.09c14.96,9.66 25.55,24.41 34.49,39.51c40.59,68.03 81.45,135.91 122.02,203.96c54.02,90.99 108.06,181.97 161.94,273.06c37.28,63 74.65,125.96 112.18,188.82c24.72,41.99 50.21,83.54 73.84,126.16c10.18,17.84 15.77,38.44 14.93,59.03c-0.59,15.92 -3.48,32.28 -11.84,46.08c-11.73,19.46 -31.39,33.2 -52.71,40.36c-11.37,4.09 -23.3,6.87 -35.43,6.89c-132.32,-0.05 -264.64,0.04 -396.95,0.03c-11.38,-0.29 -22.95,-1.6 -33.63,-5.72c-7.81,-3.33 -15.5,-7.43 -21.61,-13.42c-10.43,-10.32 -17.19,-24.96 -15.38,-39.83c0.94,-10.39 3.48,-20.64 7.76,-30.16c4.15,-9.77 9.99,-18.67 15.06,-27.97c22.13,-39.47 45.31,-78.35 69.42,-116.65c7.72,-12.05 14.44,-25.07 25.12,-34.87c11.35,-10.39 25.6,-18.54 41.21,-19.6c12.55,-0.52 24.89,3.82 35.35,10.55c11.8,6.92 21.09,18.44 24.2,31.88c4.49,17.01 -0.34,34.88 -7.55,50.42c-8.09,17.65 -19.62,33.67 -25.81,52.18c-1.13,4.21 -2.66,9.52 0.48,13.23c3.19,3 7.62,4.18 11.77,5.22c12,2.67 24.38,1.98 36.59,2.06c45,-0.01 90,0 135,0c8.91,-0.15 17.83,0.3 26.74,-0.22c6.43,-0.74 13.44,-1.79 18.44,-6.28c3.3,-2.92 3.71,-7.85 2.46,-11.85c-2.74,-8.86 -7.46,-16.93 -12.12,-24.89c-119.99,-204.91 -239.31,-410.22 -360.56,-614.4c-3.96,-6.56 -7.36,-13.68 -13.03,-18.98c-2.8,-2.69 -6.95,-4.22 -10.77,-3.11c-3.25,1.17 -5.45,4.03 -7.61,6.57c-5.34,6.81 -10.12,14.06 -14.51,21.52c-20.89,33.95 -40.88,68.44 -61.35,102.64c-117.9,198.43 -235.82,396.85 -353.71,595.29c-7.31,13.46 -15.09,26.67 -23.57,39.43c-7.45,10.96 -16.49,21.23 -28.14,27.83c-13.73,7.94 -30.69,11.09 -46.08,6.54c-11.23,-3.47 -22.09,-9.12 -30.13,-17.84c-10.18,-10.08 -14.69,-24.83 -14.17,-38.94c0.52,-14.86 5.49,-29.34 12.98,-42.1c71.58,-121.59 143.62,-242.92 215.93,-364.09c37.2,-62.8 74.23,-125.69 111.64,-188.36c37.84,-63.5 75.77,-126.94 113.44,-190.54c21.02,-35.82 42.19,-71.56 64.28,-106.74c6.79,-11.15 15.58,-21.15 26.16,-28.85c8.68,-5.92 18.42,-11 29.05,-11.94z" fill="#70c6be"/>
|
||||
</g>
|
||||
<g id="#1ba0d8ff">
|
||||
<path id="svg_3" d="m628.35,608.38c17.83,-2.87 36.72,1.39 51.5,11.78c11.22,8.66 19.01,21.64 21.26,35.65c1.53,10.68 0.49,21.75 -3.44,31.84c-3.02,8.73 -7.35,16.94 -12.17,24.81c-68.76,115.58 -137.5,231.17 -206.27,346.75c-8.8,14.47 -16.82,29.47 -26.96,43.07c-7.37,9.11 -16.58,16.85 -27.21,21.89c-22.47,11.97 -51.79,4.67 -68.88,-13.33c-8.66,-8.69 -13.74,-20.63 -14.4,-32.84c-0.98,-12.64 1.81,-25.42 7.53,-36.69c5.03,-10.96 10.98,-21.45 17.19,-31.77c30.22,-50.84 60.17,-101.84 90.3,-152.73c41.24,-69.98 83.16,-139.55 124.66,-209.37c4.41,-7.94 9.91,-15.26 16.09,-21.9c8.33,-8.46 18.9,-15.3 30.8,-17.16z" fill="#1ba0d8"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
46
baidupcs-go/Makefile
Normal file
46
baidupcs-go/Makefile
Normal file
@ -0,0 +1,46 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# Copyright (C) 2009-2013 OpenWrt.org
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=BaiduPCS-Go
|
||||
PKG_VERSION:=3.9.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/qjfoidnh/BaiduPCS-Go/tar.gz/v$(PKG_VERSION)?
|
||||
PKG_HASH:=de71666e961a3eeb90f8aca7b11934c3f0296d1870e036a7c086ba1decfca8ab
|
||||
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
|
||||
PKG_BUILD_DEPENDS:=golang/host
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_USE_MIPS16:=0
|
||||
|
||||
GO_PKG:=github.com/qjfoidnh/BaiduPCS-Go
|
||||
GO_PKG_LDFLAGS_X:=main.Version=v$(PKG_VERSION)
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include ../../lang/golang/golang-package.mk
|
||||
|
||||
define Package/BaiduPCS-Go
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=Download Manager
|
||||
TITLE:=Terminal utility for Baidu Network Disk
|
||||
URL:=https://github.com/qjfoidnh/BaiduPCS-Go
|
||||
DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle
|
||||
endef
|
||||
|
||||
define Package/BaiduPCS-Go/install
|
||||
$(call GoPackage/Package/Install/Bin,$(PKG_INSTALL_DIR))
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/bin/
|
||||
$(CP) $(PKG_INSTALL_DIR)/usr/bin/BaiduPCS-Go $(1)/usr/bin/
|
||||
endef
|
||||
|
||||
$(eval $(call GoBinPackage,BaiduPCS-Go))
|
||||
$(eval $(call BuildPackage,BaiduPCS-Go))
|
||||
57
brlaser/Makefile
Normal file
57
brlaser/Makefile
Normal file
@ -0,0 +1,57 @@
|
||||
#
|
||||
# Copyright (C) 2006-2009 OpenWrt.org
|
||||
# Copyright (C) 2005-2016 Weijie Gao <hackpascal@gmail.com>
|
||||
#
|
||||
# This is free software, licensed under the GNU General Public License v2.
|
||||
# See /LICENSE for more information.
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=brlaser
|
||||
PKG_VERSION:=v3
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE_URL:=https://github.com/pdewacht/brlaser.git
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
||||
PKG_SOURCE_VERSION:=2a49e3287c70c254e7e3ac9dabe9d6a07218c3fa
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION)-$(PKG_SOURCE_VERSION).tar.gz
|
||||
|
||||
PKG_FIXUP:=autoreconf
|
||||
|
||||
PKG_INSTALL:=1
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include $(INCLUDE_DIR)/cmake.mk
|
||||
|
||||
define Package/brlaser
|
||||
SECTION:=print
|
||||
CATEGORY:=Printing and Imaging
|
||||
TITLE:=Driver for (some) Brother laster printers
|
||||
DEPENDS:=+libcupsimage +libstdcpp
|
||||
MAINTAINER:=Weijie Gao <hackpascal@gmail.com>
|
||||
URL:=https://github.com/pdewacht/brlaser
|
||||
endef
|
||||
|
||||
define Package/brlaser/description
|
||||
Driver for Brother DCP-7030 / Brother DCP-7055 / Brother DCP-7065DN
|
||||
endef
|
||||
|
||||
define Package/brlaser/install
|
||||
$(INSTALL_DIR) $(1)/usr/lib/cups/filter
|
||||
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/lib/cups/filter/rastertobrlaser $(1)/usr/lib/cups/filter/
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/share/cups/drv
|
||||
$(INSTALL_DATA) $(PKG_INSTALL_DIR)/usr/share/cups/drv/brlaser.drv $(1)/usr/share/cups/drv/
|
||||
|
||||
(cd $(PKG_BUILD_DIR) && ppdc brlaser.drv)
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/share/ppd/Brother
|
||||
$(CP) $(PKG_BUILD_DIR)/ppd/*.ppd $(1)/usr/share/ppd/Brother
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/uci-defaults
|
||||
$(INSTALL_BIN) ./files/brlaser-set-cups-filter-permission $(1)/etc/uci-defaults/
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,brlaser))
|
||||
3
brlaser/files/brlaser-set-cups-filter-permission
Normal file
3
brlaser/files/brlaser-set-cups-filter-permission
Normal file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
[ -d /usr/lib/cups/filter ] && chmod 0754 /usr/lib/cups/filter/*
|
||||
45
brook/Makefile
Normal file
45
brook/Makefile
Normal file
@ -0,0 +1,45 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
#
|
||||
# Copyright (C) 2021-2023 ImmortalWrt.org
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=brook
|
||||
PKG_VERSION:=20260101.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/txthinking/brook/tar.gz/v$(PKG_VERSION)?
|
||||
PKG_HASH:=8ddba4ed9ae9d10928e169f8121c6791e0e3c2907fa27d6e0055fe434f6e700e
|
||||
|
||||
PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
|
||||
PKG_LICENSE:=GPL-3.0
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
|
||||
PKG_BUILD_DEPENDS:=golang/host
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_USE_MIPS16:=0
|
||||
PKG_BUILD_FLAGS:=no-mips16
|
||||
|
||||
GO_PKG:=github.com/txthinking/brook
|
||||
GO_PKG_BUILD_PKG:=$(GO_PKG)/cli/brook
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
|
||||
|
||||
define Package/brook
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=Web Servers/Proxies
|
||||
TITLE:=A cross-platform proxy software
|
||||
DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle
|
||||
URL:=https://github.com/txthinking/brook
|
||||
endef
|
||||
|
||||
define Package/brook/description
|
||||
Brook is a cross-platform strong encryption and not detectable proxy.
|
||||
Zero-Configuration.
|
||||
endef
|
||||
|
||||
$(eval $(call GoBinPackage,brook))
|
||||
$(eval $(call BuildPackage,brook))
|
||||
72
chinadns-ng/Makefile
Normal file
72
chinadns-ng/Makefile
Normal file
@ -0,0 +1,72 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=chinadns-ng
|
||||
PKG_VERSION:=2025.08.09
|
||||
PKG_RELEASE:=1
|
||||
|
||||
ifeq ($(ARCH),aarch64)
|
||||
ifeq ($(BOARD),rockchip)
|
||||
PKG_ARCH:=chinadns-ng+wolfssl@aarch64-linux-musl@generic+v8a@fast+lto
|
||||
PKG_HASH:=3fe0217615dd7060b7287d2b6b31d2a0b364137398bfb335a03bead322eac716
|
||||
else
|
||||
PKG_ARCH:=chinadns-ng+wolfssl_noasm@aarch64-linux-musl@generic+v8a@fast+lto
|
||||
PKG_HASH:=42ddd494200ec6d88b35902927688d316bc23e06e6c08d9e01eb2412196ab845
|
||||
endif
|
||||
else ifeq ($(ARCH),arm)
|
||||
ARM_CPU_FEATURES:=$(word 2,$(subst +,$(space),$(call qstrip,$(CONFIG_CPU_TYPE))))
|
||||
ifeq ($(ARM_CPU_FEATURES),)
|
||||
PKG_ARCH:=chinadns-ng+wolfssl@arm-linux-musleabi@generic+v6+soft_float@fast+lto
|
||||
PKG_HASH:=0a401d1dc11129481b2baf86f847d55d66bd7e725cba4bf57875fdad27ef0052
|
||||
else
|
||||
PKG_ARCH:=chinadns-ng+wolfssl@arm-linux-musleabihf@generic+v7a@fast+lto
|
||||
PKG_HASH:=dfa1f6ba80fb0925613822f4c4e00df8da68e7b8b772048d26a0d1a9d07d346b
|
||||
endif
|
||||
else ifeq ($(ARCH),mips)
|
||||
PKG_ARCH:=chinadns-ng+wolfssl@mips-linux-musl@mips32+soft_float@fast+lto
|
||||
PKG_HASH:=b610821a8f61b0ed3c8c7e82e10d401348a9de17f900988589024a37c4099c8e
|
||||
else ifeq ($(ARCH),mipsel)
|
||||
PKG_ARCH:=chinadns-ng+wolfssl@mipsel-linux-musl@mips32+soft_float@fast+lto
|
||||
PKG_HASH:=760544a88724e3b1b9eac79c9400231e81aa8786f8f00a979229e175811ffe6d
|
||||
else ifeq ($(ARCH),mips64)
|
||||
PKG_ARCH:=chinadns-ng+wolfssl@mips64-linux-musl@mips64+soft_float@fast+lto
|
||||
PKG_HASH:=2d0fce18a7ef1d74fdc12738767e66998a52c2b30d8790da760933853fe8726e
|
||||
else ifeq ($(ARCH),i386)
|
||||
PKG_ARCH:=chinadns-ng+wolfssl@i386-linux-musl@i686@fast+lto
|
||||
PKG_HASH:=85e057dd0a0e8913b30471737436ab8b71834c494ed9f9e53544261b1ffdc8d6
|
||||
else ifeq ($(ARCH),x86_64)
|
||||
PKG_ARCH:=chinadns-ng+wolfssl@x86_64-linux-musl@x86_64@fast+lto
|
||||
PKG_HASH:=842ea4e9816efd91d39bc76ead5c4a42e79011757e37c521b4270b675cfcb30c
|
||||
else
|
||||
PKG_HASH:=dummy
|
||||
endif
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION)-$(PKG_ARCH)
|
||||
PKG_SOURCE_URL:=https://github.com/zfl9/chinadns-ng/releases/download/$(PKG_VERSION)/$(PKG_ARCH)?
|
||||
UNPACK_CMD=$(CP) $(DL_DIR)/$(PKG_SOURCE) $(PKG_BUILD_DIR)/$(PKG_NAME)
|
||||
|
||||
PKG_LICENSE:=AGPL-3.0-only
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
PKG_MAINTAINER:=sbwml <admin@cooluc.com>
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/chinadns-ng
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=IP Addresses and Names
|
||||
TITLE:=ChinaDNS next generation, refactoring with epoll and ipset.
|
||||
URL:=https://github.com/zfl9/chinadns-ng
|
||||
DEPENDS:=@(aarch64||arm||i386||mips||mipsel||mips64||x86_64) +ipset
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/chinadns-ng/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/chinadns-ng $(1)/usr/bin
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,chinadns-ng))
|
||||
46
daemonlogger/Makefile
Normal file
46
daemonlogger/Makefile
Normal file
@ -0,0 +1,46 @@
|
||||
#
|
||||
# Copyright (C) 2007-2011 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:=daemonlogger
|
||||
PKG_VERSION:=1.2.1
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=@SF/daemonlogger
|
||||
PKG_HASH:=79fcd34d815e9c671ffa1ea3c7d7d50f895bb7a79b4448c4fd1c37857cf44a0b
|
||||
|
||||
PKG_LICENSE:=GPL-2.0
|
||||
PKG_LICENSE_FILES:=COPYING
|
||||
|
||||
PKG_MAINTAINER:=Mirko Vogt <mirko@openwrt.org>
|
||||
|
||||
PKG_INSTALL:=1
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/daemonlogger
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
DEPENDS:=+libpcap +libdnet
|
||||
TITLE:=Software Network Tap
|
||||
URL:=http://www.snort.org/snort-downloads/additional-downloads
|
||||
endef
|
||||
|
||||
CONFIGURE_VARS += \
|
||||
BUILD_CC="$(TARGET_CC)" \
|
||||
HOSTCC="$(HOSTCC)"
|
||||
|
||||
MAKE_FLAGS := CCOPT="$(TARGET_CFLAGS)" INCLS="-I. $(TARGET_CPPFLAGS)"
|
||||
|
||||
define Package/daemonlogger/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/daemonlogger $(1)/usr/bin/
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,daemonlogger))
|
||||
53
dns2socks-rust/Makefile
Normal file
53
dns2socks-rust/Makefile
Normal file
@ -0,0 +1,53 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# Copyright (C) 2017-2024 Zxlhhyccc <zxlhhyccc@gmail.com>
|
||||
# Copyright (C) 2021-2024 ImmortalWrt.org
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=dns2socks-rust
|
||||
PKG_VERSION:=0.2.5
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/tun2proxy/dns2socks/tar.gz/v$(PKG_VERSION)?
|
||||
PKG_HASH:=53f62c38df51bc5f11c519549213a7fd0dcb33b595410b662ea63d480faced9a
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/dns2socks-$(PKG_VERSION)
|
||||
|
||||
PKG_MAINTAINER:=Zxlhhyccc <zxlhhyccc@gmail.com>
|
||||
PKG_LICENSE:=MIT
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
|
||||
PKG_BUILD_DEPENDS:=rust/host
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
|
||||
#RUST_PKG:=dns2socks
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include $(TOPDIR)/feeds/packages/lang/rust/rust-package.mk
|
||||
|
||||
define Package/dns2socks-rust
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=IP Addresses and Names
|
||||
TITLE:=DNS forwards to SOCKS5 server
|
||||
URL:=https://github.com/tun2proxy/dns2socks.git
|
||||
DEPENDS:=$$(RUST_ARCH_DEPENDS)
|
||||
endef
|
||||
|
||||
define Package/dns2socks-rust/description
|
||||
This is a DNS server that forwards DNS requests to a SOCKS5 server.
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
$(call Build/Compile/Cargo,,--all-features)
|
||||
endef
|
||||
|
||||
define Package/dns2socks-rust/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/target/$(RUSTC_TARGET_ARCH)/release/dns2socks $(1)/usr/bin/dns2socks-rust
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,dns2socks-rust))
|
||||
51
dns2socks/Makefile
Normal file
51
dns2socks/Makefile
Normal file
@ -0,0 +1,51 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
#
|
||||
# Copyright (C) 2021-2026 ImmortalWrt.org
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=dns2socks
|
||||
PKG_VERSION:=2.2
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/kenzok8/dns2socks/tar.gz/v$(PKG_VERSION)?
|
||||
PKG_HASH:=790f7ca5ec07677a699121eb071c9e66cdafe65a22bb229eed7fd3cf215631d2
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
|
||||
|
||||
PKG_LICENSE:=BSD-3-Clause
|
||||
PKG_LICENSE_FILE:=LICENSE
|
||||
PKG_MAINTAINER:=ghostmaker
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/dns2socks
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=IP Addresses and Names
|
||||
TITLE:=DNS to SOCKS or HTTP proxy
|
||||
URL:=https://github.com/kenzok8/dns2socks
|
||||
DEPENDS:=+libpthread
|
||||
endef
|
||||
|
||||
define Package/dns2socks/description
|
||||
This is a command line utility to resolve DNS requests via
|
||||
a SOCKS tunnel like Tor or a HTTP proxy.
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
$(TARGET_CC) \
|
||||
$(TARGET_CFLAGS) \
|
||||
$(TARGET_CPPFLAGS) \
|
||||
$(FPIC) \
|
||||
-o $(PKG_BUILD_DIR)/dns2socks \
|
||||
$(PKG_BUILD_DIR)/src/dns2socks.c \
|
||||
$(TARGET_LDFLAGS) -pthread
|
||||
endef
|
||||
|
||||
define Package/dns2socks/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/dns2socks $(1)/usr/bin/dns2socks
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,dns2socks))
|
||||
42
dns2tcp/Makefile
Normal file
42
dns2tcp/Makefile
Normal file
@ -0,0 +1,42 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
#
|
||||
# Copyright (C) 2022 ImmortalWrt.org
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=dns2tcp
|
||||
PKG_VERSION:=1.1.2
|
||||
PKG_RELEASE:=2
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/zfl9/dns2tcp/tar.gz/v$(PKG_VERSION)?
|
||||
PKG_HASH:=5e8c6302a1d32c16ae7d4b8e39cd9aad1f2d7e68fe18813e76cb1e48ec5940d2
|
||||
|
||||
PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
|
||||
PKG_LICENSE:=AGPL-3.0-only
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_BUILD_FLAGS:=no-mips16 gc-sections lto
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/dns2tcp
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=IP Addresses and Names
|
||||
TITLE:=utility to convert dns query from udp to tcp
|
||||
URL:=https://github.com/zfl9/dns2tcp
|
||||
endef
|
||||
|
||||
TARGET_CFLAGS+= $(FPIC)
|
||||
MAKE_FLAGS+= \
|
||||
CFLAGS="-std=c99 $(TARGET_CFLAGS)" \
|
||||
EVCFLAGS="$(TARGET_CFLAGS)"
|
||||
|
||||
define Package/dns2tcp/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/dns2tcp $(1)/usr/bin/
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,dns2tcp))
|
||||
74
docker/Makefile
Normal file
74
docker/Makefile
Normal file
@ -0,0 +1,74 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=docker
|
||||
PKG_VERSION:=29.5.2
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/docker/cli/tar.gz/v$(PKG_VERSION)?
|
||||
PKG_HASH:=e5cf80d9e0b6630bb2e97decf664ad5f0c732b296b7c47030044c3a7287bbddb
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/cli-$(PKG_VERSION)
|
||||
PKG_GIT_SHORT_COMMIT:=$(shell $(CURDIR)/git-short-commit.sh 'github.com/docker/cli' 'v$(PKG_VERSION)' '$(TMP_DIR)/git-short-commit/$(PKG_NAME)-$(PKG_VERSION)')
|
||||
|
||||
PKG_MAINTAINER:=Gerard Ryan <G.M0N3Y.2503@gmail.com>
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
|
||||
PKG_BUILD_DEPENDS:=golang/host
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_BUILD_FLAGS:=no-mips16
|
||||
|
||||
GO_PKG:=github.com/docker/cli
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include ../../lang/golang/golang-package.mk
|
||||
|
||||
define Package/docker
|
||||
SECTION:=utils
|
||||
CATEGORY:=Utilities
|
||||
TITLE:=Docker Community Edition CLI
|
||||
URL:=https://www.docker.com/
|
||||
DEPENDS:=$(GO_ARCH_DEPENDS)
|
||||
endef
|
||||
|
||||
define Package/docker/description
|
||||
The CLI used in the Docker CE and Docker EE products.
|
||||
endef
|
||||
|
||||
GO_PKG_INSTALL_EXTRA:=\
|
||||
cli/compose/schema/data \
|
||||
vendor/google.golang.org/protobuf/internal/editiondefaults/editions_defaults.binpb
|
||||
|
||||
TAR_OPTIONS:=--strip-components 1 $(TAR_OPTIONS)
|
||||
TAR_CMD=$(HOST_TAR) -C $(1) $(TAR_OPTIONS)
|
||||
TARGET_LDFLAGS += $(if $(CONFIG_USE_GLIBC),-lc -lgcc_eh)
|
||||
|
||||
define Build/Prepare
|
||||
$(Build/Prepare/Default)
|
||||
|
||||
# Verify PKG_GIT_SHORT_COMMIT
|
||||
( \
|
||||
EXPECTED_PKG_GIT_SHORT_COMMIT=$$$$( $(CURDIR)/git-short-commit.sh 'github.com/docker/cli' 'v$(PKG_VERSION)' '$(TMP_DIR)/git-short-commit/$(PKG_NAME)-$(PKG_VERSION)' ); \
|
||||
if [ "$$$${EXPECTED_PKG_GIT_SHORT_COMMIT}" != "$(strip $(PKG_GIT_SHORT_COMMIT))" ]; then \
|
||||
echo "ERROR: Expected 'PKG_GIT_SHORT_COMMIT:=$$$${EXPECTED_PKG_GIT_SHORT_COMMIT}', found 'PKG_GIT_SHORT_COMMIT:=$(strip $(PKG_GIT_SHORT_COMMIT))'"; \
|
||||
exit 1; \
|
||||
fi \
|
||||
)
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
( \
|
||||
cd $(PKG_BUILD_DIR); \
|
||||
$(GO_PKG_VARS) \
|
||||
GITCOMMIT=$(PKG_GIT_SHORT_COMMIT) \
|
||||
VERSION=$(PKG_VERSION) \
|
||||
./scripts/build/binary; \
|
||||
)
|
||||
endef
|
||||
|
||||
define Package/docker/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin/
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/build/docker $(1)/usr/bin/
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,docker))
|
||||
49
docker/git-short-commit.sh
Normal file
49
docker/git-short-commit.sh
Normal file
@ -0,0 +1,49 @@
|
||||
|
||||
#!/bin/sh
|
||||
#
|
||||
# USAGE: git-short-commit.sh <GIT_URL> <GIT_REF> <GIT_DIR>
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
error() {
|
||||
echo "ERROR: ${*}" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
GIT_URL="${1}"
|
||||
if [ -z "${GIT_URL}" ]; then
|
||||
error "Git URL not specified"
|
||||
fi
|
||||
|
||||
GIT_REF="${2}"
|
||||
if [ -z "${GIT_REF}" ]; then
|
||||
error "Git reference not specified"
|
||||
fi
|
||||
|
||||
GIT_DIR="${3}"
|
||||
if [ -z "${GIT_DIR}" ]; then
|
||||
error "Git clone directory not specified"
|
||||
fi
|
||||
|
||||
clean_up() {
|
||||
rm -rf "${GIT_DIR}"
|
||||
}
|
||||
trap clean_up EXIT
|
||||
|
||||
git init --quiet "${GIT_DIR}"
|
||||
(
|
||||
cd "${GIT_DIR}"
|
||||
for PREFIX in "" "https://" "http://" "git@"; do
|
||||
echo "Trying remote '${PREFIX}${GIT_URL}'" >&2
|
||||
git remote add origin "${PREFIX}${GIT_URL}"
|
||||
|
||||
if git fetch --depth 1 origin "${GIT_REF}"; then
|
||||
git checkout --detach FETCH_HEAD --
|
||||
git rev-parse --short HEAD
|
||||
break
|
||||
fi
|
||||
|
||||
git remote remove origin
|
||||
done
|
||||
)
|
||||
87
dockerd/Config.in
Normal file
87
dockerd/Config.in
Normal file
@ -0,0 +1,87 @@
|
||||
config DOCKER_CHECK_CONFIG
|
||||
bool "Installs check-config.sh with dependencies"
|
||||
default n
|
||||
depends on PACKAGE_dockerd
|
||||
select PACKAGE_bash
|
||||
select PACKAGE_kmod-ikconfig
|
||||
|
||||
# These options are mostly specified by https://github.com/moby/moby/blob/master/contrib/check-config.sh
|
||||
|
||||
config DOCKER_CGROUP_OPTIONS
|
||||
bool "Enable available kernel support for CGroupsV1"
|
||||
default y
|
||||
depends on PACKAGE_dockerd
|
||||
select KERNEL_CGROUP_DEVICE
|
||||
select KERNEL_CGROUP_FREEZER
|
||||
select KERNEL_NET_CLS_CGROUP
|
||||
select KERNEL_CGROUP_NET_PRIO
|
||||
select PACKAGE_cgroupfs-mount
|
||||
help
|
||||
Selects kernel options to enable CGroups V1.
|
||||
|
||||
config DOCKER_OPTIONAL_FEATURES
|
||||
bool "Enable optional kernel support for Docker"
|
||||
default n
|
||||
depends on PACKAGE_dockerd
|
||||
select KERNEL_MEMCG_SWAP_ENABLED
|
||||
select KERNEL_CFQ_GROUP_IOSCHED
|
||||
select KERNEL_CGROUP_PERF
|
||||
select KERNEL_CGROUP_HUGETLB
|
||||
help
|
||||
Select 'Optional Features' kernel options for Docker that are unselected.
|
||||
See https://github.com/moby/moby/blob/master/contrib/check-config.sh
|
||||
|
||||
menu "Network"
|
||||
depends on PACKAGE_dockerd
|
||||
|
||||
config DOCKER_NET_OVERLAY
|
||||
bool "Includes the Overlay network feature"
|
||||
default n
|
||||
select PACKAGE_kmod-vxlan
|
||||
help
|
||||
Selects kmod-vxlan for the Overlay network feature.
|
||||
|
||||
config DOCKER_NET_ENCRYPT
|
||||
bool "Includes encrypted networking kernel modules"
|
||||
depends on DOCKER_NET_OVERLAY
|
||||
default n
|
||||
select PACKAGE_kmod-crypto-gcm
|
||||
select PACKAGE_kmod-crypto-seqiv
|
||||
select PACKAGE_kmod-crypto-ghash
|
||||
select PACKAGE_kmod-ipsec
|
||||
help
|
||||
Select needed kernel modules for encrypted networking support.
|
||||
|
||||
config DOCKER_NET_MACVLAN
|
||||
bool "Includes macvlan kernel modules"
|
||||
default y
|
||||
select PACKAGE_kmod-macvlan
|
||||
select PACKAGE_kmod-dummy
|
||||
|
||||
config DOCKER_NET_TFTP
|
||||
bool "Includes ftp/tftp client kernel modules"
|
||||
default n
|
||||
select PACKAGE_kmod-nf-nathelper
|
||||
select PACKAGE_kmod-nf-nathelper-extra
|
||||
endmenu
|
||||
|
||||
menu "Storage"
|
||||
depends on PACKAGE_dockerd
|
||||
|
||||
config DOCKER_STO_DEVMAPPER
|
||||
bool "Enables support for devmapper snapshotting"
|
||||
default n
|
||||
select PACKAGE_libdevmapper
|
||||
|
||||
config DOCKER_STO_EXT4
|
||||
bool "Enables support for ext3 or ext4 as the backing filesystem"
|
||||
default y
|
||||
select KERNEL_EXT4_FS_POSIX_ACL
|
||||
select KERNEL_EXT4_FS_SECURITY
|
||||
|
||||
config DOCKER_STO_BTRFS
|
||||
bool "Enables support for btrfs as the backing filesystem"
|
||||
default n
|
||||
select KERNEL_BTRFS_FS_POSIX_ACL
|
||||
select PACKAGE_btrfs-progs
|
||||
endmenu
|
||||
182
dockerd/Makefile
Normal file
182
dockerd/Makefile
Normal file
@ -0,0 +1,182 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=dockerd
|
||||
PKG_VERSION:=29.5.2
|
||||
PKG_RELEASE:=1
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_GIT_URL:=github.com/moby/moby
|
||||
PKG_GIT_REF:=docker-v$(PKG_VERSION)
|
||||
PKG_SOURCE_URL:=https://codeload.$(PKG_GIT_URL)/tar.gz/$(PKG_GIT_REF)?
|
||||
PKG_HASH:=1235ed325d324c76f52e52beaa1dac85c92a073bf2f9fcc5bb9f67e35a668028
|
||||
PKG_GIT_SHORT_COMMIT:=$(shell $(CURDIR)/git-short-commit.sh '$(PKG_GIT_URL)' '$(PKG_GIT_REF)' '$(TMP_DIR)/git-short-commit/$(PKG_NAME)-$(PKG_VERSION)')
|
||||
|
||||
PKG_MAINTAINER:=Gerard Ryan <G.M0N3Y.2503@gmail.com>
|
||||
|
||||
PKG_BUILD_DEPENDS:=golang/host
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_BUILD_FLAGS:=no-mips16
|
||||
|
||||
GO_PKG:=github.com/docker/docker
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include ../../lang/golang/golang-package.mk
|
||||
|
||||
define Package/dockerd/config
|
||||
source "$(SOURCE)/Config.in"
|
||||
endef
|
||||
|
||||
define Package/dockerd
|
||||
SECTION:=utils
|
||||
CATEGORY:=Utilities
|
||||
TITLE:=Docker Community Edition Daemon
|
||||
URL:=https://www.docker.com/
|
||||
DEPENDS:=$(GO_ARCH_DEPENDS) \
|
||||
+ca-certificates \
|
||||
+containerd \
|
||||
+iptables \
|
||||
+iptables-mod-extra \
|
||||
+IPV6:ip6tables \
|
||||
+IPV6:kmod-ipt-nat6 \
|
||||
+KERNEL_SECCOMP:libseccomp \
|
||||
+kmod-ipt-nat \
|
||||
+kmod-ipt-physdev \
|
||||
+kmod-nf-ipvs \
|
||||
+kmod-veth \
|
||||
+tini \
|
||||
+uci-firewall \
|
||||
@!(mips||mips64||mipsel)
|
||||
USERID:=docker:docker
|
||||
MENU:=1
|
||||
endef
|
||||
|
||||
define Package/dockerd/conffiles
|
||||
/etc/config/dockerd
|
||||
endef
|
||||
|
||||
define Package/dockerd/description
|
||||
The Docker CE Engine.
|
||||
endef
|
||||
|
||||
TAR_OPTIONS:=--strip-components 1 $(TAR_OPTIONS)
|
||||
TAR_CMD=$(HOST_TAR) -C $(1) $(TAR_OPTIONS)
|
||||
TARGET_LDFLAGS += $(if $(CONFIG_USE_GLIBC),-lc -lgcc_eh)
|
||||
|
||||
# $(1) = path to dependent package 'Makefile'
|
||||
# $(2) = relevant dependency '.installer' file
|
||||
define EnsureVendoredVersion
|
||||
( \
|
||||
DEP_VER=$$$$( grep --only-matching --perl-regexp '(?<=PKG_VERSION:=)(.*)' "$(1)" ); \
|
||||
VEN_VER=$$$$( grep --only-matching --perl-regexp '(?<=_VERSION:=v)(.*)(?=})' "$(PKG_BUILD_DIR)/hack/dockerfile/install/$(2)" ); \
|
||||
if [ "$$$${VEN_VER}" != "$$$${DEP_VER}" ]; then \
|
||||
echo "ERROR: Expected 'PKG_VERSION:=$$$${VEN_VER}' in '$(1)', found 'PKG_VERSION:=$$$${DEP_VER}'"; \
|
||||
exit 1; \
|
||||
fi \
|
||||
)
|
||||
endef
|
||||
|
||||
# $(1) = path to dependent package 'Makefile'
|
||||
# $(2) = relevant dependency '.installer' file
|
||||
define EnsureVendoredCommit
|
||||
( \
|
||||
DEP_VER=$$$$( grep --only-matching --perl-regexp '(?<=PKG_SOURCE_VERSION:=)(.*)' "$(1)" ); \
|
||||
VEN_VER=$$$$( grep --only-matching --perl-regexp '(?<=_COMMIT:=)(.*)(?=})' "$(PKG_BUILD_DIR)/hack/dockerfile/install/$(2)" ); \
|
||||
if [ "$$$${VEN_VER}" != "$$$${DEP_VER}" ]; then \
|
||||
echo "ERROR: Expected 'PKG_SOURCE_VERSION:=$$$${VEN_VER}' in '$(1)', found 'PKG_SOURCE_VERSION:=$$$${DEP_VER}'"; \
|
||||
exit 1; \
|
||||
fi \
|
||||
)
|
||||
endef
|
||||
|
||||
define Build/Prepare
|
||||
$(Build/Prepare/Default)
|
||||
|
||||
# Verify dependencies are the vendored version
|
||||
$(call EnsureVendoredVersion,../containerd/Makefile,containerd.installer)
|
||||
$(call EnsureVendoredVersion,../runc/Makefile,runc.installer)
|
||||
$(call EnsureVendoredVersion,../tini/Makefile,tini.installer)
|
||||
|
||||
# Verify CLI is the same version
|
||||
( \
|
||||
CLI_MAKEFILE="../docker/Makefile"; \
|
||||
CLI_VERSION=$$$$( grep --only-matching --perl-regexp '(?<=PKG_VERSION:=)(.*)' "$$$${CLI_MAKEFILE}" ); \
|
||||
if [ "$$$${CLI_VERSION}" != "$(PKG_VERSION)" ]; then \
|
||||
echo "ERROR: Expected 'PKG_VERSION:=$(PKG_VERSION)' in '$$$${CLI_MAKEFILE}', found 'PKG_VERSION:=$$$${CLI_VERSION}'"; \
|
||||
exit 1; \
|
||||
fi \
|
||||
)
|
||||
|
||||
# Verify PKG_GIT_SHORT_COMMIT
|
||||
( \
|
||||
EXPECTED_PKG_GIT_SHORT_COMMIT=$$$$( $(CURDIR)/git-short-commit.sh '$(PKG_GIT_URL)' '$(PKG_GIT_REF)' '$(TMP_DIR)/git-short-commit/$(PKG_NAME)-$(PKG_VERSION)' ); \
|
||||
if [ "$$$${EXPECTED_PKG_GIT_SHORT_COMMIT}" != "$(strip $(PKG_GIT_SHORT_COMMIT))" ]; then \
|
||||
echo "ERROR: Expected 'PKG_GIT_SHORT_COMMIT:=$$$${EXPECTED_PKG_GIT_SHORT_COMMIT}', found 'PKG_GIT_SHORT_COMMIT:=$(strip $(PKG_GIT_SHORT_COMMIT))'"; \
|
||||
exit 1; \
|
||||
fi \
|
||||
)
|
||||
endef
|
||||
|
||||
BUILDTAGS:=
|
||||
ifeq ($(KERNEL_SECCOMP),y)
|
||||
BUILDTAGS += seccomp
|
||||
endif
|
||||
ifeq ($(CONFIG_SELINUX),y)
|
||||
BUILDTAGS += selinux
|
||||
endif
|
||||
|
||||
define Build/Compile
|
||||
( \
|
||||
cd $(PKG_BUILD_DIR); \
|
||||
$(GO_PKG_VARS) \
|
||||
DOCKER_GITCOMMIT=$(PKG_GIT_SHORT_COMMIT) \
|
||||
DOCKER_BUILDTAGS='$(BUILDTAGS)' \
|
||||
VERSION=$(PKG_VERSION) \
|
||||
./hack/make.sh binary; \
|
||||
)
|
||||
endef
|
||||
|
||||
define Package/dockerd/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin/
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/bundles/binary-daemon/dockerd $(1)/usr/bin/
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/bundles/binary-daemon/docker-proxy $(1)/usr/bin/
|
||||
|
||||
$(INSTALL_DIR) $(1)/opt/docker/
|
||||
|
||||
ifeq ($(CONFIG_DOCKER_CHECK_CONFIG),y)
|
||||
$(INSTALL_DIR) $(1)/usr/share/docker/
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/contrib/check-config.sh $(1)/usr/share/docker/
|
||||
endif
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_BIN) ./files/dockerd.init $(1)/etc/init.d/dockerd
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/config
|
||||
$(INSTALL_CONF) ./files/etc/config/dockerd $(1)/etc/config/dockerd
|
||||
|
||||
# Must be after systcl 11-br-netfilter.conf from kmod-br-netfilter
|
||||
$(INSTALL_DIR) $(1)/etc/sysctl.d
|
||||
$(INSTALL_DATA) ./files/etc/sysctl.d/sysctl-br-netfilter-ip.conf \
|
||||
$(1)/etc/sysctl.d/12-br-netfilter-ip.conf
|
||||
endef
|
||||
|
||||
define Package/dockerd/postinst
|
||||
#!/bin/sh
|
||||
[ -n "$$IPKG_INSTROOT" ] || {
|
||||
/etc/init.d/dockerd enable
|
||||
/etc/init.d/dockerd uciadd
|
||||
/etc/init.d/dockerd start
|
||||
}
|
||||
endef
|
||||
|
||||
define Package/dockerd/prerm
|
||||
#!/bin/sh
|
||||
[ -n "$$IPKG_INSTROOT" ] || {
|
||||
/etc/init.d/dockerd disable
|
||||
/etc/init.d/dockerd stop
|
||||
/etc/init.d/dockerd ucidel
|
||||
}
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,dockerd))
|
||||
285
dockerd/files/dockerd.init
Executable file
285
dockerd/files/dockerd.init
Executable file
@ -0,0 +1,285 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
USE_PROCD=1
|
||||
START=99
|
||||
|
||||
extra_command "uciadd" "<interface> <device> <zone> Add docker bridge configuration to network and firewall uci config"
|
||||
extra_command "ucidel" "<interface> <device> <zone> Delete docker bridge configuration from network and firewall uci config"
|
||||
|
||||
DOCKER_CONF_DIR="/tmp/dockerd"
|
||||
DOCKERD_CONF="${DOCKER_CONF_DIR}/daemon.json"
|
||||
|
||||
uci_quiet() {
|
||||
uci -q "${@}" >/dev/null
|
||||
}
|
||||
|
||||
json_add_array_string() {
|
||||
json_add_string "" "${1}"
|
||||
}
|
||||
|
||||
find_network_device() {
|
||||
local device="${1}"
|
||||
local device_section=""
|
||||
|
||||
check_device() {
|
||||
local cfg="${1}"
|
||||
local device="${2}"
|
||||
|
||||
local type name
|
||||
config_get type "${cfg}" type
|
||||
config_get name "${cfg}" name
|
||||
|
||||
[ "${type}" = "bridge" ] && [ "${name}" = "${device}" ] \
|
||||
&& device_section="${cfg}"
|
||||
}
|
||||
|
||||
config_load network
|
||||
config_foreach check_device device "${device}"
|
||||
|
||||
echo "${device_section}"
|
||||
}
|
||||
|
||||
boot() {
|
||||
uciadd
|
||||
rc_procd start_service
|
||||
}
|
||||
|
||||
uciadd() {
|
||||
local iface="${1}"
|
||||
local device="${2}"
|
||||
local zone="${3}"
|
||||
|
||||
[ -z "${iface}" ] && {
|
||||
iface="docker"
|
||||
device="docker0"
|
||||
zone="docker"
|
||||
}
|
||||
|
||||
/etc/init.d/dockerd running && {
|
||||
echo "Please stop dockerd service first"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Add network interface
|
||||
if ! uci_quiet get network.${iface}; then
|
||||
logger -t "dockerd-init" -p notice "Adding interface '${iface}' to network config"
|
||||
uci_quiet add network interface
|
||||
uci_quiet rename network.@interface[-1]="${iface}"
|
||||
uci_quiet set network.@interface[-1].device="${device}"
|
||||
uci_quiet set network.@interface[-1].proto="none"
|
||||
uci_quiet set network.@interface[-1].auto="0"
|
||||
uci_quiet commit network
|
||||
fi
|
||||
|
||||
# Add docker bridge device
|
||||
if [ "$(find_network_device "$device")" = "" ]; then
|
||||
logger -t "dockerd-init" -p notice "Adding bridge device '${device}' to network config"
|
||||
uci_quiet add network device
|
||||
uci_quiet set network.@device[-1].type="bridge"
|
||||
uci_quiet set network.@device[-1].name="${device}"
|
||||
uci_quiet commit network
|
||||
else
|
||||
logger -t "dockerd-init" -p notice "Bridge device '${device}' already defined in network config"
|
||||
fi
|
||||
|
||||
# Add firewall zone
|
||||
if ! uci_quiet get firewall.${zone}; then
|
||||
logger -t "dockerd-init" -p notice "Adding firewall zone '${zone}' to firewall config"
|
||||
uci_quiet add firewall zone
|
||||
uci_quiet rename firewall.@zone[-1]="${zone}"
|
||||
uci_quiet set firewall.@zone[-1].input="ACCEPT"
|
||||
uci_quiet set firewall.@zone[-1].output="ACCEPT"
|
||||
uci_quiet set firewall.@zone[-1].forward="ACCEPT"
|
||||
uci_quiet set firewall.@zone[-1].name="${zone}"
|
||||
uci_quiet commit firewall
|
||||
fi
|
||||
|
||||
# Add interface to firewall zone
|
||||
if uci_quiet get firewall.${zone}; then
|
||||
uci_quiet del_list firewall.${zone}.network="${iface}"
|
||||
uci_quiet add_list firewall.${zone}.network="${iface}"
|
||||
uci_quiet commit firewall
|
||||
fi
|
||||
|
||||
reload_config
|
||||
}
|
||||
|
||||
ucidel() {
|
||||
local iface="${1}"
|
||||
local device="${2}"
|
||||
local zone="${3}"
|
||||
|
||||
[ -z "${iface}" ] && {
|
||||
iface="docker"
|
||||
device="docker0"
|
||||
zone="docker"
|
||||
}
|
||||
|
||||
/etc/init.d/dockerd running && {
|
||||
echo "Please stop dockerd service first"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Remove network device
|
||||
if uci_quiet delete network.$(find_network_device "${device}"); then
|
||||
logger -t "dockerd-init" -p notice "Deleting bridge device '${device}' from network config"
|
||||
uci_quiet commit network
|
||||
fi
|
||||
|
||||
# Remove network interface
|
||||
if uci_quiet get network.${iface}; then
|
||||
logger -t "dockerd-init" -p notice "Deleting interface '${iface}' from network config"
|
||||
uci_quiet delete network.${iface}
|
||||
uci_quiet commit network
|
||||
fi
|
||||
|
||||
# Remove interface from firewall zone
|
||||
if uci_quiet get firewall.${zone}; then
|
||||
logger -t "dockerd-init" -p notice "Deleting network interface '${iface}' in zone '${zone}' from firewall config"
|
||||
uci_quiet del_list firewall.${zone}.network="${iface}"
|
||||
uci_quiet commit firewall
|
||||
# Remove Firewall zone if network is empty
|
||||
if ! uci_quiet get firewall.${zone}.network; then
|
||||
logger -t "dockerd-init" -p notice "Deleting firewall zone '${zone}' from firewall config"
|
||||
uci_quiet delete firewall.${zone}
|
||||
fi
|
||||
uci_quiet commit firewall
|
||||
fi
|
||||
|
||||
reload_config
|
||||
}
|
||||
|
||||
process_config() {
|
||||
local alt_config_file data_root log_level iptables bip
|
||||
|
||||
[ -f /etc/config/dockerd ] || {
|
||||
# Use the daemon default configuration
|
||||
DOCKERD_CONF=""
|
||||
return 0
|
||||
}
|
||||
|
||||
# reset configuration
|
||||
rm -fr "${DOCKER_CONF_DIR}"
|
||||
mkdir -p "${DOCKER_CONF_DIR}"
|
||||
|
||||
config_load 'dockerd'
|
||||
config_get alt_config_file globals alt_config_file
|
||||
[ -n "${alt_config_file}" ] && [ -f "${alt_config_file}" ] && {
|
||||
ln -s "${alt_config_file}" "${DOCKERD_CONF}"
|
||||
return 0
|
||||
}
|
||||
|
||||
config_get data_root globals data_root "/opt/docker/"
|
||||
config_get log_level globals log_level "warn"
|
||||
config_get_bool iptables globals iptables "1"
|
||||
|
||||
# Don't add these options by default
|
||||
# omission == docker defaults
|
||||
config_get log_driver globals log_driver ""
|
||||
config_get bip globals bip ""
|
||||
config_get registry_mirrors globals registry_mirrors ""
|
||||
config_get hosts globals hosts ""
|
||||
config_get dns globals dns ""
|
||||
config_get_bool ipv6 globals ipv6 ""
|
||||
config_get ip globals ip ""
|
||||
config_get fixed_cidr globals fixed_cidr ""
|
||||
config_get fixed_cidr_v6 globals fixed_cidr_v6 ""
|
||||
|
||||
. /usr/share/libubox/jshn.sh
|
||||
json_init
|
||||
json_add_string "data-root" "${data_root}"
|
||||
json_add_string "log-level" "${log_level}"
|
||||
json_add_boolean "iptables" "${iptables}"
|
||||
[ -z "${log_driver}" ] || json_add_string "log-driver" "${log_driver}"
|
||||
[ -z "${bip}" ] || json_add_string "bip" "${bip}"
|
||||
[ -z "${registry_mirrors}" ] || json_add_array "registry-mirrors"
|
||||
[ -z "${registry_mirrors}" ] || config_list_foreach globals registry_mirrors json_add_array_string
|
||||
[ -z "${registry_mirrors}" ] || json_close_array
|
||||
[ -z "${hosts}" ] || json_add_array "hosts"
|
||||
[ -z "${hosts}" ] || config_list_foreach globals hosts json_add_array_string
|
||||
[ -z "${hosts}" ] || json_close_array
|
||||
[ -z "${dns}" ] || json_add_array "dns"
|
||||
[ -z "${dns}" ] || config_list_foreach globals dns json_add_array_string
|
||||
[ -z "${dns}" ] || json_close_array
|
||||
[ -z "${ipv6}" ] || json_add_boolean "ipv6" "${ipv6}"
|
||||
[ -z "${ip}" ] || json_add_string "ip" "${ip}"
|
||||
[ -z "${fixed_cidr}" ] || json_add_string "fixed-cidr" "${fixed_cidr}"
|
||||
[ -z "${fixed_cidr_v6}" ] || json_add_string "fixed-cidr-v6" "${fixed_cidr_v6}"
|
||||
json_dump > "${DOCKERD_CONF}"
|
||||
|
||||
[ "${iptables}" -eq "1" ] && config_foreach iptables_add_blocking_rule firewall
|
||||
}
|
||||
|
||||
start_service() {
|
||||
local nofile=$(cat /proc/sys/fs/nr_open)
|
||||
|
||||
process_config
|
||||
|
||||
procd_open_instance
|
||||
procd_set_param stderr 1
|
||||
if [ -z "${DOCKERD_CONF}" ]; then
|
||||
procd_set_param command /usr/bin/dockerd
|
||||
else
|
||||
procd_set_param command /usr/bin/dockerd --config-file="${DOCKERD_CONF}"
|
||||
fi
|
||||
procd_set_param limits nofile="${nofile} ${nofile}"
|
||||
procd_close_instance
|
||||
}
|
||||
|
||||
reload_service() {
|
||||
process_config
|
||||
procd_send_signal dockerd
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger 'dockerd'
|
||||
}
|
||||
|
||||
iptables_add_blocking_rule() {
|
||||
local cfg="${1}"
|
||||
|
||||
local device=""
|
||||
local extra_iptables_args=""
|
||||
|
||||
handle_iptables_rule() {
|
||||
local interface="${1}"
|
||||
local outbound="${2}"
|
||||
local extra_iptables_args="${3}"
|
||||
|
||||
local inbound=""
|
||||
|
||||
. /lib/functions/network.sh
|
||||
network_get_physdev inbound "${interface}"
|
||||
|
||||
[ -z "${inbound}" ] && {
|
||||
logger -t "dockerd-init" -p notice "Unable to get physical device for interface ${interface}"
|
||||
return
|
||||
}
|
||||
|
||||
# Wait for a maximum of 10 second per command, retrying every millisecond
|
||||
local iptables_wait_args="--wait 10 --wait-interval 1000"
|
||||
|
||||
# Ignore errors as it might already be present
|
||||
iptables ${iptables_wait_args} --table filter --new DOCKER-USER 2>/dev/null
|
||||
if ! iptables ${iptables_wait_args} --table filter --check DOCKER-USER --in-interface "${inbound}" --out-interface "${outbound}" ${extra_iptables_args} --jump REJECT 2>/dev/null; then
|
||||
logger -t "dockerd-init" -p notice "Drop traffic from ${inbound} to ${outbound}"
|
||||
iptables ${iptables_wait_args} --table filter --insert DOCKER-USER --in-interface "${inbound}" --out-interface "${outbound}" ${extra_iptables_args} --jump REJECT
|
||||
fi
|
||||
}
|
||||
|
||||
config_get device "${cfg}" device
|
||||
|
||||
[ -z "${device}" ] && {
|
||||
logger -t "dockerd-init" -p notice "No device configured for ${cfg}"
|
||||
return
|
||||
}
|
||||
|
||||
config_get extra_iptables_args "${cfg}" extra_iptables_args
|
||||
config_list_foreach "${cfg}" blocked_interfaces handle_iptables_rule "${device}" "${extra_iptables_args}"
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
if /etc/init.d/dockerd running; then
|
||||
service_stop "/usr/bin/dockerd"
|
||||
fi
|
||||
}
|
||||
38
dockerd/files/etc/config/dockerd
Normal file
38
dockerd/files/etc/config/dockerd
Normal file
@ -0,0 +1,38 @@
|
||||
# The following settings require a restart of docker to take full effect, A reload will only have partial or no effect:
|
||||
# log_driver
|
||||
# bip
|
||||
# blocked_interfaces
|
||||
# extra_iptables_args
|
||||
# device
|
||||
|
||||
config globals 'globals'
|
||||
# option alt_config_file '/etc/docker/daemon.json'
|
||||
option data_root '/opt/docker/'
|
||||
# option log_driver 'local'
|
||||
option log_level 'warn'
|
||||
option iptables '1'
|
||||
# list hosts 'unix:///var/run/docker.sock'
|
||||
# option bip '172.18.0.1/24'
|
||||
# option fixed_cidr '172.17.0.0/16'
|
||||
# option fixed_cidr_v6 'fc00:1::/80'
|
||||
# option ipv6 '1'
|
||||
# option ip '::ffff:0.0.0.0'
|
||||
# list dns '172.17.0.1'
|
||||
# list registry_mirrors 'https://<my-docker-mirror-host>'
|
||||
# list registry_mirrors 'https://hub.docker.com'
|
||||
|
||||
# Docker doesn't work well out of the box with fw4. This is because Docker relies on a compatibility layer that
|
||||
# naively translates iptables rules. For the best compatibility replace the following dependencies:
|
||||
# `firewall4` -> `firewall`
|
||||
# `iptables-nft` -> `iptables-legacy`
|
||||
# `ip6tables-nft` -> `ip6tables-legacy`
|
||||
|
||||
# Docker undermines the fw3 rules. By default all external source IPs are allowed to connect to the Docker host.
|
||||
# See https://docs.docker.com/network/iptables/ for more details.
|
||||
|
||||
# firewall config changes are only additive i.e firewall will need to be restarted first to clear old changes,
|
||||
# then docker restarted to load in new changes.
|
||||
config firewall 'firewall'
|
||||
option device 'docker0'
|
||||
list blocked_interfaces 'wan'
|
||||
# option extra_iptables_args '--match conntrack ! --ctstate RELATED,ESTABLISHED' # allow outbound connections
|
||||
7
dockerd/files/etc/sysctl.d/sysctl-br-netfilter-ip.conf
Normal file
7
dockerd/files/etc/sysctl.d/sysctl-br-netfilter-ip.conf
Normal file
@ -0,0 +1,7 @@
|
||||
# Do not edit, changes to this file will be lost on upgrades
|
||||
# /etc/sysctl.conf can be used to customize sysctl settings
|
||||
|
||||
# enable bridge firewalling for docker
|
||||
net.bridge.bridge-nf-call-ip6tables=1
|
||||
net.bridge.bridge-nf-call-iptables=1
|
||||
|
||||
52
dockerd/git-short-commit.sh
Executable file
52
dockerd/git-short-commit.sh
Executable file
@ -0,0 +1,52 @@
|
||||
|
||||
#!/bin/sh
|
||||
#
|
||||
# USAGE: git-short-commit.sh <GIT_URL> <GIT_REF> <GIT_DIR>
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
error() {
|
||||
echo "ERROR: ${*}" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
GIT_URL="${1}"
|
||||
if [ -z "${GIT_URL}" ]; then
|
||||
error "Git URL not specified"
|
||||
fi
|
||||
|
||||
GIT_REF="${2}"
|
||||
if [ -z "${GIT_REF}" ]; then
|
||||
error "Git reference not specified"
|
||||
fi
|
||||
|
||||
# Remove docker- prefix if present (e.g., docker-v29.2.1 -> v29.2.1)
|
||||
GIT_REF=$(echo "$GIT_REF" | sed 's/^docker-//')
|
||||
|
||||
GIT_DIR="${3}"
|
||||
if [ -z "${GIT_DIR}" ]; then
|
||||
error "Git clone directory not specified"
|
||||
fi
|
||||
|
||||
clean_up() {
|
||||
rm -rf "${GIT_DIR}"
|
||||
}
|
||||
trap clean_up EXIT
|
||||
|
||||
git init --quiet "${GIT_DIR}"
|
||||
(
|
||||
cd "${GIT_DIR}"
|
||||
for PREFIX in "" "https://" "http://" "git@"; do
|
||||
echo "Trying remote '${PREFIX}${GIT_URL}'" >&2
|
||||
git remote add origin "${PREFIX}${GIT_URL}"
|
||||
|
||||
if git fetch --depth 1 origin "${GIT_REF}"; then
|
||||
git checkout --detach FETCH_HEAD --
|
||||
git rev-parse --short HEAD
|
||||
break
|
||||
fi
|
||||
|
||||
git remote remove origin
|
||||
done
|
||||
)
|
||||
48
geoview/Makefile
Normal file
48
geoview/Makefile
Normal file
@ -0,0 +1,48 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=geoview
|
||||
PKG_VERSION:=0.2.6
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/snowie2000/geoview/tar.gz/$(PKG_VERSION)?
|
||||
PKG_HASH:=1b46d4255ab22ec433a14f804e9487885cc2693323579c1385d51919838db132
|
||||
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
PKG_MAINTAINER:=snowie2000
|
||||
|
||||
PKG_BUILD_DEPENDS:=golang/host
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_USE_MIPS16:=0
|
||||
PKG_BUILD_FLAGS:=no-mips16
|
||||
|
||||
GO_PKG:=github.com/snowie2000/geoview
|
||||
GO_PKG_BUILD_PKG:=$(GO_PKG)
|
||||
|
||||
GO_PKG_LDFLAGS:=-s -w
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
|
||||
|
||||
define Package/geoview
|
||||
TITLE:=A geofile toolkit
|
||||
URL:=https://github.com/snowie2000/geoview
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=IP Addresses and Names
|
||||
DEPENDS+= $(GO_ARCH_DEPENDS)
|
||||
endef
|
||||
|
||||
define Package/geoview/description
|
||||
geoview is a handy tool to extract useful information from geo* files.
|
||||
endef
|
||||
|
||||
define Package/geoview/install
|
||||
$(call GoPackage/Package/Install/Bin,$(PKG_INSTALL_DIR))
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/geoview $(1)/usr/bin/
|
||||
endef
|
||||
|
||||
$(eval $(call GoBinPackage,geoview))
|
||||
$(eval $(call BuildPackage,geoview))
|
||||
58
geth/Makefile
Normal file
58
geth/Makefile
Normal file
@ -0,0 +1,58 @@
|
||||
#
|
||||
# Copyright (C) 2018 Sartura Ltd.
|
||||
#
|
||||
# This is free software, licensed under the GNU General Public License v2.
|
||||
# See /LICENSE for more information.
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=go-ethereum
|
||||
PKG_VERSION:=1.10.20
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/ethereum/go-ethereum/tar.gz/v${PKG_VERSION}?
|
||||
PKG_HASH:=15ff54f0a4444eb9faa7c1f6219d3a1db5d547178b4eef6679bb601abc681f9d
|
||||
|
||||
PKG_MAINTAINER:=Mislav Novakovic <mislav.novakovic@sartura.hr>
|
||||
PKG_LICENSE:=GPL-3.0-or-later LGPL-3.0-or-later
|
||||
PKG_LICENSE_FILES:=COPYING COPYING.LESSER
|
||||
|
||||
PKG_BUILD_DEPENDS:=golang/host
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_USE_MIPS16:=0
|
||||
PKG_CONFIG_DEPENDS:=CONFIG_BUILD_NLS
|
||||
|
||||
GO_PKG:=github.com/ethereum/go-ethereum
|
||||
GO_PKG_BUILD_PKG:=github.com/ethereum/go-ethereum/cmd/geth
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include $(INCLUDE_DIR)/nls.mk
|
||||
include ../../lang/golang/golang-package.mk
|
||||
|
||||
define Package/geth
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
TITLE:=Ethereum Go client
|
||||
URL:=https://geth.ethereum.org/
|
||||
DEPENDS:=$(GO_ARCH_DEPENDS) $(ICONV_DEPENDS)
|
||||
endef
|
||||
|
||||
define Package/geth/description
|
||||
Ethereum is a decentralized platform that runs smart contracts, applications
|
||||
that run exactly as programmed without possibility of downtime, censorship,
|
||||
fraud or third party interference.
|
||||
endef
|
||||
|
||||
TARGET_LDFLAGS += $(if $(ICONV_FULL),-liconv)
|
||||
|
||||
define Package/geth/install
|
||||
$(call GoPackage/Package/Install/Bin,$(1))
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/init.d/
|
||||
$(INSTALL_BIN) ./files/geth.init $(1)/etc/init.d/geth
|
||||
endef
|
||||
|
||||
$(eval $(call GoBinPackage,geth))
|
||||
$(eval $(call BuildPackage,geth))
|
||||
15
geth/files/geth.init
Normal file
15
geth/files/geth.init
Normal file
@ -0,0 +1,15 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
START=90
|
||||
STOP=10
|
||||
|
||||
USE_PROCD=1
|
||||
PROG=/usr/bin/geth
|
||||
|
||||
start_service() {
|
||||
procd_open_instance
|
||||
procd_set_param command ${PROG}
|
||||
procd_append_param command --syncmode "light" --cache 1024
|
||||
procd_set_param respawn
|
||||
procd_close_instance
|
||||
}
|
||||
59
gn/Makefile
Normal file
59
gn/Makefile
Normal file
@ -0,0 +1,59 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
#
|
||||
# Copyright (C) 2022 ImmortalWrt.org
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=gn
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://gn.googlesource.com/gn.git
|
||||
PKG_SOURCE_DATE:=2026-05-21
|
||||
PKG_SOURCE_VERSION:=e44942b445d9376451f7d519965dae036efde5bc
|
||||
PKG_MIRROR_HASH:=8d41089dd918aa94a5f8ab6ac78b056220e353755e5b97f2f2cc70ff634c1f1c
|
||||
|
||||
PKG_LICENSE:=BSD 3-Clause
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
|
||||
|
||||
PKG_HOST_ONLY:=1
|
||||
HOST_BUILD_PARALLEL:=1
|
||||
|
||||
include $(INCLUDE_DIR)/host-build.mk
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/gn
|
||||
SECTION:=devel
|
||||
CATEGORY:=Development
|
||||
TITLE:=A meta-build system that generates build files for Ninja
|
||||
URL:=https://gn.googlesource.com/gn/
|
||||
BUILDONLY:=1
|
||||
endef
|
||||
|
||||
define Package/gn/description
|
||||
GN can generate Ninja build files for C, C++, Rust, Objective C,
|
||||
and Swift source on most popular platforms.
|
||||
endef
|
||||
|
||||
define Host/Configure
|
||||
$(PYTHON) $(HOST_BUILD_DIR)/build/gen.py \
|
||||
--no-last-commit-position \
|
||||
--allow-warnings
|
||||
endef
|
||||
|
||||
define Host/Compile
|
||||
+$(NINJA) -C $(HOST_BUILD_DIR)/out
|
||||
endef
|
||||
|
||||
define Host/Install
|
||||
$(INSTALL_DIR) $(STAGING_DIR_HOSTPKG)/bin/
|
||||
$(INSTALL_BIN) $(HOST_BUILD_DIR)/out/gn $(STAGING_DIR_HOSTPKG)/bin/
|
||||
endef
|
||||
|
||||
define Host/Clean
|
||||
$(RM) $(STAGING_DIR_HOSTPKG)/bin/gn
|
||||
endef
|
||||
|
||||
$(eval $(call HostBuild))
|
||||
$(eval $(call BuildPackage,gn))
|
||||
9
gn/src/out/last_commit_position.h
Normal file
9
gn/src/out/last_commit_position.h
Normal file
@ -0,0 +1,9 @@
|
||||
// Generated by build/gen.py.
|
||||
|
||||
#ifndef OUT_LAST_COMMIT_POSITION_H_
|
||||
#define OUT_LAST_COMMIT_POSITION_H_
|
||||
|
||||
#define LAST_COMMIT_POSITION_NUM 2406
|
||||
#define LAST_COMMIT_POSITION "2406 (e44942b445d9)"
|
||||
|
||||
#endif // OUT_LAST_COMMIT_POSITION_H_
|
||||
118
gnurl/Makefile
Normal file
118
gnurl/Makefile
Normal file
@ -0,0 +1,118 @@
|
||||
#
|
||||
# Copyright (C) 2007-2015 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:=gnurl
|
||||
PKG_VERSION:=7.72.0
|
||||
PKG_RELEASE:=2
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=@GNU/gnunet
|
||||
PKG_HASH:=b1e72be03fcf9b3fdaf01ae0702c686e9de10f67ef538918447c9e81555694f8
|
||||
|
||||
PKG_MAINTAINER:=Daniel Golle <daniel@makrotopia.org>
|
||||
PKG_LICENSE:=MIT
|
||||
PKG_LICENSE_FILES:=COPYING
|
||||
|
||||
PKG_FIXUP:=autoreconf
|
||||
PKG_INSTALL:=1
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/gnurl/Default
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
URL:=https://gnunet.org/en/gnurl.html
|
||||
endef
|
||||
|
||||
define Package/gnurl
|
||||
$(call Package/gnurl/Default)
|
||||
SUBMENU:=File Transfer
|
||||
DEPENDS:=+libgnurl
|
||||
TITLE:=A client-side HTTP/HTTPS transfer utility
|
||||
endef
|
||||
|
||||
define Package/libgnurl
|
||||
$(call Package/gnurl/Default)
|
||||
SECTION:=libs
|
||||
CATEGORY:=Libraries
|
||||
DEPENDS:=+libgnutls +libidn2 +zlib
|
||||
TITLE:=A client-side HTTP/HTTPS transfer library
|
||||
endef
|
||||
|
||||
TARGET_CFLAGS += $(FPIC)
|
||||
|
||||
CONFIGURE_ARGS += \
|
||||
$(call autoconf_bool,CONFIG_IPV6,ipv6) \
|
||||
--with-gnutls="$(STAGING_DIR)/usr" \
|
||||
--with-libidn="$(STAGING_DIR)/usr" \
|
||||
--with-zlib="$(STAGING_DIR)/usr" \
|
||||
--with-ca-path="/etc/ssl/certs/" \
|
||||
--enable-shared \
|
||||
--enable-static \
|
||||
--without-axtls \
|
||||
--without-brotli \
|
||||
--without-libssh2 \
|
||||
--without-libmetalink \
|
||||
--without-winidn \
|
||||
--without-librtmp \
|
||||
--without-nghttp2 \
|
||||
--without-nss \
|
||||
--without-cyassl \
|
||||
--without-libpsl \
|
||||
--without-polarssl \
|
||||
--without-ssl \
|
||||
--without-winssl \
|
||||
--without-darwinssl \
|
||||
--without-zstd \
|
||||
--disable-ares \
|
||||
--disable-sspi \
|
||||
--disable-crypto-auth \
|
||||
--disable-ntlm-wb \
|
||||
--disable-tls-srp \
|
||||
--disable-ldap \
|
||||
--disable-ldaps \
|
||||
--disable-rtsp \
|
||||
--disable-dict \
|
||||
--disable-telnet \
|
||||
--disable-tftp \
|
||||
--disable-pop3 \
|
||||
--disable-imap \
|
||||
--disable-smtp \
|
||||
--disable-gopher \
|
||||
--disable-file \
|
||||
--disable-ftp \
|
||||
--disable-smb \
|
||||
--disable-debug \
|
||||
--disable-manual \
|
||||
--disable-verbose
|
||||
|
||||
define Build/InstallDev
|
||||
$(INSTALL_DIR) $(2)/bin $(1)/usr/bin $(1)/usr/include/gnurl $(1)/usr/lib $(1)/usr/lib/pkgconfig
|
||||
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/gnurl-config $(1)/usr/bin/
|
||||
$(CP) $(PKG_INSTALL_DIR)/usr/include/gnurl/*.h $(1)/usr/include/gnurl
|
||||
$(CP) $(PKG_INSTALL_DIR)/usr/lib/libgnurl.{a,so*} $(1)/usr/lib/
|
||||
$(CP) $(PKG_BUILD_DIR)/libgnurl.pc $(1)/usr/lib/pkgconfig/
|
||||
$(SED) 's,^\(prefix\|exec_prefix\)=.*,\1=$(STAGING_DIR)/usr,g' $(1)/usr/bin/gnurl-config
|
||||
[ -n "$(TARGET_LDFLAGS)" ] && $(SED) 's#$(TARGET_LDFLAGS)##g' $(1)/usr/lib/pkgconfig/libgnurl.pc || true
|
||||
$(LN) $(STAGING_DIR)/usr/bin/gnurl-config $(2)/bin/
|
||||
endef
|
||||
|
||||
define Package/gnurl/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/gnurl $(1)/usr/bin/
|
||||
endef
|
||||
|
||||
define Package/libgnurl/install
|
||||
$(INSTALL_DIR) $(1)/usr/lib
|
||||
$(CP) $(PKG_INSTALL_DIR)/usr/lib/libgnurl.so.* $(1)/usr/lib/
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,gnurl))
|
||||
$(eval $(call BuildPackage,libgnurl))
|
||||
56
gost/Makefile
Normal file
56
gost/Makefile
Normal file
@ -0,0 +1,56 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
#
|
||||
# Copyright (C) 2021 ImmortalWrt.org
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=gost
|
||||
PKG_VERSION:=3.2.6
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/go-gost/gost/tar.gz/v$(PKG_VERSION)?
|
||||
PKG_HASH:=79874354530b899576dd4866d3b1400651d0b17c1e7a90ad30c44686a0642600
|
||||
|
||||
PKG_LICENSE:=MIT
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
|
||||
|
||||
PKG_BUILD_DEPENDS:=golang/host
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_BUILD_FLAGS:=no-mips16
|
||||
|
||||
GO_PKG:=github.com/go-gost/gost
|
||||
GO_PKG_BUILD_PKG:=$(GO_PKG)/cmd/gost
|
||||
GO_PKG_LDFLAGS_X:=main.version=$(PKG_VERSION)
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include ../../lang/golang/golang-package.mk
|
||||
|
||||
define Package/gost
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
TITLE:=GO Simple Tunnel
|
||||
URL:=https://gost.run/
|
||||
DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle
|
||||
USERID:=gost:gost
|
||||
endef
|
||||
|
||||
define Package/gost/conffiles
|
||||
/etc/config/gost
|
||||
/etc/gost/
|
||||
endef
|
||||
|
||||
define Package/gost/install
|
||||
$(call GoPackage/Package/Install/Bin,$(1))
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/config
|
||||
$(INSTALL_CONF) $(CURDIR)/files/gost.config $(1)/etc/config/gost
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_BIN) $(CURDIR)/files/gost.init $(1)/etc/init.d/gost
|
||||
$(INSTALL_DIR) $(1)/etc/uci-defaults
|
||||
$(INSTALL_BIN) $(CURDIR)/files/gost.uci $(1)/etc/uci-defaults/99-gost-migrate
|
||||
endef
|
||||
|
||||
$(eval $(call GoBinPackage,gost))
|
||||
$(eval $(call BuildPackage,gost))
|
||||
6
gost/files/gost.config
Normal file
6
gost/files/gost.config
Normal file
@ -0,0 +1,6 @@
|
||||
|
||||
config gost 'config'
|
||||
option enabled '0'
|
||||
option config_file ''
|
||||
#list arguments ''
|
||||
|
||||
46
gost/files/gost.init
Normal file
46
gost/files/gost.init
Normal file
@ -0,0 +1,46 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
USE_PROCD=1
|
||||
START=99
|
||||
|
||||
CONF="gost"
|
||||
PROG="/usr/bin/gost"
|
||||
|
||||
start_service() {
|
||||
local enabled
|
||||
|
||||
config_load "gost"
|
||||
config_get_bool enabled "config" "enabled" 0
|
||||
[ "$enabled" -eq 1 ] || return 1
|
||||
|
||||
local config_file arguments
|
||||
config_get config_file "config" "config_file"
|
||||
config_get arguments "config" "arguments"
|
||||
|
||||
procd_open_instance
|
||||
|
||||
procd_set_param command "$PROG"
|
||||
if [ -n "$config_file" ]; then
|
||||
procd_append_param command -C "$config_file"
|
||||
procd_set_param file "$config_file"
|
||||
fi
|
||||
if [ -n "$arguments" ]; then
|
||||
set -- $arguments
|
||||
procd_append_param command "$@"
|
||||
fi
|
||||
|
||||
procd_set_param user gost
|
||||
procd_set_param group gost
|
||||
|
||||
procd_set_param limits core="unlimited"
|
||||
procd_set_param limits nofile="1000000 1000000"
|
||||
procd_set_param stderr 1
|
||||
|
||||
procd_set_param respawn
|
||||
|
||||
procd_close_instance
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger "$CONF"
|
||||
}
|
||||
10
gost/files/gost.uci
Normal file
10
gost/files/gost.uci
Normal file
@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
grep -q "config gost$" "/etc/config/gost" || exit 0
|
||||
|
||||
sed -e "s,config gost,config gost 'config'," \
|
||||
-e "s,option enable ,option enabled ," \
|
||||
-e "s,option run_command,list arguments," \
|
||||
-i "/etc/config/gost"
|
||||
|
||||
exit 0
|
||||
56
gowebdav/Makefile
Normal file
56
gowebdav/Makefile
Normal file
@ -0,0 +1,56 @@
|
||||
#
|
||||
# This is free software, licensed under the GNU General Public License v2.
|
||||
# See /LICENSE for more information.
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=gowebdav
|
||||
PKG_VERSION:=5.7.4
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/hacdias/webdav/tar.gz/v$(PKG_VERSION)?
|
||||
PKG_HASH:=5462eaf18292727e7af66d77c1723f729c97a31d4dfe4b47343f0644a5e63454
|
||||
|
||||
PKG_LICENSE:=MIT
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
PKG_MAINTAINER:=ElonH <elonhhuang@gmail.com>
|
||||
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/webdav-$(PKG_VERSION)
|
||||
PKG_BUILD_DEPENDS:=golang/host
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_USE_MIPS16:=0
|
||||
|
||||
GO_PKG:=github.com/hacdias/webdav
|
||||
GO_PKG_LDFLAGS:=-s -w
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include ../../lang/golang/golang-package.mk
|
||||
|
||||
define Package/gowebdav
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=File Transfer
|
||||
TITLE:=Simple Go WebDAV server
|
||||
URL:=https://github.com/hacdias/webdav
|
||||
DEPENDS:=$(GO_ARCH_DEPENDS)
|
||||
endef
|
||||
|
||||
define Package/gowebdav/description
|
||||
Webdav command line interface is really easy to use so you can easily create
|
||||
a WebDAV server for your own user. By default, it runs on a random free port
|
||||
and supports JSON, YAML and TOML configuration.
|
||||
endef
|
||||
|
||||
define Package/gowebdav/install
|
||||
$(call GoPackage/Package/Install/Bin,$(1))
|
||||
|
||||
# $(INSTALL_DIR) $(1)/etc/config/ $(1)/etc/init.d/ $(1)/etc/webdav/
|
||||
# $(INSTALL_BIN) $(CURDIR)/files/webdav.init $(1)/etc/init.d/webdav
|
||||
# $(INSTALL_CONF) $(CURDIR)/files/webdav.config $(1)/etc/config/webdav
|
||||
# $(INSTALL_CONF) $(CURDIR)/files/webdav.yaml $(1)/etc/webdav/webdav.yaml
|
||||
endef
|
||||
|
||||
$(eval $(call GoBinPackage,gowebdav))
|
||||
$(eval $(call BuildPackage,gowebdav))
|
||||
3
gowebdav/files/webdav.config
Normal file
3
gowebdav/files/webdav.config
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
config webdav 'config'
|
||||
option enabled '0'
|
||||
22
gowebdav/files/webdav.init
Normal file
22
gowebdav/files/webdav.init
Normal file
@ -0,0 +1,22 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
# Copyright (C) 2021 ImmortalWrt
|
||||
|
||||
START=90
|
||||
STOP=10
|
||||
|
||||
WEBDAV="/usr/bin/webdav"
|
||||
|
||||
WEBDAVCONFIG="/etc/webdav/webdav.yaml"
|
||||
|
||||
start() {
|
||||
local enabled="$(uci get webdav.config.enabled)"
|
||||
stop
|
||||
[ "${enabled}" == "1" ] || exit 0
|
||||
export HOME="/root/"
|
||||
"${WEBDAV}" -c "${WEBDAVCONFIG}" >/dev/null 2>&1 &
|
||||
}
|
||||
|
||||
stop() {
|
||||
killall -9 pgrep "${WEBDAV}" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
37
gowebdav/files/webdav.yaml
Normal file
37
gowebdav/files/webdav.yaml
Normal file
@ -0,0 +1,37 @@
|
||||
# Server related settings
|
||||
address: 0.0.0.0
|
||||
port: 10880
|
||||
auth: true
|
||||
tls: false
|
||||
cert: cert.pem
|
||||
key: key.pem
|
||||
prefix: /
|
||||
|
||||
# Default user settings (will be merged)
|
||||
scope: .
|
||||
modify: true
|
||||
rules: []
|
||||
|
||||
# CORS configuration
|
||||
cors:
|
||||
enabled: true
|
||||
credentials: true
|
||||
allowed_headers:
|
||||
- Depth
|
||||
allowed_hosts:
|
||||
- http://localhost:8999
|
||||
allowed_methods:
|
||||
- GET
|
||||
exposed_headers:
|
||||
- Content-Length
|
||||
- Content-Range
|
||||
|
||||
users:
|
||||
- username: video
|
||||
password: 123
|
||||
scope: /mnt/sdb1
|
||||
modify: true
|
||||
rules:
|
||||
- regex: false
|
||||
allow: false
|
||||
path: /some/file
|
||||
63
headscale/Makefile
Normal file
63
headscale/Makefile
Normal file
@ -0,0 +1,63 @@
|
||||
#
|
||||
# This is free software, licensed under the GNU General Public License v2.
|
||||
# See /LICENSE for more information.
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=headscale
|
||||
PKG_VERSION:=0.21.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/juanfont/headscale/tar.gz/v$(PKG_VERSION)?
|
||||
PKG_HASH:=2afbdc038508cb694be496db9ba6b63bbc611b7038e8299e60eef0f1b227f12f
|
||||
|
||||
PKG_LICENSE:=BSD-3-clause
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
|
||||
PKG_BUILD_DEPENDS:=golang/host
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_USE_MIPS16:=0
|
||||
|
||||
GO_PKG:=github.com/juanfont/headscale
|
||||
GO_PKG_BUILD_PKG:=$(GO_PKG)/cmd/headscale
|
||||
GO_PKG_LDFLAGS:=-s -w
|
||||
GO_PKG_LDFLAGS+= \
|
||||
-X '$(GO_PKG_BUILD_PKG)/cli.Version=v$(PKG_VERSION)'
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include ../../lang/golang/golang-package.mk
|
||||
|
||||
define Package/headscale
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=VPN
|
||||
TITLE:=A Tailscale control server
|
||||
URL:=https://github.com/juanfont/headscale
|
||||
DEPENDS:=$(GO_ARCH_DEPENDS)
|
||||
endef
|
||||
|
||||
define Package/headscale/description
|
||||
An open source, self-hosted implementation of the Tailscale control server.
|
||||
endef
|
||||
|
||||
define Package/headscale/conffiles
|
||||
/etc/headscale/config.yaml
|
||||
/etc/headscale/db.sqlite
|
||||
/etc/headscale/derp.yaml
|
||||
/etc/headscale/noise_private.key
|
||||
/etc/headscale/private.key
|
||||
endef
|
||||
|
||||
define Package/headscale/install
|
||||
$(call GoPackage/Package/Install/Bin,$(1))
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/headscale
|
||||
touch $(1)/etc/headscale/db.sqlite
|
||||
$(INSTALL_CONF) $(PKG_BUILD_DIR)/config-example.yaml $(1)/etc/headscale/config.yaml
|
||||
$(INSTALL_CONF) $(PKG_BUILD_DIR)/derp-example.yaml $(1)/etc/headscale/derp.yaml
|
||||
endef
|
||||
|
||||
$(eval $(call GoBinPackage,headscale))
|
||||
$(eval $(call BuildPackage,headscale))
|
||||
59
hysteria/Makefile
Normal file
59
hysteria/Makefile
Normal file
@ -0,0 +1,59 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
#
|
||||
# Copyright (C) 2021 ImmortalWrt.org
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=hysteria
|
||||
PKG_VERSION:=2.9.2
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/apernet/hysteria/tar.gz/app/v$(PKG_VERSION)?
|
||||
PKG_HASH:=d3d0e7b2e49f0cebb861fb215aa1625e4efa895a902d768db7ea45227d88b555
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-app-v$(PKG_VERSION)
|
||||
|
||||
PKG_LICENSE:=MIT
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
|
||||
|
||||
PKG_BUILD_DEPENDS:=golang/host
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_USE_MIPS16:=0
|
||||
PKG_BUILD_FLAGS:=no-mips16
|
||||
|
||||
GO_PKG:=github.com/apernet/hysteria
|
||||
GO_PKG_BUILD_PKG:=$(GO_PKG)/app/v2
|
||||
GO_PKG_LDFLAGS_X = \
|
||||
$(GO_PKG)/app/v2/cmd.appVersion=v$(PKG_VERSION) \
|
||||
$(GO_PKG)/app/v2/cmd.appType=release \
|
||||
$(GO_PKG)/app/v2/cmd.appPlatform=$(GO_OS) \
|
||||
$(GO_PKG)/app/v2/cmd.appArch=$(GO_ARCH)
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
|
||||
|
||||
define Package/hysteria
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
TITLE:=A feature-packed network utility optimized for networks of poor quality
|
||||
URL:=https://github.com/tobyxdd/hysteria
|
||||
DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle
|
||||
endef
|
||||
|
||||
define Package/hysteria/description
|
||||
Hysteria is a feature-packed network utility optimized for networks
|
||||
of poor quality (e.g. satellite connections, congested public Wi-Fi,
|
||||
connecting from China to servers abroad) powered by a custom version
|
||||
of QUIC protocol.
|
||||
endef
|
||||
|
||||
define Package/hysteria/install
|
||||
$(call GoPackage/Package/Install/Bin,$(PKG_INSTALL_DIR))
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/app $(1)/usr/bin/hysteria
|
||||
endef
|
||||
|
||||
$(eval $(call GoBinPackage,hysteria))
|
||||
$(eval $(call BuildPackage,hysteria))
|
||||
41
internet-detector-mod-email/Makefile
Normal file
41
internet-detector-mod-email/Makefile
Normal file
@ -0,0 +1,41 @@
|
||||
#
|
||||
# (с) 2026 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector)
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=internet-detector-mod-email
|
||||
PKG_VERSION:=1.7.3
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=gSpot <https://github.com/gSpotx2f/luci-app-internet-detector>
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/$(PKG_NAME)
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
TITLE:=Email module for internet-detector
|
||||
URL:=https://github.com/gSpotx2f/luci-app-internet-detector
|
||||
PKGARCH:=all
|
||||
DEPENDS:=+internet-detector +mailsend
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/description
|
||||
Email support for internet-detector.
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/conffiles
|
||||
endef
|
||||
|
||||
define Build/Configure
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_DIR) $(1)/usr/lib/lua/internet-detector/modules
|
||||
$(INSTALL_DATA) ./files/usr/lib/lua/internet-detector/modules/mod_email.lua $(1)/usr/lib/lua/internet-detector/modules/mod_email.lua
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
@ -0,0 +1,226 @@
|
||||
--[[
|
||||
Dependences:
|
||||
mailsend
|
||||
--]]
|
||||
local unistd = require("posix.unistd")
|
||||
|
||||
local Module = {
|
||||
name = "mod_email",
|
||||
runPrio = 60,
|
||||
config = {
|
||||
debug = false,
|
||||
},
|
||||
syslog = function(level, msg) return true end,
|
||||
debugOutput = function(msg) return true end,
|
||||
writeValue = function(filePath, str) return false end,
|
||||
readValue = function(filePath) return nil end,
|
||||
deadPeriod = 0,
|
||||
alivePeriod = 0,
|
||||
mode = 0, -- 0: connected, 1: disconnected, 2: both
|
||||
hostAlias = "OpenWrt",
|
||||
mta = "/usr/bin/mailsend",
|
||||
mtaConnectTimeout = 5,
|
||||
mtaReadTimeout = 5,
|
||||
mailRecipient = nil,
|
||||
mailSender = nil,
|
||||
mailUser = nil,
|
||||
mailPassword = nil,
|
||||
mailSmtp = nil,
|
||||
mailSmtpPort = nil,
|
||||
mailSecurity = "tls",
|
||||
msgTextPattern = "[%s] (%s) @ %s", -- Message (host, instance, message)
|
||||
msgSubPattern = "%s notification", -- Subject (host)
|
||||
msgConnectPattern = "Connected: %s",
|
||||
msgDisconnectPattern = "Disconnected: %s",
|
||||
msgSeparator = " | ",
|
||||
msgMaxItems = 50,
|
||||
msgSendAttempts = 3,
|
||||
msgSendTimeout = 5,
|
||||
status = nil,
|
||||
_enabled = false,
|
||||
_deadCounter = 0,
|
||||
_aliveCounter = 0,
|
||||
_msgSentDisconnect = true,
|
||||
_disconnected = true,
|
||||
_msgSentConnect = true,
|
||||
_connected = true,
|
||||
_msgBuffer = {},
|
||||
_msgSendCounter = 3,
|
||||
_msgTimeoutCounter = 5,
|
||||
}
|
||||
|
||||
function Module:init(t)
|
||||
if t.mode ~= nil then
|
||||
self.mode = tonumber(t.mode)
|
||||
end
|
||||
if t.dead_period ~= nil then
|
||||
self.deadPeriod = tonumber(t.dead_period)
|
||||
end
|
||||
if t.alive_period ~= nil then
|
||||
self.alivePeriod = tonumber(t.alive_period)
|
||||
end
|
||||
if t.host_alias then
|
||||
self.hostAlias = t.host_alias
|
||||
else
|
||||
self.hostAlias = self.config.hostname
|
||||
end
|
||||
|
||||
self.mailRecipient = t.mail_recipient
|
||||
self.mailSender = t.mail_sender
|
||||
self.mailUser = t.mail_user
|
||||
self.mailPassword = t.mail_password
|
||||
self.mailSmtp = t.mail_smtp
|
||||
self.mailSmtpPort = t.mail_smtp_port
|
||||
|
||||
if t.mail_security ~= nil then
|
||||
self.mailSecurity = t.mail_security
|
||||
end
|
||||
if tonumber(t.message_at_startup) == 1 then
|
||||
self._msgSentDisconnect = false
|
||||
self._disconnected = false
|
||||
self._msgSentConnect = false
|
||||
self._connected = false
|
||||
end
|
||||
|
||||
if unistd.access(self.mta, "x") then
|
||||
self._enabled = true
|
||||
else
|
||||
self._enabled = false
|
||||
self.syslog("err", string.format("%s: %s is not available", self.name, self.mta))
|
||||
end
|
||||
|
||||
if (not self.mailRecipient or
|
||||
not self.mailSender or
|
||||
not self.mailUser or
|
||||
not self.mailPassword or
|
||||
not self.mailSmtp or
|
||||
not self.mailSmtpPort) then
|
||||
self._enabled = false
|
||||
self.syslog("warning", string.format(
|
||||
"%s: Insufficient data to connect to the SMTP server", self.name))
|
||||
end
|
||||
|
||||
self._msgSendCounter = self.msgSendAttempts
|
||||
end
|
||||
|
||||
function Module:appendNotice(str)
|
||||
self._msgBuffer[#self._msgBuffer + 1] = str
|
||||
if #self._msgBuffer > self.msgMaxItems then
|
||||
local t = {}
|
||||
for i = #self._msgBuffer - self.msgMaxItems + 1, #self._msgBuffer do
|
||||
t[#t + 1] = self._msgBuffer[i]
|
||||
end
|
||||
self._msgBuffer = t
|
||||
end
|
||||
end
|
||||
|
||||
function Module:sendMessage(msg, textPattern)
|
||||
local retVal = 1
|
||||
local verboseArg = ""
|
||||
local emailMsg = string.format(
|
||||
textPattern, self.hostAlias, self.config.serviceConfig.instance, msg)
|
||||
|
||||
-- Debug
|
||||
if self.config.debug then
|
||||
verboseArg = " -v"
|
||||
end
|
||||
self.debugOutput(string.format("--- %s ---", self.name))
|
||||
|
||||
local securityArgs = "-starttls -auth-login"
|
||||
if self.mailSecurity == "ssl" then
|
||||
securityArgs = "-ssl -auth"
|
||||
end
|
||||
|
||||
local mtaCmd = string.format(
|
||||
'%s%s %s -smtp "%s" -port %s -ct %s -read-timeout %s -cs utf-8 -user "%s" -pass "%s" -f "%s" -t "%s" -sub "%s" -M "%s"',
|
||||
self.mta, verboseArg, securityArgs, self.mailSmtp, self.mailSmtpPort,
|
||||
self.mtaConnectTimeout, self.mtaReadTimeout,
|
||||
self.mailUser, self.mailPassword, self.mailSender, self.mailRecipient,
|
||||
string.format(self.msgSubPattern, self.hostAlias),
|
||||
emailMsg)
|
||||
|
||||
-- Debug
|
||||
self.debugOutput(string.format("%s: %s", self.name, mtaCmd))
|
||||
self.syslog("debug", string.format("%s: %s", self.name, mtaCmd))
|
||||
|
||||
retVal = os.execute(mtaCmd)
|
||||
if retVal == 0 then
|
||||
self.syslog("info", string.format(
|
||||
"%s: Message sent to %s", self.name, self.mailRecipient))
|
||||
self._msgBuffer = {}
|
||||
else
|
||||
self.syslog("err", string.format(
|
||||
"%s: An error occured while sending message", self.name))
|
||||
end
|
||||
|
||||
return retVal
|
||||
end
|
||||
|
||||
function Module:run(currentStatus, lastStatus, timeDiff, timeNow, inetChecked)
|
||||
if not self._enabled then
|
||||
return
|
||||
end
|
||||
|
||||
if currentStatus == 1 then
|
||||
self._aliveCounter = 0
|
||||
self._msgSentConnect = false
|
||||
|
||||
if not self._disconnected then
|
||||
self._disconnected = true
|
||||
self:appendNotice(string.format(
|
||||
self.msgDisconnectPattern, os.date("%Y.%m.%d %H:%M:%S", os.time())))
|
||||
end
|
||||
|
||||
if not self._msgSentDisconnect and (self.mode == 1 or self.mode == 2) then
|
||||
if self._deadCounter >= self.deadPeriod then
|
||||
self._msgSendCounter = 0
|
||||
self._msgSentDisconnect = true
|
||||
else
|
||||
self._deadCounter = self._deadCounter + timeDiff
|
||||
end
|
||||
end
|
||||
self._connected = false
|
||||
elseif currentStatus == 0 then
|
||||
self._deadCounter = 0
|
||||
self._msgSentDisconnect = false
|
||||
|
||||
if not self._connected then
|
||||
self._connected = true
|
||||
self:appendNotice(string.format(
|
||||
self.msgConnectPattern, os.date("%Y.%m.%d %H:%M:%S", os.time())))
|
||||
end
|
||||
|
||||
if not self._msgSentConnect and (self.mode == 0 or self.mode == 2) then
|
||||
if self._aliveCounter >= self.alivePeriod then
|
||||
self._msgSendCounter = 0
|
||||
self._msgSentConnect = true
|
||||
else
|
||||
self._aliveCounter = self._aliveCounter + timeDiff
|
||||
end
|
||||
end
|
||||
self._disconnected = false
|
||||
end
|
||||
|
||||
if self._msgSendCounter < self.msgSendAttempts then
|
||||
if self._msgTimeoutCounter >= self.msgSendTimeout then
|
||||
if #self._msgBuffer > 0 then
|
||||
if self:sendMessage(table.concat(self._msgBuffer, self.msgSeparator), self.msgTextPattern) == 0 then
|
||||
self._msgSendCounter = self.msgSendAttempts
|
||||
else
|
||||
self._msgSendCounter = self._msgSendCounter + 1
|
||||
end
|
||||
end
|
||||
self._msgTimeoutCounter = 0
|
||||
else
|
||||
self._msgTimeoutCounter = self._msgTimeoutCounter + timeDiff
|
||||
end
|
||||
else
|
||||
self._msgTimeoutCounter = self.msgSendTimeout
|
||||
end
|
||||
end
|
||||
|
||||
function Module:onExit()
|
||||
return true
|
||||
end
|
||||
|
||||
return Module
|
||||
41
internet-detector-mod-modem-restart/Makefile
Normal file
41
internet-detector-mod-modem-restart/Makefile
Normal file
@ -0,0 +1,41 @@
|
||||
#
|
||||
# (с) 2026 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector)
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=internet-detector-mod-modem-restart
|
||||
PKG_VERSION:=1.7.3
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=gSpot <https://github.com/gSpotx2f/luci-app-internet-detector>
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/$(PKG_NAME)
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
TITLE:=Modem restart module for internet-detector
|
||||
URL:=https://github.com/gSpotx2f/luci-app-internet-detector
|
||||
PKGARCH:=all
|
||||
DEPENDS:=+internet-detector +modemmanager
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/description
|
||||
Support modem restart for internet detector.
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/conffiles
|
||||
endef
|
||||
|
||||
define Build/Configure
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_DIR) $(1)/usr/lib/lua/internet-detector/modules
|
||||
$(INSTALL_DATA) ./files/usr/lib/lua/internet-detector/modules/mod_modem_restart.lua $(1)/usr/lib/lua/internet-detector/modules/mod_modem_restart.lua
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
@ -0,0 +1,152 @@
|
||||
--[[
|
||||
Dependences:
|
||||
modemmanager
|
||||
--]]
|
||||
local unistd = require("posix.unistd")
|
||||
|
||||
local Module = {
|
||||
name = "mod_modem_restart",
|
||||
runPrio = 40,
|
||||
config = {},
|
||||
syslog = function(level, msg) return true end,
|
||||
debugOutput = function(msg) return true end,
|
||||
writeValue = function(filePath, str) return false end,
|
||||
readValue = function(filePath) return nil end,
|
||||
mmcli = "/usr/bin/mmcli",
|
||||
mmInit = "/etc/init.d/modemmanager",
|
||||
deadPeriod = 600,
|
||||
attempts = 1,
|
||||
attemptInterval = 15,
|
||||
ifaceTimeout = 0,
|
||||
iface = nil,
|
||||
anyBand = false,
|
||||
status = nil,
|
||||
_enabled = false,
|
||||
_attemptsCounter = 0,
|
||||
_attemptIntervalCounter = 0,
|
||||
_deadCounter = 0,
|
||||
_firstAttempt = true,
|
||||
_ifaceRestarting = false,
|
||||
_ifaceRestartCounter = 0,
|
||||
_disconnectedAtStartup = false,
|
||||
}
|
||||
|
||||
function Module:toggleIface(flag)
|
||||
if not self.iface then
|
||||
return
|
||||
end
|
||||
return os.execute(
|
||||
string.format("%s %s", (flag and "/sbin/ifup" or "/sbin/ifdown"), self.iface)
|
||||
)
|
||||
end
|
||||
|
||||
function Module:init(t)
|
||||
if t.dead_period ~= nil then
|
||||
self.deadPeriod = tonumber(t.dead_period)
|
||||
end
|
||||
if t.attempts ~= nil then
|
||||
self.attempts = tonumber(t.attempts)
|
||||
end
|
||||
if t.attempt_interval ~= nil then
|
||||
self.attemptInterval = tonumber(t.attempt_interval)
|
||||
end
|
||||
if t.iface ~= nil then
|
||||
self.iface = t.iface
|
||||
end
|
||||
if t.iface_timeout ~= nil then
|
||||
self.ifaceTimeout = tonumber(t.iface_timeout)
|
||||
end
|
||||
if t.any_band ~= nil then
|
||||
self.anyBand = (tonumber(t.any_band) ~= 0)
|
||||
end
|
||||
if tonumber(t.disconnected_at_startup) == 1 then
|
||||
self._disconnectedAtStartup = true
|
||||
end
|
||||
|
||||
if not unistd.access(self.mmcli, "x") then
|
||||
self.anyBand = false
|
||||
end
|
||||
|
||||
if (unistd.access(self.mmInit, "x")
|
||||
and os.execute(string.format("%s enabled", self.mmInit)) == 0) then
|
||||
self._enabled = true
|
||||
else
|
||||
self._enabled = false
|
||||
self.syslog("err", string.format(
|
||||
"%s: modemmanager service is not available", self.name))
|
||||
end
|
||||
end
|
||||
|
||||
function Module:restartMM()
|
||||
if os.execute(string.format("%s enabled", self.mmInit)) == 0 then
|
||||
if self.anyBand then
|
||||
self.syslog("info", string.format(
|
||||
"%s: resetting current-bands to 'any'", self.name))
|
||||
os.execute(string.format("%s -m any --set-current-bands=any", self.mmcli))
|
||||
end
|
||||
|
||||
self.syslog("info", string.format("%s: reconnecting modem", self.name))
|
||||
os.execute(string.format("%s restart", self.mmInit))
|
||||
|
||||
if self.iface then
|
||||
self.syslog("info", string.format(
|
||||
"%s: restarting network interface '%s'", self.name, self.iface))
|
||||
self:toggleIface(false)
|
||||
if self.ifaceTimeout < 1 then
|
||||
self:toggleIface(true)
|
||||
else
|
||||
self._ifaceRestarting = true
|
||||
end
|
||||
end
|
||||
else
|
||||
self.syslog("warning", string.format(
|
||||
"%s: modemmanager service is disabled", self.name))
|
||||
end
|
||||
if self.attempts > 0 then
|
||||
self._attemptsCounter = self._attemptsCounter + 1
|
||||
end
|
||||
end
|
||||
|
||||
function Module:run(currentStatus, lastStatus, timeDiff, timeNow, inetChecked)
|
||||
if not self._enabled then
|
||||
return
|
||||
end
|
||||
if self.iface and self._ifaceRestarting then
|
||||
if self._ifaceRestartCounter >= self.ifaceTimeout then
|
||||
self:toggleIface(true)
|
||||
self._ifaceRestarting = false
|
||||
self._ifaceRestartCounter = 0
|
||||
else
|
||||
self._ifaceRestartCounter = self._ifaceRestartCounter + timeDiff
|
||||
end
|
||||
else
|
||||
if currentStatus == 1 then
|
||||
if self._disconnectedAtStartup and self._deadCounter >= self.deadPeriod then
|
||||
if self.attempts == 0 or self._attemptsCounter < self.attempts then
|
||||
if self._firstAttempt or self._attemptIntervalCounter >= self.attemptInterval then
|
||||
self:restartMM()
|
||||
self._attemptIntervalCounter = 0
|
||||
self._firstAttempt = false
|
||||
else
|
||||
self._attemptIntervalCounter = self._attemptIntervalCounter + timeDiff
|
||||
end
|
||||
end
|
||||
else
|
||||
self._deadCounter = self._deadCounter + timeDiff
|
||||
end
|
||||
else
|
||||
self._attemptsCounter = 0
|
||||
self._attemptIntervalCounter = 0
|
||||
self._deadCounter = 0
|
||||
self._disconnectedAtStartup = true
|
||||
self._firstAttempt = true
|
||||
end
|
||||
self._ifaceRestartCounter = 0
|
||||
end
|
||||
end
|
||||
|
||||
function Module:onExit()
|
||||
return true
|
||||
end
|
||||
|
||||
return Module
|
||||
45
internet-detector-mod-telegram/Makefile
Normal file
45
internet-detector-mod-telegram/Makefile
Normal file
@ -0,0 +1,45 @@
|
||||
#
|
||||
# (с) 2026 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector)
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=internet-detector-mod-telegram
|
||||
PKG_VERSION:=1.7.3
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=gSpot <https://github.com/gSpotx2f/luci-app-internet-detector>
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/$(PKG_NAME)
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
TITLE:=Telegram messenger module for internet-detector
|
||||
URL:=https://github.com/gSpotx2f/luci-app-internet-detector
|
||||
PKGARCH:=all
|
||||
DEPENDS:=+internet-detector +curl +ucode +ucode-mod-fs
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/description
|
||||
Telegram messenger support for internet-detector.
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/conffiles
|
||||
endef
|
||||
|
||||
define Build/Configure
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_DIR) $(1)/usr/lib/lua/internet-detector/modules
|
||||
$(INSTALL_DATA) ./files/usr/lib/lua/internet-detector/modules/mod_telegram.lua $(1)/usr/lib/lua/internet-detector/modules/mod_telegram.lua
|
||||
$(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d
|
||||
$(INSTALL_DATA) ./files/usr/share/rpcd/acl.d/luci-app-internet-detector-mod-telegram.json $(1)/usr/share/rpcd/acl.d/luci-app-internet-detector-mod-telegram.json
|
||||
$(INSTALL_DIR) $(1)/usr/share/rpcd/ucode
|
||||
$(INSTALL_DATA) ./files/usr/share/rpcd/ucode/luci.internet-detector-mod-telegram $(1)/usr/share/rpcd/ucode/luci.internet-detector-mod-telegram
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
@ -0,0 +1,306 @@
|
||||
--[[
|
||||
Dependences:
|
||||
curl
|
||||
--]]
|
||||
|
||||
local unistd = require("posix.unistd")
|
||||
|
||||
local Module = {
|
||||
name = "mod_telegram",
|
||||
runPrio = 70,
|
||||
syslog = function(level, msg) return true end,
|
||||
debugOutput = function(msg) return true end,
|
||||
writeValue = function(filePath, str) return false end,
|
||||
readValue = function(filePath) return nil end,
|
||||
deadPeriod = 0,
|
||||
alivePeriod = 0,
|
||||
mode = 0, -- 0: connected, 1: disconnected, 2: both
|
||||
hostAlias = "OpenWrt",
|
||||
connectTimeout = 5,
|
||||
tgAPIToken = nil,
|
||||
tgChatId = nil,
|
||||
tgMsgURLpattern = "https://api.telegram.org/bot%s/sendMessage?chat_id=%s&parse_mode=html&text=%s",
|
||||
msgTextPattern = "<strong>[%s] (%s)</strong> @ %s", -- Message (host, instance, message)
|
||||
msgConnectPattern = "Connected: %s",
|
||||
msgDisconnectPattern = "Disconnected: %s",
|
||||
msgSeparator = " | ",
|
||||
msgMaxItems = 50,
|
||||
msgSendAttempts = 3,
|
||||
msgSendTimeout = 5,
|
||||
curlExec = "/usr/bin/curl",
|
||||
curlParams = "-s -g --no-keepalive",
|
||||
proxyAuthString = "",
|
||||
proxyString = "",
|
||||
ifaceString = "",
|
||||
status = nil,
|
||||
_enabled = false,
|
||||
_deadCounter = 0,
|
||||
_aliveCounter = 0,
|
||||
_msgSentDisconnect = true,
|
||||
_disconnected = true,
|
||||
_msgSentConnect = true,
|
||||
_connected = true,
|
||||
_msgBuffer = {},
|
||||
_msgSendCounter = 3,
|
||||
_msgTimeoutCounter = 5,
|
||||
}
|
||||
|
||||
local function prequire(package)
|
||||
local retVal, pkg = pcall(require, package)
|
||||
return retVal and pkg
|
||||
end
|
||||
|
||||
function Module:init(t)
|
||||
self._enabled = true
|
||||
if t.mode ~= nil then
|
||||
self.mode = tonumber(t.mode)
|
||||
end
|
||||
if t.dead_period ~= nil then
|
||||
self.deadPeriod = tonumber(t.dead_period)
|
||||
end
|
||||
if t.alive_period ~= nil then
|
||||
self.alivePeriod = tonumber(t.alive_period)
|
||||
end
|
||||
if t.host_alias then
|
||||
self.hostAlias = t.host_alias
|
||||
else
|
||||
self.hostAlias = self.config.hostname
|
||||
end
|
||||
if t.api_token ~= nil then
|
||||
self.tgAPIToken = t.api_token
|
||||
end
|
||||
if t.chat_id ~= nil then
|
||||
self.tgChatId = t.chat_id
|
||||
end
|
||||
if (t.proxy_type and t.proxy_host and t.proxy_port) then
|
||||
if t.proxy_user and t.proxy_passwd then
|
||||
self.proxyAuthString = string.format(
|
||||
' --proxy-user "%s:%s"',
|
||||
t.proxy_user,
|
||||
t.proxy_passwd)
|
||||
end
|
||||
self.proxyString = string.format(
|
||||
" --proxy %s://%s:%d%s",
|
||||
t.proxy_type,
|
||||
t.proxy_host,
|
||||
t.proxy_port,
|
||||
self.proxyAuthString)
|
||||
end
|
||||
if t.iface then
|
||||
self.ifaceString = " --interface " .. t.iface
|
||||
end
|
||||
if tonumber(t.message_at_startup) == 1 then
|
||||
self._msgSentDisconnect = false
|
||||
self._disconnected = false
|
||||
self._msgSentConnect = false
|
||||
self._connected = false
|
||||
end
|
||||
|
||||
if unistd.access(self.curlExec, "x") then
|
||||
self._enabled = true
|
||||
else
|
||||
self._enabled = false
|
||||
self.syslog("err", string.format("%s: %s is not available", self.name, self.curlExec))
|
||||
end
|
||||
if not self.tgAPIToken then
|
||||
self._enabled = false
|
||||
self.syslog("err", string.format("%s: Telegram bot API token not specified.", self.name))
|
||||
end
|
||||
if not self.tgChatId then
|
||||
self._enabled = false
|
||||
self.syslog("err", string.format("%s: Telegram chat ID not specified.", self.name))
|
||||
end
|
||||
|
||||
self._msgSendCounter = self.msgSendAttempts
|
||||
end
|
||||
|
||||
function Module:escape(str)
|
||||
local t = {}
|
||||
for i in str:gmatch(".") do
|
||||
if i:match("[^%w_]") then
|
||||
t[#t + 1] = "%" .. string.format("%x", string.byte(i))
|
||||
else
|
||||
t[#t + 1] = i
|
||||
end
|
||||
end
|
||||
return table.concat(t)
|
||||
end
|
||||
|
||||
function Module:appendNotice(str)
|
||||
self._msgBuffer[#self._msgBuffer + 1] = str
|
||||
if #self._msgBuffer > self.msgMaxItems then
|
||||
local t = {}
|
||||
for i = #self._msgBuffer - self.msgMaxItems + 1, #self._msgBuffer do
|
||||
t[#t + 1] = self._msgBuffer[i]
|
||||
end
|
||||
self._msgBuffer = t
|
||||
end
|
||||
end
|
||||
|
||||
function Module:httpRequest(url)
|
||||
local retCode = 1, data
|
||||
|
||||
self.debugOutput(string.format("--- %s ---", self.name))
|
||||
|
||||
local curl = string.format(
|
||||
'%s%s%s --connect-timeout %s %s "%s"; printf "\n$?";',
|
||||
self.curlExec,
|
||||
self.ifaceString,
|
||||
self.proxyString,
|
||||
self.connectTimeout,
|
||||
self.curlParams,
|
||||
url
|
||||
)
|
||||
|
||||
self.debugOutput(curl)
|
||||
|
||||
local fh = io.popen(curl, "r")
|
||||
if fh then
|
||||
data = fh:read("*a")
|
||||
fh:close()
|
||||
if data ~= nil then
|
||||
local s, e = data:find("[0-9]+\n?$")
|
||||
retCode = tonumber(data:sub(s))
|
||||
data = data:sub(0, s - 2)
|
||||
if not data or data == "" then
|
||||
data = nil
|
||||
end
|
||||
end
|
||||
else
|
||||
retCode = 1
|
||||
end
|
||||
|
||||
self.debugOutput(string.format(
|
||||
"data = %s; retCode = %s\n",
|
||||
tostring(data),
|
||||
tostring(retCode)))
|
||||
|
||||
return retCode, data
|
||||
end
|
||||
|
||||
function Module:parseResponse(str)
|
||||
local ok, errCode, desc
|
||||
ok = str:match('"ok":(%w+)')
|
||||
if ok == "false" then
|
||||
errCode = tonumber(str:match('"error_code":(%d+)'))
|
||||
if errCode then
|
||||
desc = str:match('"description":"([%w%s%p_]+)"')
|
||||
end
|
||||
end
|
||||
return ok, errCode, desc
|
||||
end
|
||||
|
||||
function Module:messageRequest(msg, textPattern)
|
||||
local retVal = 1
|
||||
local tgMsg = string.format(
|
||||
textPattern, self.hostAlias, self.config.serviceConfig.instance, msg)
|
||||
local url = string.format(
|
||||
self.tgMsgURLpattern, self.tgAPIToken, self.tgChatId, self:escape(tgMsg))
|
||||
|
||||
local ok, errCode, desc
|
||||
local retCode, data = self:httpRequest(url)
|
||||
if data then
|
||||
ok, errCode, desc = self:parseResponse(data)
|
||||
end
|
||||
if retCode == 0 and ok == "true" then
|
||||
retVal = 0
|
||||
self.syslog("info", string.format(
|
||||
"%s: Message sent to chat %s", self.name, self.tgChatId))
|
||||
else
|
||||
if errCode == 400 or errCode == 406 then
|
||||
retVal = 2
|
||||
elseif (errCode == 401 or
|
||||
errCode == 403 or
|
||||
errCode == 404 or
|
||||
errCode == 420) then
|
||||
retVal = 3
|
||||
end
|
||||
if errCode and desc then
|
||||
self.syslog("warning", string.format(
|
||||
"%s: %s %s", self.name, tostring(errCode), tostring(desc)))
|
||||
end
|
||||
end
|
||||
return retVal
|
||||
end
|
||||
|
||||
function Module:sendMessage(msg, textPattern)
|
||||
local retVal = self:messageRequest(msg, textPattern)
|
||||
if retVal == 0 then
|
||||
self._msgBuffer = {}
|
||||
elseif retVal == 2 then
|
||||
self.syslog("err", string.format(
|
||||
"%s: Server error (invalid API token or chat ID)", self.name))
|
||||
else
|
||||
self.syslog("err", string.format(
|
||||
"%s: An error occured while sending message", self.name))
|
||||
end
|
||||
return retVal
|
||||
end
|
||||
|
||||
function Module:run(currentStatus, lastStatus, timeDiff, timeNow, inetChecked)
|
||||
if not self._enabled then
|
||||
return
|
||||
end
|
||||
|
||||
if currentStatus == 1 then
|
||||
self._aliveCounter = 0
|
||||
self._msgSentConnect = false
|
||||
|
||||
if not self._disconnected then
|
||||
self._disconnected = true
|
||||
self:appendNotice(string.format(
|
||||
self.msgDisconnectPattern, os.date("%Y.%m.%d %H:%M:%S", os.time())))
|
||||
end
|
||||
if not self._msgSentDisconnect and (self.mode == 1 or self.mode == 2) then
|
||||
if self._deadCounter >= self.deadPeriod then
|
||||
self._msgSendCounter = 0
|
||||
self._msgSentDisconnect = true
|
||||
else
|
||||
self._deadCounter = self._deadCounter + timeDiff
|
||||
end
|
||||
end
|
||||
self._connected = false
|
||||
elseif currentStatus == 0 then
|
||||
self._deadCounter = 0
|
||||
self._msgSentDisconnect = false
|
||||
|
||||
if not self._connected then
|
||||
self._connected = true
|
||||
self:appendNotice(string.format(
|
||||
self.msgConnectPattern, os.date("%Y.%m.%d %H:%M:%S", os.time())))
|
||||
end
|
||||
if not self._msgSentConnect and (self.mode == 0 or self.mode == 2) then
|
||||
if self._aliveCounter >= self.alivePeriod then
|
||||
self._msgSendCounter = 0
|
||||
self._msgSentConnect = true
|
||||
else
|
||||
self._aliveCounter = self._aliveCounter + timeDiff
|
||||
end
|
||||
end
|
||||
self._disconnected = false
|
||||
end
|
||||
|
||||
if self._msgSendCounter < self.msgSendAttempts then
|
||||
if self._msgTimeoutCounter >= self.msgSendTimeout then
|
||||
if #self._msgBuffer > 0 then
|
||||
local retVal = self:sendMessage(table.concat(self._msgBuffer, self.msgSeparator), self.msgTextPattern)
|
||||
if retVal == 1 then
|
||||
self._msgSendCounter = self._msgSendCounter + 1
|
||||
else
|
||||
self._msgSendCounter = self.msgSendAttempts
|
||||
end
|
||||
end
|
||||
self._msgTimeoutCounter = 0
|
||||
else
|
||||
self._msgTimeoutCounter = self._msgTimeoutCounter + timeDiff
|
||||
end
|
||||
else
|
||||
self._msgTimeoutCounter = self.msgSendTimeout
|
||||
end
|
||||
end
|
||||
|
||||
function Module:onExit()
|
||||
return true
|
||||
end
|
||||
|
||||
return Module
|
||||
@ -0,0 +1,10 @@
|
||||
{
|
||||
"luci-app-internet-detector-mod-telegram": {
|
||||
"description": "Grant access to internet-detector-mod-telegram procedures",
|
||||
"read": {
|
||||
"ubus": {
|
||||
"luci.internet-detector-mod-telegram": [ "GetTgChatId" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,128 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { popen, stat } from 'fs';
|
||||
|
||||
const curlPath = '/usr/bin/curl';
|
||||
const curlExec = (stat(curlPath)?.perm?.user_exec) ? curlPath : null;
|
||||
const curlParams = '-s -g --no-keepalive';
|
||||
const curlConnectTimeout = 15;
|
||||
const tgUpdatesURLPattern = 'https://api.telegram.org/bot%s/getUpdates';
|
||||
|
||||
if(!curlExec) {
|
||||
die('Error! Curl not found!');
|
||||
}
|
||||
|
||||
function httpRequest(url, proxyType, proxyHost, proxyPort, proxyUser, proxyPasswd, iface) {
|
||||
let retCode = 1;
|
||||
let data = '';
|
||||
let proxyAuthString = '';
|
||||
let proxyString = '';
|
||||
let ifaceString = iface ? (' --interface ' + iface) : '';
|
||||
|
||||
if(!url) {
|
||||
return { retCode: 1, data };
|
||||
}
|
||||
|
||||
if(proxyType && proxyHost && proxyPort) {
|
||||
if(proxyUser && proxyPasswd) {
|
||||
proxyAuthString = sprintf(
|
||||
' --proxy-user "%s:%s"',
|
||||
proxyUser,
|
||||
proxyPasswd);
|
||||
}
|
||||
proxyString = sprintf(
|
||||
' --proxy %s://%s:%d%s',
|
||||
proxyType,
|
||||
proxyHost,
|
||||
proxyPort,
|
||||
proxyAuthString);
|
||||
}
|
||||
const curl = sprintf(
|
||||
'%s%s%s --connect-timeout %s %s "%s"; printf "\n$?";',
|
||||
curlExec,
|
||||
ifaceString,
|
||||
proxyString,
|
||||
curlConnectTimeout,
|
||||
curlParams,
|
||||
url
|
||||
);
|
||||
const fd = popen(curl, 'r');
|
||||
if(fd) {
|
||||
data = fd.read('all');
|
||||
fd.close();
|
||||
if(data) {
|
||||
const retCodeRegexp = /[0-9]+\n?$/;
|
||||
const r = match(data, retCodeRegexp);
|
||||
if(length(r) > 0) {
|
||||
retCode = r[0];
|
||||
}
|
||||
data = replace(data, retCodeRegexp, '');
|
||||
}
|
||||
}
|
||||
return { retCode, data };
|
||||
}
|
||||
|
||||
function parseResponse(str) {
|
||||
let ok = null;
|
||||
let chatId = [];
|
||||
let errCode = null;
|
||||
let errDesc = null;
|
||||
|
||||
const data = json(str);
|
||||
ok = data.ok;
|
||||
if(ok == false) {
|
||||
errCode = data.error_code;
|
||||
errDesc = data.description;
|
||||
} else {
|
||||
let chats = [];
|
||||
if(data.result) {
|
||||
for(let i in data.result) {
|
||||
if(i.message && i.message.chat && i.message.chat.id) {
|
||||
push(chats, i.message.chat.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(length(chats) > 0) {
|
||||
chatId = uniq(chats);
|
||||
}
|
||||
}
|
||||
return { ok, chatId, errCode, errDesc };
|
||||
}
|
||||
|
||||
const methods = {
|
||||
GetTgChatId: {
|
||||
args: {
|
||||
botToken : 'String',
|
||||
proxyType : 'String',
|
||||
proxyHost : 'String',
|
||||
proxyPort : 'String',
|
||||
proxyUser : 'String',
|
||||
proxyPasswd: 'String',
|
||||
iface : 'String',
|
||||
},
|
||||
call: function(request) {
|
||||
const botToken = request.args?.botToken;
|
||||
if(botToken) {
|
||||
const ret = httpRequest(
|
||||
sprintf(tgUpdatesURLPattern, botToken),
|
||||
request.args.proxyType,
|
||||
request.args.proxyHost,
|
||||
request.args.proxyPort,
|
||||
request.args.proxyUser,
|
||||
request.args.proxyPasswd,
|
||||
request.args.iface
|
||||
);
|
||||
if(ret.retCode == 0 && length(ret.data) > 0) {
|
||||
return parseResponse(ret.data);
|
||||
} else {
|
||||
return { ok: null, chatId: [], errCode: ret.retCode, errDesc: 'Network request error' };
|
||||
}
|
||||
} else {
|
||||
return { ok: null, chatId: [], errCode: 1, errDesc: 'Bot token missing' };
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return { 'luci.internet-detector-mod-telegram': methods };
|
||||
67
internet-detector/Makefile
Normal file
67
internet-detector/Makefile
Normal file
@ -0,0 +1,67 @@
|
||||
#
|
||||
# (с) 2026 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector)
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=internet-detector
|
||||
PKG_VERSION:=1.7.3
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=gSpot <https://github.com/gSpotx2f/luci-app-internet-detector>
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/$(PKG_NAME)
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
TITLE:=Internet detector
|
||||
URL:=https://github.com/gSpotx2f/luci-app-internet-detector
|
||||
PKGARCH:=all
|
||||
DEPENDS:=+lua +luaposix +libuci-lua
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/description
|
||||
Internet-detector is a small daemon
|
||||
for checking Internet availability.
|
||||
Written in Lua using the luaposix library.
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/conffiles
|
||||
/etc/config/internet-detector
|
||||
/etc/internet-detector/down-script.internet
|
||||
/etc/internet-detector/up-script.internet
|
||||
/etc/internet-detector/public-ip-script.internet
|
||||
/etc/internet-detector/regular-script.internet
|
||||
endef
|
||||
|
||||
define Build/Configure
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_DIR) $(1)/etc/config
|
||||
$(INSTALL_CONF) ./files/etc/config/internet-detector $(1)/etc/config/internet-detector
|
||||
$(INSTALL_DIR) $(1)/etc/internet-detector
|
||||
$(INSTALL_DATA) ./files/etc/internet-detector/down-script.internet $(1)/etc/internet-detector/down-script.internet
|
||||
$(INSTALL_DATA) ./files/etc/internet-detector/up-script.internet $(1)/etc/internet-detector/up-script.internet
|
||||
$(INSTALL_DATA) ./files/etc/internet-detector/public-ip-script.internet $(1)/etc/internet-detector/public-ip-script.internet
|
||||
$(INSTALL_DATA) ./files/etc/internet-detector/regular-script.internet $(1)/etc/internet-detector/regular-script.internet
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_BIN) ./files/etc/init.d/internet-detector $(1)/etc/init.d/internet-detector
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) ./files/usr/bin/internet-detector $(1)/usr/bin/internet-detector
|
||||
$(INSTALL_DIR) $(1)/usr/lib/lua/internet-detector
|
||||
$(INSTALL_DATA) ./files/usr/lib/lua/internet-detector/main.lua $(1)/usr/lib/lua/internet-detector/main.lua
|
||||
$(INSTALL_DATA) ./files/usr/lib/lua/internet-detector/init.lua $(1)/usr/lib/lua/internet-detector/init.lua
|
||||
$(INSTALL_DIR) $(1)/usr/lib/lua/internet-detector/modules
|
||||
$(INSTALL_DATA) ./files/usr/lib/lua/internet-detector/modules/mod_led_control.lua $(1)/usr/lib/lua/internet-detector/modules/mod_led_control.lua
|
||||
$(INSTALL_DATA) ./files/usr/lib/lua/internet-detector/modules/mod_reboot.lua $(1)/usr/lib/lua/internet-detector/modules/mod_reboot.lua
|
||||
$(INSTALL_DATA) ./files/usr/lib/lua/internet-detector/modules/mod_network_restart.lua $(1)/usr/lib/lua/internet-detector/modules/mod_network_restart.lua
|
||||
$(INSTALL_DATA) ./files/usr/lib/lua/internet-detector/modules/mod_public_ip.lua $(1)/usr/lib/lua/internet-detector/modules/mod_public_ip.lua
|
||||
$(INSTALL_DATA) ./files/usr/lib/lua/internet-detector/modules/mod_user_scripts.lua $(1)/usr/lib/lua/internet-detector/modules/mod_user_scripts.lua
|
||||
$(INSTALL_DATA) ./files/usr/lib/lua/internet-detector/modules/mod_regular_script.lua $(1)/usr/lib/lua/internet-detector/modules/mod_regular_script.lua
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
65
internet-detector/files/etc/config/internet-detector
Normal file
65
internet-detector/files/etc/config/internet-detector
Normal file
@ -0,0 +1,65 @@
|
||||
|
||||
config main 'config'
|
||||
option mode '1'
|
||||
option logging_level '6'
|
||||
|
||||
config instance 'internet'
|
||||
option enabled '1'
|
||||
option description 'Default instance'
|
||||
list hosts '8.8.8.8'
|
||||
list hosts '1.1.1.1'
|
||||
list urls 'https://www.google.com'
|
||||
option check_type '0'
|
||||
option tcp_port '53'
|
||||
option interval_up '30'
|
||||
option interval_down '5'
|
||||
option connection_attempts '2'
|
||||
option connection_timeout '2'
|
||||
option mod_led_control_enabled '0'
|
||||
option mod_reboot_enabled '0'
|
||||
option mod_reboot_disconnected_at_startup '0'
|
||||
option mod_reboot_dead_period '3600'
|
||||
option mod_reboot_force_reboot_delay '300'
|
||||
option mod_network_restart_enabled '0'
|
||||
option mod_network_restart_disconnected_at_startup '0'
|
||||
option mod_network_restart_dead_period '900'
|
||||
option mod_network_restart_attempts '1'
|
||||
option mod_network_restart_attempt_interval '60'
|
||||
option mod_network_restart_device_timeout '0'
|
||||
option mod_modem_restart_enabled '0'
|
||||
option mod_modem_restart_disconnected_at_startup '0'
|
||||
option mod_modem_restart_dead_period '600'
|
||||
option mod_modem_restart_attempts '1'
|
||||
option mod_modem_restart_attempt_interval '60'
|
||||
option mod_modem_restart_iface_timeout '0'
|
||||
option mod_modem_restart_any_band '0'
|
||||
option mod_public_ip_enabled '0'
|
||||
option mod_public_ip_provider 'opendns1'
|
||||
option mod_public_ip_qtype '0'
|
||||
option mod_public_ip_interval '600'
|
||||
option mod_public_ip_interval_failed '60'
|
||||
option mod_public_ip_request_attempts '2'
|
||||
option mod_public_ip_timeout '2'
|
||||
option mod_public_ip_enable_ip_script '0'
|
||||
option mod_email_enabled '0'
|
||||
option mod_email_message_at_startup '0'
|
||||
option mod_email_mode '0'
|
||||
option mod_email_alive_period '0'
|
||||
option mod_email_mail_security 'tls'
|
||||
option mod_telegram_enabled '0'
|
||||
option mod_telegram_message_at_startup '0'
|
||||
option mod_telegram_mode '0'
|
||||
option mod_telegram_dead_period '0'
|
||||
option mod_telegram_alive_period '0'
|
||||
option mod_user_scripts_enabled '0'
|
||||
option mod_user_scripts_alive_period '0'
|
||||
option mod_user_scripts_up_script_attempts '1'
|
||||
option mod_user_scripts_up_script_attempt_interval '60'
|
||||
option mod_user_scripts_connected_at_startup '0'
|
||||
option mod_user_scripts_dead_period '0'
|
||||
option mod_user_scripts_down_script_attempts '1'
|
||||
option mod_user_scripts_down_script_attempt_interval '60'
|
||||
option mod_user_scripts_disconnected_at_startup '0'
|
||||
option mod_regular_script_enabled '0'
|
||||
option mod_regular_script_inet_state '2'
|
||||
option mod_regular_script_interval '3600'
|
||||
39
internet-detector/files/etc/init.d/internet-detector
Executable file
39
internet-detector/files/etc/init.d/internet-detector
Executable file
@ -0,0 +1,39 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
START=97
|
||||
STOP=01
|
||||
|
||||
USE_PROCD=1
|
||||
PROG="/usr/bin/internet-detector"
|
||||
|
||||
run_instance() {
|
||||
config_get enabled "$1" enabled "0"
|
||||
if [ $enabled = "1" ]; then
|
||||
procd_open_instance "$1"
|
||||
procd_set_param command "$PROG" "-a" "nodaemon" "-i" "$1"
|
||||
procd_set_param respawn
|
||||
procd_set_param term_timeout 60
|
||||
procd_close_instance
|
||||
fi
|
||||
}
|
||||
|
||||
start_service() {
|
||||
config_load "internet-detector"
|
||||
config_get mode "config" mode "0"
|
||||
if [ $mode = "1" ]; then
|
||||
config_foreach run_instance "instance"
|
||||
fi
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
$PROG -a stop
|
||||
}
|
||||
|
||||
reload_service() {
|
||||
stop
|
||||
start
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger "internet-detector"
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
# Shell commands to run when disconnected from the Internet
|
||||
@ -0,0 +1,2 @@
|
||||
# Shell commands that run when the public IP address changes.
|
||||
# New IP is available as value of the $PUBLIC_IP variable.
|
||||
@ -0,0 +1 @@
|
||||
# Shell commands that are run regularly
|
||||
@ -0,0 +1 @@
|
||||
# Shell commands that run when connected to the Internet
|
||||
98
internet-detector/files/usr/bin/internet-detector
Executable file
98
internet-detector/files/usr/bin/internet-detector
Executable file
@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env lua
|
||||
|
||||
--[[
|
||||
Internet detector daemon for OpenWrt.
|
||||
|
||||
Dependences:
|
||||
lua
|
||||
luaposix
|
||||
libuci-lua
|
||||
|
||||
(с) 2025 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector)
|
||||
--]]
|
||||
|
||||
local getopt = require("posix.unistd").getopt
|
||||
local InternetDetector = require("internet-detector.main")
|
||||
|
||||
local function help()
|
||||
return table.concat({
|
||||
[1] = string.format(
|
||||
"Usage: %s -a daemon -i <UCI instance> | -a nodaemon -i <UCI instance> | -a debug -i <UCI instance> | -a stop | -S | -I | -U | -h",
|
||||
arg[0]),
|
||||
[2] = " -a ARG action: daemon | nodaemon | debug | stop",
|
||||
[3] = " -i ARG instance: UCI instance name",
|
||||
[4] = " -S status",
|
||||
[5] = " -I inet status",
|
||||
[6] = " -U uipoll",
|
||||
[7] = " -h print this help text"
|
||||
}, "\n")
|
||||
end
|
||||
|
||||
local action, instance
|
||||
local params = {}
|
||||
local last_index = 1
|
||||
for r, optarg, optind in getopt(arg, "a:i:SIUh") do
|
||||
if r == "?" then
|
||||
print("Error! Unrecognized option")
|
||||
os.exit(1)
|
||||
end
|
||||
last_index = optind
|
||||
if r == "a" then
|
||||
action = optarg
|
||||
elseif r == "i" then
|
||||
instance = optarg
|
||||
else
|
||||
params[#params + 1] = r
|
||||
end
|
||||
end
|
||||
if action == "stop" then
|
||||
InternetDetector:stop()
|
||||
elseif action then
|
||||
if not instance then
|
||||
print("Error! Instance not specified [-i]")
|
||||
os.exit(1)
|
||||
end
|
||||
if action == "daemon" then
|
||||
if InternetDetector:setServiceConfig(instance) then
|
||||
InternetDetector:daemon()
|
||||
else
|
||||
os.exit(126)
|
||||
end
|
||||
elseif action == "nodaemon" then
|
||||
if InternetDetector:setServiceConfig(instance) then
|
||||
InternetDetector:noDaemon()
|
||||
else
|
||||
os.exit(126)
|
||||
end
|
||||
elseif action == "debug" then
|
||||
if InternetDetector:setServiceConfig(instance) then
|
||||
InternetDetector.debug = true
|
||||
InternetDetector:noDaemon()
|
||||
else
|
||||
os.exit(126)
|
||||
end
|
||||
else
|
||||
print("Error! Wrong action [-a]")
|
||||
os.exit(1)
|
||||
end
|
||||
else
|
||||
if params[1] == "S" then
|
||||
print(InternetDetector:status())
|
||||
elseif params[1] == "I" then
|
||||
print(InternetDetector:inetStatus())
|
||||
elseif params[1] == "U" then
|
||||
if InternetDetector:status() == "stoped" then
|
||||
os.exit(126)
|
||||
else
|
||||
InternetDetector:setSIGUSR()
|
||||
print(InternetDetector:inetStatus())
|
||||
end
|
||||
elseif params[1] == "h" then
|
||||
print(help())
|
||||
else
|
||||
print(help())
|
||||
os.exit(1)
|
||||
end
|
||||
end
|
||||
|
||||
os.exit(0)
|
||||
@ -0,0 +1 @@
|
||||
return require("internet-detector.main")
|
||||
806
internet-detector/files/usr/lib/lua/internet-detector/main.lua
Normal file
806
internet-detector/files/usr/lib/lua/internet-detector/main.lua
Normal file
@ -0,0 +1,806 @@
|
||||
|
||||
local dirent = require("posix.dirent")
|
||||
local fcntl = require("posix.fcntl")
|
||||
local signal = require("posix.signal")
|
||||
local socket = require("posix.sys.socket")
|
||||
local stat = require("posix.sys.stat")
|
||||
local syslog = require("posix.syslog")
|
||||
local time = require("posix.time")
|
||||
local unistd = require("posix.unistd")
|
||||
local uci = require("uci")
|
||||
|
||||
-- Default settings
|
||||
|
||||
local InternetDetector = {
|
||||
appName = "internet-detector",
|
||||
libDir = "/usr/lib/lua",
|
||||
logLevels = {
|
||||
emerg = { level = syslog.LOG_EMERG, num = 0 },
|
||||
alert = { level = syslog.LOG_ALERT, num = 1 },
|
||||
crit = { level = syslog.LOG_CRIT, num = 2 },
|
||||
err = { level = syslog.LOG_ERR, num = 3 },
|
||||
warning = { level = syslog.LOG_WARNING, num = 4 },
|
||||
notice = { level = syslog.LOG_NOTICE, num = 5 },
|
||||
info = { level = syslog.LOG_INFO, num = 6 },
|
||||
debug = { level = syslog.LOG_DEBUG, num = 7 },
|
||||
},
|
||||
pingCmd = "/bin/ping",
|
||||
pingParams = "-c 1",
|
||||
curlExec = "/usr/bin/curl",
|
||||
curlParams = '-s -g --no-keepalive --head --user-agent "Mozilla/5.0 (X11; Linux x86_64; rv:142.0) Gecko/20100101 Firefox/142.0"',
|
||||
mode = 0, -- 0: disabled, 1: Service, 2: UI detector
|
||||
loggingLevel = 6,
|
||||
hostname = "OpenWrt",
|
||||
uiRunTime = 30,
|
||||
noModules = false,
|
||||
uiAvailModules = { mod_public_ip = true },
|
||||
debug = false,
|
||||
serviceConfig = {
|
||||
hosts = {
|
||||
[1] = "8.8.8.8",
|
||||
[2] = "1.1.1.1",
|
||||
},
|
||||
urls = {
|
||||
[1] = "https://www.google.com",
|
||||
},
|
||||
check_type = 0, -- 0: TCP, 1: ICMP
|
||||
tcp_port = 53,
|
||||
icmp_packet_size = 56,
|
||||
interval_up = 30,
|
||||
interval_down = 5,
|
||||
connection_attempts = 2,
|
||||
connection_timeout = 2,
|
||||
proxy_type = nil,
|
||||
proxy_host = nil,
|
||||
proxy_port = nil,
|
||||
proxy_user = nil,
|
||||
proxy_passwd = nil,
|
||||
iface = nil,
|
||||
instance = nil,
|
||||
},
|
||||
modules = {},
|
||||
parsedHosts = {},
|
||||
proxyAuthString = "",
|
||||
proxyString = "",
|
||||
uiCounter = 0,
|
||||
pidFile = nil,
|
||||
statusFile = nil,
|
||||
}
|
||||
InternetDetector.configDir = string.format("/etc/%s", InternetDetector.appName)
|
||||
InternetDetector.modulesDir = string.format(
|
||||
"%s/%s/modules", InternetDetector.libDir, InternetDetector.appName)
|
||||
InternetDetector.commonDir = string.format("/tmp/run/%s", InternetDetector.appName)
|
||||
InternetDetector.appNamePattern = InternetDetector.appName:gsub("-", "%%-")
|
||||
InternetDetector.pidFilePattern = "^" .. InternetDetector.appNamePattern .. ".-%.pid$"
|
||||
|
||||
-- Loading settings from UCI
|
||||
|
||||
local uciCursor = uci.cursor()
|
||||
local mode, err = uciCursor:get(InternetDetector.appName, "config", "mode")
|
||||
if mode ~= nil then
|
||||
InternetDetector.mode = tonumber(mode)
|
||||
elseif err then
|
||||
io.stderr:write(string.format("Error: %s\n", err))
|
||||
end
|
||||
local loggingLevel, err = uciCursor:get(InternetDetector.appName, "config", "logging_level")
|
||||
if loggingLevel ~= nil then
|
||||
InternetDetector.loggingLevel = tonumber(loggingLevel)
|
||||
elseif err then
|
||||
io.stderr:write(string.format("Error: %s\n", err))
|
||||
end
|
||||
local hostname, err = uciCursor:get("system", "@[0]", "hostname")
|
||||
if hostname ~= nil then
|
||||
InternetDetector.hostname = hostname
|
||||
elseif err then
|
||||
io.stderr:write(string.format("Error: %s\n", err))
|
||||
end
|
||||
|
||||
function InternetDetector:prequire(package)
|
||||
local ok, pkg = pcall(require, package)
|
||||
return ok and pkg
|
||||
end
|
||||
|
||||
function InternetDetector:loadInstanceConfig(instance)
|
||||
local sections = uciCursor:get_all(self.appName)
|
||||
local t = sections[instance]
|
||||
if t then
|
||||
for k, v in pairs(t) do
|
||||
if type(v) == "string" and v:match("^[%d]+$") then
|
||||
v = tonumber(v)
|
||||
end
|
||||
self.serviceConfig[k] = v
|
||||
end
|
||||
self.serviceConfig.instance = instance
|
||||
self.serviceConfig.instanceNum = t[".index"]
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function InternetDetector:writeValueToFile(filePath, str)
|
||||
local retValue = false
|
||||
local fh = io.open(filePath, "w")
|
||||
if fh then
|
||||
fh:setvbuf("no")
|
||||
fh:write(string.format("%s\n", str))
|
||||
fh:close()
|
||||
retValue = true
|
||||
end
|
||||
return retValue
|
||||
end
|
||||
|
||||
function InternetDetector:readValueFromFile(filePath)
|
||||
local retValue
|
||||
local fh = io.open(filePath, "r")
|
||||
if fh then
|
||||
retValue = fh:read("*l")
|
||||
fh:close()
|
||||
end
|
||||
return retValue
|
||||
end
|
||||
|
||||
function InternetDetector:statusJson(inet, instance, t)
|
||||
local lines = { [1] = string.format(
|
||||
'{"instance":"%s","num":"%d","inet":%d',
|
||||
instance,
|
||||
self.serviceConfig.instanceNum,
|
||||
inet)}
|
||||
if t then
|
||||
for k, v in pairs(t) do
|
||||
lines[#lines + 1] = string.format('"%s":"%s"', k, v)
|
||||
end
|
||||
end
|
||||
return table.concat(lines, ",") .. "}"
|
||||
end
|
||||
|
||||
function InternetDetector:writeLogMessage(level, msg)
|
||||
local levelItem = self.logLevels[level]
|
||||
local levelValue = (levelItem and levelItem.level) or self.logLevels["info"].level
|
||||
local num = (levelItem and levelItem.num) or self.logLevels["info"].num
|
||||
if num <= self.loggingLevel then
|
||||
syslog.syslog(levelValue, string.format(
|
||||
"%s: %s", self.serviceConfig.instance or "", msg))
|
||||
end
|
||||
end
|
||||
|
||||
function InternetDetector:debugOutput(msg)
|
||||
if self.debug then
|
||||
io.stdout:write(string.format("[%s] %s\n", os.date("%Y.%m.%d-%H:%M:%S"), msg))
|
||||
io.stdout:flush()
|
||||
end
|
||||
end
|
||||
|
||||
function InternetDetector:loadModules()
|
||||
self.modules = {}
|
||||
local ok, modulesDir = pcall(dirent.files, self.modulesDir)
|
||||
if ok then
|
||||
for item in modulesDir do
|
||||
if item:match("^mod_") then
|
||||
local modName = item:gsub("%.lua$", "")
|
||||
if self.noModules and not self.uiAvailModules[modName] then
|
||||
else
|
||||
local modConfig = {}
|
||||
for k, v in pairs(self.serviceConfig) do
|
||||
if k:match("^" .. modName) then
|
||||
modConfig[k:gsub("^" .. modName .. "_", "")] = v
|
||||
end
|
||||
end
|
||||
if modConfig.enabled == 1 then
|
||||
local m
|
||||
if self.debug then
|
||||
m = require(string.format("%s.modules.%s", self.appName, modName))
|
||||
else
|
||||
m = self:prequire(string.format("%s.modules.%s", self.appName, modName))
|
||||
end
|
||||
if m then
|
||||
m.config = self
|
||||
m.syslog = function(level, msg) self:writeLogMessage(level, msg) end
|
||||
m.debugOutput = function(msg) self:debugOutput(msg) end
|
||||
m.writeValue = function(filePath, str) return self:writeValueToFile(filePath, str) end
|
||||
m.readValue = function(filePath) return self:readValueFromFile(filePath) end
|
||||
m:init(modConfig)
|
||||
self.modules[#self.modules + 1] = m
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
table.sort(self.modules, function(a, b) return a.runPrio < b.runPrio end)
|
||||
end
|
||||
end
|
||||
|
||||
function InternetDetector:parseHost(host)
|
||||
local addr, port = host:match("^([^%[%]:]+):?(%d?%d?%d?%d?%d?)$")
|
||||
if not addr then
|
||||
addr, port = host:match("^%[?([^%[%]]+)%]?:?(%d?%d?%d?%d?%d?)$")
|
||||
end
|
||||
return addr, tonumber(port)
|
||||
end
|
||||
|
||||
function InternetDetector:parseHosts()
|
||||
self.parsedHosts = {}
|
||||
for k, v in ipairs(self.serviceConfig.hosts) do
|
||||
local addr, port = self:parseHost(v)
|
||||
self.parsedHosts[k] = { addr = addr, port = port }
|
||||
end
|
||||
end
|
||||
|
||||
function InternetDetector:parseUrls()
|
||||
self.parsedHosts = {}
|
||||
for k, v in ipairs(self.serviceConfig.urls) do
|
||||
self.parsedHosts[k] = { addr = v }
|
||||
end
|
||||
end
|
||||
|
||||
function InternetDetector:pingHost(host)
|
||||
local ping = string.format(
|
||||
"%s %s -W %d -s %d%s %s > /dev/null 2>&1",
|
||||
self.pingCmd,
|
||||
self.pingParams,
|
||||
self.serviceConfig.connection_timeout,
|
||||
self.serviceConfig.icmp_packet_size,
|
||||
self.serviceConfig.iface and (" -I " .. self.serviceConfig.iface) or "",
|
||||
host
|
||||
)
|
||||
local retCode = os.execute(ping)
|
||||
|
||||
self:debugOutput(string.format(
|
||||
"--- Ping ---\ntime = %s\n%s\nretCode = %s", os.time(), ping, retCode))
|
||||
|
||||
return retCode
|
||||
end
|
||||
|
||||
function InternetDetector:TCPConnectionToHost(host, port)
|
||||
local retCode = 1
|
||||
local saTable, errMsg, errNum = socket.getaddrinfo(host, port or self.serviceConfig.tcp_port)
|
||||
|
||||
if not saTable then
|
||||
self:debugOutput(string.format(
|
||||
"GETADDRINFO ERROR: %s, %s", errMsg, errNum))
|
||||
else
|
||||
local family = saTable[1].family
|
||||
|
||||
if family then
|
||||
local sock, errMsg, errNum = socket.socket(family, socket.SOCK_STREAM, 0)
|
||||
|
||||
if not sock then
|
||||
self:debugOutput(string.format(
|
||||
"SOCKET ERROR: %s, %s", errMsg, errNum))
|
||||
return retCode
|
||||
end
|
||||
|
||||
socket.setsockopt(sock, socket.SOL_SOCKET,
|
||||
socket.SO_SNDTIMEO, self.serviceConfig.connection_timeout, 0)
|
||||
socket.setsockopt(sock, socket.SOL_SOCKET,
|
||||
socket.SO_RCVTIMEO, self.serviceConfig.connection_timeout, 0)
|
||||
|
||||
if self.serviceConfig.iface then
|
||||
local ok, errMsg, errNum = socket.setsockopt(sock, socket.SOL_SOCKET,
|
||||
socket.SO_BINDTODEVICE, self.serviceConfig.iface)
|
||||
if not ok then
|
||||
self:debugOutput(string.format(
|
||||
"SOCKET ERROR: %s, %s", errMsg, errNum))
|
||||
|
||||
unistd.close(sock)
|
||||
return retCode
|
||||
end
|
||||
end
|
||||
|
||||
local success = socket.connect(sock, saTable[1])
|
||||
|
||||
if self.debug then
|
||||
if not success then
|
||||
self:debugOutput(string.format(
|
||||
"SOCKET CONNECT ERROR: %s", tostring(success)))
|
||||
end
|
||||
local sockTable, err_s, e_s = socket.getsockname(sock)
|
||||
local peerTable, err_p, e_p = socket.getpeername(sock)
|
||||
if not sockTable then
|
||||
sockTable = {}
|
||||
self:debugOutput(
|
||||
string.format("SOCKET ERROR: %s, %s", err_s, e_s))
|
||||
end
|
||||
if not peerTable then
|
||||
peerTable = {}
|
||||
self:debugOutput(
|
||||
string.format("SOCKET ERROR: %s, %s", err_p, e_p))
|
||||
end
|
||||
self:debugOutput(string.format(
|
||||
"--- TCP ---\ntime = %s\nconnection_timeout = %s\niface = %s\nhost:port = [%s]:%s\nsockname = [%s]:%s\npeername = [%s]:%s\nsuccess = %s",
|
||||
os.time(),
|
||||
self.serviceConfig.connection_timeout,
|
||||
tostring(self.serviceConfig.iface),
|
||||
host,
|
||||
port or self.serviceConfig.tcp_port,
|
||||
tostring(sockTable.addr),
|
||||
tostring(sockTable.port),
|
||||
tostring(peerTable.addr),
|
||||
tostring(peerTable.port),
|
||||
tostring(success))
|
||||
)
|
||||
end
|
||||
|
||||
socket.shutdown(sock, socket.SHUT_RDWR)
|
||||
unistd.close(sock)
|
||||
retCode = success and 0 or 1
|
||||
end
|
||||
end
|
||||
return retCode
|
||||
end
|
||||
|
||||
function InternetDetector:httpRequest(url)
|
||||
local retCode = 1, data
|
||||
local curl = string.format(
|
||||
'%s%s%s --connect-timeout %s %s "%s"; printf "\n$?";',
|
||||
self.curlExec,
|
||||
self.serviceConfig.iface and (" --interface " .. self.serviceConfig.iface) or "",
|
||||
self.proxyString,
|
||||
self.serviceConfig.connection_timeout,
|
||||
self.curlParams,
|
||||
url
|
||||
)
|
||||
local fh = io.popen(curl, "r")
|
||||
if fh then
|
||||
data = fh:read("*a")
|
||||
fh:close()
|
||||
if data ~= nil then
|
||||
local s, e = data:find("[0-9]+\n?$")
|
||||
retCode = tonumber(data:sub(s))
|
||||
data = data:sub(0, s - 2)
|
||||
if not data or data == "" then
|
||||
data = nil
|
||||
end
|
||||
end
|
||||
else
|
||||
retCode = 1
|
||||
end
|
||||
|
||||
self:debugOutput(string.format(
|
||||
"--- Curl ---\ntime = %s\n%s\nretCode = %s\ndata = [\n%s]\n",
|
||||
os.time(),
|
||||
curl,
|
||||
retCode,
|
||||
tostring(data)))
|
||||
|
||||
return retCode, data
|
||||
end
|
||||
|
||||
function InternetDetector:getHTTPCode(data)
|
||||
local httpCode
|
||||
local respHeader = data:match("^HTTP/[^%c]+")
|
||||
if respHeader then
|
||||
httpCode = respHeader:match("%d%d%d")
|
||||
end
|
||||
return tonumber(httpCode)
|
||||
end
|
||||
|
||||
function InternetDetector:checkURL(url)
|
||||
local httpCode
|
||||
local retCode, data = self:httpRequest(url)
|
||||
if retCode == 0 and data then
|
||||
httpCode = self:getHTTPCode(data)
|
||||
end
|
||||
return (httpCode ~= 200) and 1 or 0
|
||||
end
|
||||
|
||||
function InternetDetector:exit()
|
||||
for _, e in ipairs(self.modules) do
|
||||
e:onExit()
|
||||
end
|
||||
self:removeProcessFiles()
|
||||
if self.loggingLevel > 0 then
|
||||
self:writeLogMessage("info", "stoped")
|
||||
syslog.closelog()
|
||||
end
|
||||
os.exit(0)
|
||||
end
|
||||
|
||||
function InternetDetector:resetUiCounter(signo)
|
||||
self.uiCounter = 0
|
||||
end
|
||||
|
||||
function InternetDetector:mainLoop()
|
||||
signal.signal(signal.SIGTERM, function(signo) self:exit(signo) end)
|
||||
signal.signal(signal.SIGINT, function(signo) self:exit(signo) end)
|
||||
signal.signal(signal.SIGQUIT, function(signo) self:exit(signo) end)
|
||||
signal.signal(signal.SIGUSR1, function(signo) self:resetUiCounter(signo) end)
|
||||
|
||||
local mTimeNow, mTimeDiff, mLastTime, uiTimeNow, uiLastTime
|
||||
local lastStatus = -1
|
||||
local currentStatus = -1
|
||||
local interval = self.serviceConfig.interval_up
|
||||
local modulesStatus = {}
|
||||
local counter = 0
|
||||
local inetChecked = false
|
||||
local checking = false
|
||||
local hostNum = 1
|
||||
local attempt = 1
|
||||
|
||||
local checkFunc = self.TCPConnectionToHost
|
||||
if self.serviceConfig.check_type == 1 then
|
||||
checkFunc = self.pingHost
|
||||
self:parseHosts()
|
||||
elseif self.serviceConfig.check_type == 2 then
|
||||
checkFunc = self.checkURL
|
||||
self:parseUrls()
|
||||
if (self.serviceConfig.proxy_type and self.serviceConfig.proxy_host and
|
||||
self.serviceConfig.proxy_port) then
|
||||
if self.serviceConfig.proxy_user and self.serviceConfig.proxy_passwd then
|
||||
self.proxyAuthString = string.format(
|
||||
' --proxy-user "%s:%s"',
|
||||
self.serviceConfig.proxy_user,
|
||||
self.serviceConfig.proxy_passwd)
|
||||
end
|
||||
self.proxyString = string.format(
|
||||
" --proxy %s://%s:%d%s",
|
||||
self.serviceConfig.proxy_type,
|
||||
self.serviceConfig.proxy_host,
|
||||
self.serviceConfig.proxy_port,
|
||||
self.proxyAuthString)
|
||||
end
|
||||
else
|
||||
self:parseHosts()
|
||||
end
|
||||
|
||||
self:writeValueToFile(
|
||||
self.statusFile, self:statusJson(currentStatus, self.serviceConfig.instance))
|
||||
|
||||
while true do
|
||||
if counter == 0 or counter >= interval then
|
||||
checking = true
|
||||
end
|
||||
|
||||
inetChecked = false
|
||||
|
||||
if checking then
|
||||
local newStatus = 1
|
||||
if hostNum <= #self.parsedHosts then
|
||||
if attempt <= self.serviceConfig.connection_attempts then
|
||||
local addr = self.parsedHosts[hostNum].addr
|
||||
local port = self.parsedHosts[hostNum].port
|
||||
local retCode = 1
|
||||
if self.debug then
|
||||
retCode = checkFunc(self, addr, port)
|
||||
else
|
||||
local ok, status = pcall(checkFunc, self, addr, port)
|
||||
if ok then
|
||||
retCode = status
|
||||
else
|
||||
self:writeLogMessage("err", string.format(
|
||||
"An error occurred while checking the host %s: %s",
|
||||
tostring(addr),
|
||||
tostring(status))
|
||||
)
|
||||
end
|
||||
end
|
||||
if retCode == 0 then
|
||||
attempt = 1
|
||||
hostNum = 1
|
||||
checking = false
|
||||
newStatus = 0
|
||||
counter = 0
|
||||
inetChecked = true
|
||||
else
|
||||
attempt = attempt + 1
|
||||
if attempt > self.serviceConfig.connection_attempts then
|
||||
attempt = 1
|
||||
hostNum = hostNum + 1
|
||||
end
|
||||
end
|
||||
else
|
||||
attempt = 1
|
||||
hostNum = hostNum + 1
|
||||
end
|
||||
if hostNum > #self.parsedHosts then
|
||||
hostNum = 1
|
||||
checking = false
|
||||
counter = 0
|
||||
inetChecked = true
|
||||
end
|
||||
else
|
||||
hostNum = 1
|
||||
checking = false
|
||||
counter = 0
|
||||
inetChecked = true
|
||||
end
|
||||
|
||||
if inetChecked then
|
||||
currentStatus = newStatus
|
||||
if not stat.stat(self.statusFile) then
|
||||
self:writeValueToFile(self.statusFile, self:statusJson(
|
||||
currentStatus, self.serviceConfig.instance))
|
||||
end
|
||||
if currentStatus == 0 then
|
||||
interval = self.serviceConfig.interval_up
|
||||
if currentStatus ~= lastStatus then
|
||||
self:writeValueToFile(self.statusFile, self:statusJson(
|
||||
currentStatus, self.serviceConfig.instance))
|
||||
self:writeLogMessage("notice", "Connected")
|
||||
end
|
||||
elseif currentStatus == 1 then
|
||||
interval = self.serviceConfig.interval_down
|
||||
if currentStatus ~= lastStatus then
|
||||
self:writeValueToFile(self.statusFile, self:statusJson(
|
||||
currentStatus, self.serviceConfig.instance))
|
||||
self:writeLogMessage("notice", "Disconnected")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
mTimeDiff = 0
|
||||
for _, e in ipairs(self.modules) do
|
||||
mTimeNow = time.clock_gettime(time.CLOCK_MONOTONIC).tv_sec
|
||||
if mLastTime then
|
||||
mTimeDiff = mTimeDiff + mTimeNow - mLastTime
|
||||
else
|
||||
mTimeDiff = 1
|
||||
end
|
||||
mLastTime = mTimeNow
|
||||
|
||||
if self.debug then
|
||||
e:run(currentStatus, lastStatus, mTimeDiff, mTimeNow, inetChecked)
|
||||
else
|
||||
local ok, err = pcall(e.run, e, currentStatus, lastStatus, mTimeDiff, mTimeNow, inetChecked)
|
||||
if not ok then
|
||||
self:writeLogMessage("err", string.format(
|
||||
"%s: Module error: %s", e.name, tostring(err)))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local modStatusChanged = false
|
||||
for k, v in ipairs(self.modules) do
|
||||
if modulesStatus[v.name] ~= v.status then
|
||||
modulesStatus[v.name] = v.status
|
||||
modStatusChanged = true
|
||||
end
|
||||
end
|
||||
if modStatusChanged and next(modulesStatus) then
|
||||
self:writeValueToFile(self.statusFile, self:statusJson(
|
||||
currentStatus, self.serviceConfig.instance, modulesStatus))
|
||||
end
|
||||
|
||||
unistd.sleep(1)
|
||||
|
||||
if not checking then
|
||||
lastStatus = currentStatus
|
||||
counter = counter + 1
|
||||
end
|
||||
|
||||
if self.mode == 2 then
|
||||
uiTimeNow = time.clock_gettime(time.CLOCK_MONOTONIC).tv_sec
|
||||
if uiLastTime then
|
||||
self.uiCounter = self.uiCounter + uiTimeNow - uiLastTime
|
||||
else
|
||||
self.uiCounter = self.uiCounter + 1
|
||||
end
|
||||
uiLastTime = uiTimeNow
|
||||
if self.uiCounter >= self.uiRunTime then
|
||||
self:exit(signal.SIGTERM)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function InternetDetector:removeProcessFiles()
|
||||
os.remove(self.statusFile)
|
||||
os.remove(self.pidFile)
|
||||
end
|
||||
|
||||
function InternetDetector:status()
|
||||
local ok, commonDir = pcall(dirent.files, self.commonDir)
|
||||
if ok then
|
||||
for item in commonDir do
|
||||
if item:match(self.pidFilePattern) then
|
||||
return "running"
|
||||
end
|
||||
end
|
||||
end
|
||||
return "stoped"
|
||||
end
|
||||
|
||||
function InternetDetector:inetStatus()
|
||||
local inetStat = '{"instances":[]}'
|
||||
local ok, commonDir = pcall(dirent.files, self.commonDir)
|
||||
if ok then
|
||||
local statusFilePattern = "^" .. self.appNamePattern .. ".-%.status$"
|
||||
local lines = {}
|
||||
for item in commonDir do
|
||||
if item:match(statusFilePattern) then
|
||||
lines[#lines + 1] = self:readValueFromFile(
|
||||
string.format("%s/%s", self.commonDir, item))
|
||||
end
|
||||
end
|
||||
inetStat = '{"instances":[' .. table.concat(lines, ",") .. "]}"
|
||||
end
|
||||
return inetStat
|
||||
end
|
||||
|
||||
function InternetDetector:stopInstance(pidFile)
|
||||
local retVal = false, pidValue
|
||||
if stat.stat(pidFile) then
|
||||
pidValue = self:readValueFromFile(pidFile)
|
||||
if pidValue then
|
||||
local ok, errMsg, errNum
|
||||
for i = 0, 10 do
|
||||
ok, errMsg, errNum = signal.kill(tonumber(pidValue), signal.SIGTERM)
|
||||
if ok then
|
||||
break
|
||||
end
|
||||
end
|
||||
if not ok then
|
||||
io.stderr:write(string.format(
|
||||
'Process stopping error: %s (%s). PID: "%s"\n', errMsg, errNum, pidValue))
|
||||
end
|
||||
if errNum == 3 then
|
||||
os.remove(pidFile)
|
||||
end
|
||||
retVal = true
|
||||
else
|
||||
os.remove(pidFile)
|
||||
end
|
||||
end
|
||||
if not pidValue then
|
||||
io.stderr:write(
|
||||
string.format('PID file "%s" does not exists. Is the %s not running?\n',
|
||||
pidFile, self.appName))
|
||||
end
|
||||
return retVal
|
||||
end
|
||||
|
||||
function InternetDetector:stop()
|
||||
local nopids = false
|
||||
for i = 0, 100 do
|
||||
nopids = true
|
||||
local ok, commonDir = pcall(dirent.files, self.commonDir)
|
||||
if ok then
|
||||
for item in commonDir do
|
||||
if item:match(self.pidFilePattern) then
|
||||
if self:stopInstance(string.format("%s/%s", self.commonDir, item)) then
|
||||
nopids = false
|
||||
end
|
||||
end
|
||||
end
|
||||
if nopids then
|
||||
break
|
||||
end
|
||||
time.nanosleep({ tv_sec = 0, tv_nsec = 10000000 })
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function InternetDetector:setSIGUSR()
|
||||
local ok, commonDir = pcall(dirent.files, self.commonDir)
|
||||
if ok then
|
||||
for item in commonDir do
|
||||
if item:match(self.pidFilePattern) then
|
||||
pidValue = self:readValueFromFile(string.format("%s/%s", self.commonDir, item))
|
||||
if pidValue then
|
||||
signal.kill(tonumber(pidValue), signal.SIGUSR1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function InternetDetector:preRun()
|
||||
-- Exit if internet-detector mode != (1 or 2)
|
||||
if self.mode ~= 1 and self.mode ~= 2 then
|
||||
io.stderr:write(string.format('Start failed, mode != (1 or 2)\n', self.appName))
|
||||
os.exit(0)
|
||||
end
|
||||
local s = stat.stat(self.commonDir)
|
||||
if not s or not (stat.S_ISDIR(s.st_mode) ~= 0) then
|
||||
if not stat.mkdir(self.commonDir) then
|
||||
io.stderr:write(
|
||||
string.format('Error occurred while creating %s. Exit.\n', self.commonDir))
|
||||
os.exit(1)
|
||||
end
|
||||
end
|
||||
if self.serviceConfig.check_type == 2 and not unistd.access(self.curlExec, "x") then
|
||||
io.stderr:write(string.format(
|
||||
"Error, %s is not available. You need to install curl.\n", self.curlExec))
|
||||
os.exit(1)
|
||||
end
|
||||
local ok, commonDir = pcall(dirent.files, self.commonDir)
|
||||
if ok then
|
||||
local instancePattern = "^" .. self.appNamePattern .. "%." .. self.serviceConfig.instance .. "%.[%d]+%.pid$"
|
||||
for item in commonDir do
|
||||
if item:match(instancePattern) then
|
||||
self:stopInstance(string.format("%s/%s", self.commonDir, item))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function InternetDetector:run()
|
||||
local pidValue = unistd.getpid()
|
||||
self.pidFile = string.format(
|
||||
"%s/%s.%s.%s.pid", self.commonDir, self.appName, self.serviceConfig.instance, pidValue)
|
||||
self.statusFile = string.format(
|
||||
"%s/%s.%s.status", self.commonDir, self.appName, self.serviceConfig.instance)
|
||||
self:writeValueToFile(self.pidFile, pidValue)
|
||||
|
||||
if self.loggingLevel > 0 then
|
||||
syslog.openlog(self.appName, syslog.LOG_PID, syslog.LOG_DAEMON)
|
||||
end
|
||||
self:writeLogMessage("info", "started")
|
||||
self:loadModules()
|
||||
|
||||
-- Loaded modules
|
||||
local modules = {}
|
||||
for _, v in ipairs(self.modules) do
|
||||
modules[#modules + 1] = string.format("%s", v.name)
|
||||
end
|
||||
if #modules > 0 then
|
||||
self:writeLogMessage(
|
||||
"info", string.format("Loaded modules: %s", table.concat(modules, ", "))
|
||||
)
|
||||
end
|
||||
|
||||
if self.debug then
|
||||
local function inspectTable()
|
||||
local tables = {}, f
|
||||
f = function(t, prefix)
|
||||
tables[t] = true
|
||||
for k, v in pairs(t) do
|
||||
self:debugOutput(string.format(
|
||||
"%s%s = %s", prefix, k, tostring(v))
|
||||
)
|
||||
if type(v) == "table" and not tables[v] then
|
||||
f(v, string.format("%s%s.", prefix, k))
|
||||
end
|
||||
end
|
||||
end
|
||||
return f
|
||||
end
|
||||
|
||||
self:debugOutput("--- Config ---")
|
||||
inspectTable()(self, "self.")
|
||||
end
|
||||
|
||||
self:mainLoop()
|
||||
self:exit()
|
||||
end
|
||||
|
||||
function InternetDetector:noDaemon()
|
||||
self:preRun()
|
||||
self:run()
|
||||
end
|
||||
|
||||
function InternetDetector:daemon()
|
||||
self:preRun()
|
||||
-- UNIX double fork
|
||||
if unistd.fork() == 0 then
|
||||
unistd.setpid("s")
|
||||
if unistd.fork() == 0 then
|
||||
unistd.chdir("/")
|
||||
stat.umask(0)
|
||||
local devnull = fcntl.open("/dev/null", fcntl.O_RDWR)
|
||||
io.stdout:flush()
|
||||
io.stderr:flush()
|
||||
unistd.dup2(devnull, 0) -- io.stdin
|
||||
unistd.dup2(devnull, 1) -- io.stdout
|
||||
unistd.dup2(devnull, 2) -- io.stderr
|
||||
self:run()
|
||||
unistd.close(devnull)
|
||||
end
|
||||
os.exit(0)
|
||||
end
|
||||
os.exit(0)
|
||||
end
|
||||
|
||||
function InternetDetector:setServiceConfig(instance)
|
||||
if self:loadInstanceConfig(instance) then
|
||||
if self.mode == 2 then
|
||||
self.loggingLevel = 0
|
||||
self.noModules = true
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return InternetDetector
|
||||
@ -0,0 +1,334 @@
|
||||
|
||||
local dirent = require("posix.dirent")
|
||||
local time = require("posix.time")
|
||||
local unistd = require("posix.unistd")
|
||||
|
||||
local Module = {
|
||||
name = "mod_led_control",
|
||||
runPrio = 10,
|
||||
config = {},
|
||||
syslog = function(level, msg) return true end,
|
||||
debugOutput = function(msg) return true end,
|
||||
writeValue = function(filePath, str) return false end,
|
||||
readValue = function(filePath) return nil end,
|
||||
runInterval = 5,
|
||||
sysLedsDir = "/sys/class/leds",
|
||||
ledsPerInstance = 3,
|
||||
ledAction1Default = 1, -- 1: off, 2: on, 3: blinking, 4: netdev
|
||||
ledAction2Default = 1,
|
||||
ledBlinkDelayDefault = 500,
|
||||
ledNetlinkDeviceDefault = nil,
|
||||
ledNetdevModeLinkDefault = "1",
|
||||
ledNetdevModeRxDefault = "0",
|
||||
ledNetdevModeTxDefault = "0",
|
||||
status = nil,
|
||||
_enabled = false,
|
||||
_leds = {},
|
||||
_counter = 0,
|
||||
_exit = false,
|
||||
}
|
||||
|
||||
function Module:setLedAttrs(t)
|
||||
t.ledDir = string.format("%s/%s", self.sysLedsDir, t.ledName)
|
||||
t.ledMaxBrightnessFile = string.format("%s/max_brightness", t.ledDir)
|
||||
t.ledBrightnessFile = string.format("%s/brightness", t.ledDir)
|
||||
t.ledMaxBrightness = self.readValue(t.ledMaxBrightnessFile) or "1"
|
||||
t.ledTriggerFile = string.format("%s/trigger", t.ledDir)
|
||||
t.ledDelayOnFile = string.format("%s/delay_on", t.ledDir)
|
||||
t.ledDelayOffFile = string.format("%s/delay_off", t.ledDir)
|
||||
t.ledDeviceNameFile = string.format("%s/device_name", t.ledDir)
|
||||
t.ledLinkFile = string.format("%s/link", t.ledDir)
|
||||
t.ledRxFile = string.format("%s/rx", t.ledDir)
|
||||
t.ledTxFile = string.format("%s/tx", t.ledDir)
|
||||
t.ledPrevState = {
|
||||
brightness = self.readValue(t.ledBrightnessFile),
|
||||
trigger = self.readValue(t.ledTriggerFile),
|
||||
}
|
||||
if t.ledPrevState.trigger then
|
||||
local val = t.ledPrevState.trigger:match("%[[%w%-_]+%]")
|
||||
if val then
|
||||
t.ledPrevState.trigger = val:gsub("[%]%[]", "")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Module:checkLed(t)
|
||||
return (unistd.access(t.ledDir, "r") and
|
||||
unistd.access(t.ledBrightnessFile, "rw") and
|
||||
unistd.access(t.ledTriggerFile, "rw"))
|
||||
end
|
||||
|
||||
function Module:init(t)
|
||||
for i = 1, self.ledsPerInstance do
|
||||
self._leds[i] = {}
|
||||
end
|
||||
if t.led1_name then
|
||||
self._enabled = true
|
||||
else
|
||||
return
|
||||
end
|
||||
for i, l in ipairs(self._leds) do
|
||||
local led = "led" .. i
|
||||
if t[led .. "_name"] ~= nil then
|
||||
l.ledName = t[led .. "_name"]
|
||||
l.ledAction1 = tonumber(t[led .. "_action_1"]) or self.ledAction1Default
|
||||
l.ledAction2 = tonumber(t[led .. "_action_2"]) or self.ledAction2Default
|
||||
l.ledBlinkOnDelay1 = tonumber(t[led .. "_blink_on_delay_1"]) or self.ledBlinkDelayDefault
|
||||
l.ledBlinkOffDelay1 = tonumber(t[led .. "_blink_off_delay_1"]) or self.ledBlinkDelayDefault
|
||||
l.ledBlinkOnDelay2 = tonumber(t[led .. "_blink_on_delay_2"]) or self.ledBlinkDelayDefault
|
||||
l.ledBlinkOffDelay2 = tonumber(t[led .. "_blink_off_delay_2"]) or self.ledBlinkDelayDefault
|
||||
l.ledNetlinkDevice1 = t[led .. "_netdev_device_1"] or self.ledNetlinkDeviceDefault
|
||||
l.ledNetlinkDevice2 = t[led .. "_netdev_device_2"] or self.ledNetlinkDeviceDefault
|
||||
l.ledNetdevModeLink1 = self.ledNetdevModeLinkDefault
|
||||
l.ledNetdevModeTx1 = self.ledNetdevModeTxDefault
|
||||
l.ledNetdevModeRx1 = self.ledNetdevModeRxDefault
|
||||
l.ledNetdevModeLink2 = self.ledNetdevModeLinkDefault
|
||||
l.ledNetdevModeTx2 = self.ledNetdevModeTxDefault
|
||||
l.ledNetdevModeRx2 = self.ledNetdevModeRxDefault
|
||||
local ndm1 = t[led .. "_netdev_mode_1"]
|
||||
if ndm1 ~= nil and type(ndm1) == "table" then
|
||||
local enabledFlags = {}
|
||||
for _, v in ipairs(ndm1) do
|
||||
enabledFlags[v] = "1"
|
||||
end
|
||||
l.ledNetdevModeLink1 = enabledFlags.link or "0"
|
||||
l.ledNetdevModeTx1 = enabledFlags.tx or "0"
|
||||
l.ledNetdevModeRx1 = enabledFlags.rx or "0"
|
||||
end
|
||||
local ndm2 = t[led .. "_netdev_mode_2"]
|
||||
if ndm2 ~= nil and type(ndm2) == "table" then
|
||||
local enabledFlags = {}
|
||||
for _, v in ipairs(ndm2) do
|
||||
enabledFlags[v] = "1"
|
||||
end
|
||||
l.ledNetdevModeLink2 = enabledFlags.link or "0"
|
||||
l.ledNetdevModeTx2 = enabledFlags.tx or "0"
|
||||
l.ledNetdevModeRx2 = enabledFlags.rx or "0"
|
||||
end
|
||||
self:setLedAttrs(l)
|
||||
l.enabled = true
|
||||
else
|
||||
l.enabled = false
|
||||
end
|
||||
if l.enabled and not self:checkLed(l) then
|
||||
self._enabled = false
|
||||
self.syslog("err", string.format(
|
||||
"%s: module disabled. LED '%s' is not available", self.name, l.ledName))
|
||||
end
|
||||
self._exit = false
|
||||
end
|
||||
end
|
||||
|
||||
function Module:checkLedTimer(t)
|
||||
return (unistd.access(t.ledDelayOnFile, "rw") and unistd.access(t.ledDelayOffFile, "rw"))
|
||||
end
|
||||
|
||||
function Module:checkLedNetdev(t)
|
||||
return (unistd.access(t.ledDeviceNameFile, "rw") and
|
||||
unistd.access(t.ledLinkFile, "rw") and
|
||||
unistd.access(t.ledRxFile, "rw") and
|
||||
unistd.access(t.ledTxFile, "rw"))
|
||||
end
|
||||
|
||||
function Module:setTriggerNone(t)
|
||||
self.writeValue(t.ledTriggerFile, "none")
|
||||
self.debugOutput(string.format(
|
||||
"%s: LED TRIGGER SET: none, %s", self.name, t.ledTriggerFile))
|
||||
end
|
||||
|
||||
function Module:setTriggerTimer(t, delayOn, delayOff)
|
||||
if not delayOn then
|
||||
delayOn = self.ledBlinkDelayDefault
|
||||
end
|
||||
if not delayOff then
|
||||
delayOff = self.ledBlinkDelayDefault
|
||||
end
|
||||
|
||||
self.writeValue(t.ledTriggerFile, "timer")
|
||||
|
||||
for i = 0, 10 do
|
||||
if self:checkLedTimer(t) then
|
||||
self.writeValue(t.ledDelayOnFile, delayOn)
|
||||
self.writeValue(t.ledDelayOffFile, delayOff)
|
||||
break
|
||||
else
|
||||
time.nanosleep({ tv_sec = 0, tv_nsec = 500000 })
|
||||
end
|
||||
end
|
||||
|
||||
self.debugOutput(string.format(
|
||||
"%s: LED TRIGGER SET: timer, %s; delayOn = %s, delayOff = %s",
|
||||
self.name, t.ledTriggerFile, tostring(delayOn), tostring(delayOff))
|
||||
)
|
||||
end
|
||||
|
||||
function Module:setTriggerNetdev(t, device, link, tx, rx)
|
||||
if not device then
|
||||
return
|
||||
end
|
||||
|
||||
self.writeValue(t.ledTriggerFile, "netdev")
|
||||
|
||||
for i = 0, 10 do
|
||||
if self:checkLedNetdev(t) then
|
||||
self.writeValue(t.ledDeviceNameFile, device)
|
||||
self.writeValue(t.ledLinkFile, link)
|
||||
self.writeValue(t.ledTxFile, tx)
|
||||
self.writeValue(t.ledRxFile, rx)
|
||||
break
|
||||
else
|
||||
time.nanosleep({ tv_sec = 0, tv_nsec = 500000 })
|
||||
end
|
||||
end
|
||||
|
||||
self.debugOutput(string.format(
|
||||
"%s: LED TRIGGER SET: netdev, %s; device = %s, link = %s, rx = %s, tx = %s",
|
||||
self.name, t.ledTriggerFile, tostring(device), tostring(link), tostring(rx), tostring(tx))
|
||||
)
|
||||
end
|
||||
|
||||
function Module:getCurrentTrigger(t)
|
||||
local trigger = self.readValue(t.ledTriggerFile)
|
||||
if trigger then
|
||||
if trigger:match("%[timer%]") then
|
||||
return "timer"
|
||||
elseif trigger:match("%[netdev%]") then
|
||||
return "netdev"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Module:getTriggerValues(t, trigger)
|
||||
local currentTrigger = self:getCurrentTrigger(t)
|
||||
if trigger == currentTrigger then
|
||||
if trigger == "timer" then
|
||||
return {
|
||||
trigger = currentTrigger,
|
||||
delayOn = tonumber(self.readValue(t.ledDelayOnFile)),
|
||||
delayOff = tonumber(self.readValue(t.ledDelayOffFile)),
|
||||
}
|
||||
elseif trigger == "netdev" then
|
||||
return {
|
||||
trigger = currentTrigger,
|
||||
device = self.readValue(t.ledDeviceNameFile),
|
||||
link = self.readValue(t.ledLinkFile),
|
||||
tx = self.readValue(t.ledTxFile),
|
||||
rx = self.readValue(t.ledRxFile),
|
||||
}
|
||||
end
|
||||
end
|
||||
return {}
|
||||
end
|
||||
|
||||
function Module:on(t)
|
||||
self:setTriggerNone(t)
|
||||
self.writeValue(t.ledBrightnessFile, t.ledMaxBrightness)
|
||||
|
||||
self.debugOutput(string.format("%s: LED ON: %s", self.name, t.ledBrightnessFile))
|
||||
end
|
||||
|
||||
function Module:off(t)
|
||||
self:setTriggerNone(t)
|
||||
self.writeValue(t.ledBrightnessFile, "0")
|
||||
|
||||
self.debugOutput(string.format("%s: LED OFF: %s", self.name, t.ledBrightnessFile))
|
||||
end
|
||||
|
||||
function Module:getCurrentState(t)
|
||||
local state = self.readValue(t.ledBrightnessFile)
|
||||
if state and tonumber(state) > 0 then
|
||||
return tonumber(state)
|
||||
end
|
||||
end
|
||||
|
||||
function Module:ledRunFunc(t, currentStatus)
|
||||
if currentStatus == 0 then
|
||||
if t.ledAction1 == 1 then
|
||||
if self:getCurrentState(t) or self:getCurrentTrigger(t) then
|
||||
self:off(t)
|
||||
end
|
||||
elseif t.ledAction1 == 2 then
|
||||
if not self:getCurrentState(t) or self:getCurrentTrigger(t) then
|
||||
self:on(t)
|
||||
end
|
||||
elseif t.ledAction1 == 3 then
|
||||
local triggerValues = self:getTriggerValues(t, "timer")
|
||||
if (not next(triggerValues)) or (triggerValues.delayOn ~= t.ledBlinkOnDelay1 or
|
||||
triggerValues.delayOff ~= t.ledBlinkOffDelay1) then
|
||||
self:setTriggerTimer(t, t.ledBlinkOnDelay1, t.ledBlinkOffDelay1)
|
||||
end
|
||||
elseif t.ledAction1 == 4 then
|
||||
local triggerValues = self:getTriggerValues(t, "netdev")
|
||||
if (not next(triggerValues)) or (triggerValues.device ~= t.ledNetlinkDevice1 or
|
||||
triggerValues.link ~= t.ledNetdevModeLink1 or
|
||||
triggerValues.tx ~= t.ledNetdevModeTx1 or
|
||||
triggerValues.rx ~= t.ledNetdevModeRx1) then
|
||||
self:setTriggerNetdev(t,
|
||||
t.ledNetlinkDevice1, t.ledNetdevModeLink1,
|
||||
t.ledNetdevModeTx1, t.ledNetdevModeRx1
|
||||
)
|
||||
end
|
||||
end
|
||||
elseif currentStatus == 1 then
|
||||
if t.ledAction2 == 1 then
|
||||
if self:getCurrentState(t) or self:getCurrentTrigger(t) then
|
||||
self:off(t)
|
||||
end
|
||||
elseif t.ledAction2 == 2 then
|
||||
if not self:getCurrentState(t) or self:getCurrentTrigger(t) then
|
||||
self:on(t)
|
||||
end
|
||||
elseif t.ledAction2 == 3 then
|
||||
local triggerValues = self:getTriggerValues(t, "timer")
|
||||
if (not next(triggerValues)) or (triggerValues.delayOn ~= t.ledBlinkOnDelay2 or
|
||||
triggerValues.delayOff ~= t.ledBlinkOffDelay2) then
|
||||
self:setTriggerTimer(t, t.ledBlinkOnDelay2, t.ledBlinkOffDelay2)
|
||||
end
|
||||
elseif t.ledAction2 == 4 then
|
||||
local triggerValues = self:getTriggerValues(t, "netdev")
|
||||
if (not next(triggerValues)) or (triggerValues.device ~= t.ledNetlinkDevice2 or
|
||||
triggerValues.link ~= t.ledNetdevModeLink2 or
|
||||
triggerValues.tx ~= t.ledNetdevModeTx2 or
|
||||
triggerValues.rx ~= t.ledNetdevModeRx2) then
|
||||
self:setTriggerNetdev(t,
|
||||
t.ledNetlinkDevice2, t.ledNetdevModeLink2,
|
||||
t.ledNetdevModeTx2, t.ledNetdevModeRx2
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Module:run(currentStatus, lastStatus, timeDiff, timeNow, inetChecked)
|
||||
if not self._enabled then
|
||||
return
|
||||
end
|
||||
if self._counter == 0 or self._counter >= self.runInterval or currentStatus ~= lastStatus then
|
||||
for _, t in ipairs(self._leds) do
|
||||
if self._exit then
|
||||
break
|
||||
end
|
||||
if t.enabled then
|
||||
self:ledRunFunc(t, currentStatus)
|
||||
end
|
||||
end
|
||||
self._counter = 0
|
||||
end
|
||||
self._counter = self._counter + timeDiff
|
||||
end
|
||||
|
||||
function Module:onExit()
|
||||
self._exit = true
|
||||
for _, l in ipairs(self._leds) do
|
||||
if l.ledPrevState then
|
||||
if l.ledPrevState.brightness then
|
||||
self.writeValue(l.ledBrightnessFile, l.ledPrevState.brightness)
|
||||
end
|
||||
if l.ledPrevState.trigger then
|
||||
self.writeValue(l.ledTriggerFile, l.ledPrevState.trigger)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Module
|
||||
@ -0,0 +1,160 @@
|
||||
|
||||
local unistd = require("posix.unistd")
|
||||
|
||||
local Module = {
|
||||
name = "mod_network_restart",
|
||||
runPrio = 30,
|
||||
config = {},
|
||||
syslog = function(level, msg) return true end,
|
||||
debugOutput = function(msg) return true end,
|
||||
writeValue = function(filePath, str) return false end,
|
||||
readValue = function(filePath) return nil end,
|
||||
deadPeriod = 900,
|
||||
attempts = 1,
|
||||
attemptInterval = 15,
|
||||
deviceTimeout = 0,
|
||||
status = nil,
|
||||
_attemptsCounter = 0,
|
||||
_attemptIntervalCounter = 0,
|
||||
_deadCounter = 0,
|
||||
_firstAttempt = true,
|
||||
_ifaceRestarting = false,
|
||||
_ifaceRestartCounter = 0,
|
||||
_netIfaces = {},
|
||||
_netDevices = {},
|
||||
_netItemsNum = 0,
|
||||
_disconnectedAtStartup = false,
|
||||
}
|
||||
|
||||
function Module:toggleDevices(flag)
|
||||
if #self._netDevices == 0 then
|
||||
return
|
||||
end
|
||||
local ip = "/sbin/ip"
|
||||
if unistd.access(ip, "x") then
|
||||
for _, v in ipairs(self._netDevices) do
|
||||
os.execute(string.format("%s link set dev %s %s", ip, v, (flag and "up" or "down")))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Module:toggleIfaces(flag)
|
||||
if #self._netIfaces == 0 then
|
||||
return
|
||||
end
|
||||
for _, v in ipairs(self._netIfaces) do
|
||||
os.execute(string.format("%s %s", (flag and "/sbin/ifup" or "/sbin/ifdown"), v))
|
||||
end
|
||||
end
|
||||
|
||||
function Module:netItemsUp()
|
||||
self:toggleDevices(true)
|
||||
self:toggleIfaces(true)
|
||||
end
|
||||
|
||||
function Module:netItemsDown()
|
||||
self:toggleIfaces(false)
|
||||
self:toggleDevices(false)
|
||||
end
|
||||
|
||||
function Module:restartNetworkService()
|
||||
return os.execute("/etc/init.d/network restart")
|
||||
end
|
||||
|
||||
function Module:init(t)
|
||||
if t.ifaces ~= nil and type(t.ifaces) == "table" then
|
||||
self._netIfaces = {}
|
||||
self._netDevices = {}
|
||||
self._netItemsNum = 0
|
||||
for k, v in ipairs(t.ifaces) do
|
||||
if v:match("^@") then
|
||||
self._netIfaces[#self._netIfaces + 1] = v:gsub("^@", "")
|
||||
else
|
||||
self._netDevices[#self._netDevices + 1] = v
|
||||
end
|
||||
self._netItemsNum = self._netItemsNum + 1
|
||||
end
|
||||
end
|
||||
if t.dead_period ~= nil then
|
||||
self.deadPeriod = tonumber(t.dead_period)
|
||||
end
|
||||
if t.attempts ~= nil then
|
||||
self.attempts = tonumber(t.attempts)
|
||||
end
|
||||
if t.attempt_interval ~= nil then
|
||||
self.attemptInterval = tonumber(t.attempt_interval)
|
||||
end
|
||||
if t.device_timeout ~= nil then
|
||||
self.deviceTimeout = tonumber(t.device_timeout)
|
||||
end
|
||||
if tonumber(t.disconnected_at_startup) == 1 then
|
||||
self._disconnectedAtStartup = true
|
||||
end
|
||||
end
|
||||
|
||||
function Module:networkRestartFunc()
|
||||
if self._netItemsNum > 0 then
|
||||
if #self._netIfaces > 0 then
|
||||
self.syslog("info", string.format("%s: restarting interfaces: %s",
|
||||
self.name, table.concat(self._netIfaces, ", ")))
|
||||
end
|
||||
if #self._netDevices > 0 then
|
||||
self.syslog("info", string.format("%s: restarting devices: %s",
|
||||
self.name, table.concat(self._netDevices, ", ")))
|
||||
end
|
||||
self:netItemsDown()
|
||||
if self.deviceTimeout < 1 then
|
||||
self:netItemsUp()
|
||||
else
|
||||
self._ifaceRestarting = true
|
||||
end
|
||||
else
|
||||
self.syslog("info", string.format(
|
||||
"%s: restarting network", self.name))
|
||||
self:restartNetworkService()
|
||||
end
|
||||
if self.attempts > 0 then
|
||||
self._attemptsCounter = self._attemptsCounter + 1
|
||||
end
|
||||
end
|
||||
|
||||
function Module:run(currentStatus, lastStatus, timeDiff, timeNow, inetChecked)
|
||||
if self._ifaceRestarting then
|
||||
if self._ifaceRestartCounter >= self.deviceTimeout then
|
||||
self:netItemsUp()
|
||||
self._ifaceRestarting = false
|
||||
self._ifaceRestartCounter = 0
|
||||
else
|
||||
self._ifaceRestartCounter = self._ifaceRestartCounter + timeDiff
|
||||
end
|
||||
else
|
||||
if currentStatus == 1 then
|
||||
if self._disconnectedAtStartup and self._deadCounter >= self.deadPeriod then
|
||||
if self.attempts == 0 or self._attemptsCounter < self.attempts then
|
||||
if self._firstAttempt or self._attemptIntervalCounter >= self.attemptInterval then
|
||||
self:networkRestartFunc()
|
||||
self._attemptIntervalCounter = 0
|
||||
self._firstAttempt = false
|
||||
else
|
||||
self._attemptIntervalCounter = self._attemptIntervalCounter + timeDiff
|
||||
end
|
||||
end
|
||||
else
|
||||
self._deadCounter = self._deadCounter + timeDiff
|
||||
end
|
||||
else
|
||||
self._attemptsCounter = 0
|
||||
self._attemptIntervalCounter = 0
|
||||
self._deadCounter = 0
|
||||
self._disconnectedAtStartup = true
|
||||
self._firstAttempt = true
|
||||
end
|
||||
self._ifaceRestartCounter = 0
|
||||
end
|
||||
end
|
||||
|
||||
function Module:onExit()
|
||||
return true
|
||||
end
|
||||
|
||||
return Module
|
||||
@ -0,0 +1,533 @@
|
||||
|
||||
local socket = require("posix.sys.socket")
|
||||
local stdlib = require("posix.stdlib")
|
||||
local unistd = require("posix.unistd")
|
||||
|
||||
local Module = {
|
||||
name = "mod_public_ip",
|
||||
runPrio = 50,
|
||||
config = {
|
||||
noModules = false,
|
||||
debug = false,
|
||||
serviceConfig = {
|
||||
iface = nil,
|
||||
proxy_type = nil,
|
||||
proxy_host = nil,
|
||||
proxy_port = nil,
|
||||
},
|
||||
},
|
||||
syslog = function(level, msg) return true end,
|
||||
debugOutput = function(msg) return true end,
|
||||
writeValue = function(filePath, str) return false end,
|
||||
readValue = function(filePath) return nil end,
|
||||
port = 53,
|
||||
runInterval = 600,
|
||||
runIntervalFailed = 60,
|
||||
runIntervalIPFailed = 1,
|
||||
requestAttempts = 2,
|
||||
timeout = 3,
|
||||
curlExec = "/usr/bin/curl",
|
||||
curlParams = '-s -g --no-keepalive --user-agent "Mozilla/5.0 (X11; Linux x86_64; rv:142.0) Gecko/20100101 Firefox/142.0"',
|
||||
providers = {
|
||||
opendns1 = {
|
||||
name = "opendns1", type = "dns", host = "myip.opendns.com",
|
||||
server = "208.67.222.222", server6 = "2620:119:35::35",
|
||||
port = 53, queryType = "A", queryType6 = "AAAA",
|
||||
},
|
||||
opendns2 = {
|
||||
name = "opendns2", type = "dns", host = "myip.opendns.com",
|
||||
server = "208.67.220.220", server6 = "2620:119:35::35",
|
||||
port = 53, queryType = "A", queryType6 = "AAAA",
|
||||
},
|
||||
opendns3 = {
|
||||
name = "opendns3", type = "dns", host = "myip.opendns.com",
|
||||
server = "208.67.222.220", server6 = "2620:119:35::35",
|
||||
port = 53, queryType = "A", queryType6 = "AAAA",
|
||||
},
|
||||
opendns4 = {
|
||||
name = "opendns4", type = "dns", host = "myip.opendns.com",
|
||||
server = "208.67.220.222", server6 = "2620:119:35::35",
|
||||
port = 53, queryType = "A", queryType6 = "AAAA",
|
||||
},
|
||||
google = {
|
||||
name = "google", type = "dns", host = "o-o.myaddr.l.google.com",
|
||||
server = "ns1.google.com", server6 = "ns1.google.com",
|
||||
port = 53, queryType = "TXT", queryType6 = "TXT",
|
||||
},
|
||||
akamai = {
|
||||
name = "akamai", type = "dns", host = "whoami.akamai.net",
|
||||
server = "ns1-1.akamaitech.net", server6 = "ns1-1.akamaitech.net",
|
||||
port = 53, queryType = "A", queryType6 = "AAAA",
|
||||
},
|
||||
akamai_http = {
|
||||
name = "akamai_http", type = "http", url = "http://whatismyip.akamai.com/",
|
||||
parseResponseFunc = nil,
|
||||
},
|
||||
amazonaws= {
|
||||
name = "amazonaws", type = "http", url = "http://checkip.amazonaws.com/",
|
||||
parseResponseFunc = nil,
|
||||
},
|
||||
wgetip= {
|
||||
name = "wgetip", type = "http", url = "http://wgetip.com/",
|
||||
parseResponseFunc = nil,
|
||||
},
|
||||
ifconfig= {
|
||||
name = "ifconfig", type = "http", url = "http://ifconfig.me/",
|
||||
parseResponseFunc = nil,
|
||||
},
|
||||
ipecho= {
|
||||
name = "ipecho", type = "http", url = "http://ipecho.net/plain",
|
||||
parseResponseFunc = nil,
|
||||
},
|
||||
canhazip= {
|
||||
name = "canhazip", type = "http", url = "http://canhazip.com/",
|
||||
parseResponseFunc = nil,
|
||||
},
|
||||
icanhazip = {
|
||||
name = "icanhazip", type = "http", url = "http://icanhazip.com/",
|
||||
parseResponseFunc = nil,
|
||||
},
|
||||
},
|
||||
ipScript = "",
|
||||
enableIpScript = false,
|
||||
status = nil,
|
||||
_proxyString = "",
|
||||
_provider = nil,
|
||||
_qtype = false,
|
||||
_currentIp = nil,
|
||||
_lastResolvedIp = nil,
|
||||
_enabled = false,
|
||||
_counter = 0,
|
||||
_IPFalseCounter = 0,
|
||||
_interval = 600,
|
||||
_DNSPacket = nil,
|
||||
_requestIP = nil,
|
||||
}
|
||||
|
||||
function Module:runIpScript()
|
||||
if not self.config.noModules and self.enableIpScript and unistd.access(self.ipScript, "r") then
|
||||
stdlib.setenv("PUBLIC_IP", self.status)
|
||||
os.execute(string.format('/bin/sh "%s" &', self.ipScript))
|
||||
end
|
||||
end
|
||||
|
||||
function Module:getQueryType(type)
|
||||
local types = {
|
||||
A = 1,
|
||||
NS = 2,
|
||||
MD = 3,
|
||||
MF = 4,
|
||||
CNAME = 5,
|
||||
SOA = 6,
|
||||
MB = 7,
|
||||
MG = 8,
|
||||
MR = 9,
|
||||
NULL = 10,
|
||||
WKS = 11,
|
||||
PTS = 12,
|
||||
HINFO = 13,
|
||||
MINFO = 14,
|
||||
MX = 15,
|
||||
TXT = 16,
|
||||
AAAA = 28,
|
||||
}
|
||||
return types[type]
|
||||
end
|
||||
|
||||
function Module:buildMessage(address, queryType)
|
||||
if not queryType then
|
||||
queryType = "A"
|
||||
end
|
||||
queryType = self:getQueryType(queryType)
|
||||
|
||||
local addressString = ""
|
||||
for part in address:gmatch("[^.]+") do
|
||||
local t = {}
|
||||
for i in part:gmatch(".") do
|
||||
t[#t + 1] = i
|
||||
end
|
||||
addrLen = #part
|
||||
addrPart = table.concat(t)
|
||||
addressString = addressString .. string.char(addrLen) .. addrPart
|
||||
end
|
||||
|
||||
local data = (
|
||||
string.char(
|
||||
0xaa, 0xaa,
|
||||
0x01, 0x00,
|
||||
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
) ..
|
||||
addressString ..
|
||||
string.char(
|
||||
0x00,
|
||||
0x00, queryType,
|
||||
0x00, 0x01
|
||||
)
|
||||
)
|
||||
return data
|
||||
end
|
||||
|
||||
function Module:sendUDPMessage(message, server, port)
|
||||
local success
|
||||
local retCode = 1
|
||||
local data
|
||||
|
||||
self.debugOutput(string.format("--- %s ---", self.name))
|
||||
|
||||
local saTable, errMsg, errNum = socket.getaddrinfo(server, port)
|
||||
|
||||
if not saTable then
|
||||
self.debugOutput(string.format(
|
||||
"GETADDRINFO ERROR: %s, %s", errMsg, errNum))
|
||||
else
|
||||
local family = saTable[1].family
|
||||
|
||||
if family then
|
||||
local sock, errMsg, errNum = socket.socket(family, socket.SOCK_DGRAM, 0)
|
||||
|
||||
if not sock then
|
||||
self.debugOutput(string.format(
|
||||
"SOCKET ERROR: %s, %s", errMsg, errNum))
|
||||
return retCode
|
||||
end
|
||||
|
||||
socket.setsockopt(sock, socket.SOL_SOCKET,
|
||||
socket.SO_SNDTIMEO, self.timeout, 0)
|
||||
socket.setsockopt(sock, socket.SOL_SOCKET,
|
||||
socket.SO_RCVTIMEO, self.timeout, 0)
|
||||
|
||||
if self.config.serviceConfig.iface then
|
||||
local ok, errMsg, errNum = socket.setsockopt(sock, socket.SOL_SOCKET,
|
||||
socket.SO_BINDTODEVICE, self.config.serviceConfig.iface)
|
||||
if not ok then
|
||||
self.debugOutput(string.format(
|
||||
"SOCKET ERROR: %s, %s", errMsg, errNum))
|
||||
unistd.close(sock)
|
||||
return retCode
|
||||
end
|
||||
end
|
||||
|
||||
local ok, errMsg, errNum = socket.sendto(sock, message, saTable[1])
|
||||
|
||||
local response = {}
|
||||
if ok then
|
||||
local ret, resp, errNum = socket.recvfrom(sock, 1024)
|
||||
data = ret
|
||||
if data then
|
||||
success = true
|
||||
response = resp
|
||||
else
|
||||
self.debugOutput(string.format(
|
||||
"SOCKET RECV ERROR: %s, %s", tostring(resp), tostring(errNum)))
|
||||
end
|
||||
else
|
||||
self.debugOutput(string.format(
|
||||
"SOCKET SEND ERROR: %s, %s", tostring(errMsg), tostring(errNum)))
|
||||
end
|
||||
|
||||
self.debugOutput(string.format(
|
||||
"--- UDP ---\ntime = %s\nconnection_timeout = %s\niface = %s\nserver = %s:%s\nsockname = %s:%s\nsuccess = %s",
|
||||
os.time(),
|
||||
self.timeout,
|
||||
tostring(self.config.serviceConfig.iface),
|
||||
server,
|
||||
tostring(port),
|
||||
tostring(response.addr),
|
||||
tostring(response.port),
|
||||
tostring(success))
|
||||
)
|
||||
|
||||
unistd.close(sock)
|
||||
retCode = success and 0 or 1
|
||||
end
|
||||
end
|
||||
return retCode, tostring(data)
|
||||
end
|
||||
|
||||
function Module:parseParts(message, start, parts)
|
||||
local partStart = start + 2
|
||||
local partLen = message:sub(start, start + 1)
|
||||
if #partLen == 0 then
|
||||
return parts
|
||||
end
|
||||
local partEnd = partStart + (tonumber(partLen, 16) * 2)
|
||||
parts[#parts + 1] = message:sub(partStart, partEnd - 1)
|
||||
if message:sub(partEnd, partEnd + 1) == "00" or partEnd > #message then
|
||||
return parts
|
||||
else
|
||||
return self:parseParts(message, partEnd, parts)
|
||||
end
|
||||
end
|
||||
|
||||
function Module:decodeMessage(message)
|
||||
local retTable = {}
|
||||
local t = {}
|
||||
for i = 1, #message do
|
||||
t[#t + 1] = string.format("%.2x", string.byte(message, i))
|
||||
end
|
||||
message = table.concat(t)
|
||||
|
||||
local ANCOUNT = message:sub(13, 16)
|
||||
local NSCOUNT = message:sub(17, 20)
|
||||
local ARCOUNT = message:sub(21, 24)
|
||||
|
||||
-- Question section
|
||||
local questionSectionStarts = 25
|
||||
local questionParts = self:parseParts(message, questionSectionStarts, {})
|
||||
local qtypeStarts = questionSectionStarts + (#table.concat(questionParts)) + (#questionParts * 2) + 1
|
||||
local qclassStarts = qtypeStarts + 4
|
||||
|
||||
-- Answer section
|
||||
local answerSectionStarts = qclassStarts + 4
|
||||
local numAnswers = math.max(
|
||||
tonumber(ANCOUNT, 16), tonumber(NSCOUNT, 16), tonumber(ARCOUNT, 16))
|
||||
|
||||
if numAnswers > 0 then
|
||||
for answerCount = 1, numAnswers do
|
||||
if answerSectionStarts < #message then
|
||||
local ATYPE = tonumber(
|
||||
message:sub(answerSectionStarts + 5, answerSectionStarts + 8), 16)
|
||||
local RDLENGTH = tonumber(
|
||||
message:sub(answerSectionStarts + 21, answerSectionStarts + 24), 16)
|
||||
local RDDATA = message:sub(
|
||||
answerSectionStarts + 25, answerSectionStarts + 24 + (RDLENGTH * 2))
|
||||
local RDDATA_decoded = ""
|
||||
|
||||
if #RDDATA > 0 then
|
||||
if ATYPE == self:getQueryType("A") or ATYPE == self:getQueryType("AAAA") then
|
||||
local octets = {}
|
||||
local sep = "."
|
||||
if #RDDATA > 8 then
|
||||
sep = ":"
|
||||
for i = 1, #RDDATA, 4 do
|
||||
local string = RDDATA:sub(i, i + 3)
|
||||
string = string:gsub("^00?0?", "")
|
||||
octets[#octets + 1] = string
|
||||
end
|
||||
else
|
||||
for i = 1, #RDDATA, 2 do
|
||||
octets[#octets + 1] = tonumber(RDDATA:sub(i, i + 1), 16)
|
||||
end
|
||||
end
|
||||
RDDATA_decoded = table.concat(octets, sep):gsub("0:[0:]+", "::", 1):gsub("::+", "::")
|
||||
else
|
||||
local rdata_t = {}
|
||||
for _, v in ipairs(self:parseParts(RDDATA, 1, {})) do
|
||||
local t = {}
|
||||
for i = 1, #v, 2 do
|
||||
t[#t + 1] = string.char(tonumber(v:sub(i, i + 1), 16))
|
||||
end
|
||||
rdata_t[#rdata_t + 1] = table.concat(t)
|
||||
end
|
||||
RDDATA_decoded = table.concat(rdata_t)
|
||||
end
|
||||
end
|
||||
answerSectionStarts = answerSectionStarts + 24 + (RDLENGTH * 2)
|
||||
|
||||
if RDDATA_decoded:match("^[a-fA-F0-9.:]+$") then
|
||||
retTable[#retTable + 1] = RDDATA_decoded
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return retTable
|
||||
end
|
||||
|
||||
function Module:requestIPDNS()
|
||||
local res
|
||||
local qtype = self._qtype and self._provider.queryType6 or self._provider.queryType
|
||||
local server = self._qtype and self._provider.server6 or self._provider.server
|
||||
local port = self._provider.port or self.port
|
||||
if not self._DNSPacket then
|
||||
self._DNSPacket = self:buildMessage(self._provider.host, qtype)
|
||||
end
|
||||
local retCode, response = self:sendUDPMessage(self._DNSPacket, server, port)
|
||||
if retCode == 0 and response then
|
||||
local retTable = self:decodeMessage(response)
|
||||
if #retTable > 0 then
|
||||
res = table.concat(retTable, ", ")
|
||||
end
|
||||
else
|
||||
self.syslog("warning", string.format(
|
||||
"%s: UDP error when requesting an IP address", self.name))
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
function Module:httpRequest(url)
|
||||
local retCode = 1, data
|
||||
local curl = string.format(
|
||||
'%s%s%s --connect-timeout %s %s "%s"; printf "\n$?";',
|
||||
self.curlExec,
|
||||
self.config.serviceConfig.iface and (" --interface " .. self.config.serviceConfig.iface) or "",
|
||||
self._proxyString,
|
||||
self.timeout,
|
||||
self.curlParams,
|
||||
url
|
||||
)
|
||||
local fh = io.popen(curl, "r")
|
||||
if fh then
|
||||
data = fh:read("*a")
|
||||
fh:close()
|
||||
if data ~= nil then
|
||||
local s, e = data:find("[0-9]+\n?$")
|
||||
retCode = tonumber(data:sub(s))
|
||||
data = data:sub(0, s - 2)
|
||||
if not data or data == "" then
|
||||
data = nil
|
||||
end
|
||||
end
|
||||
else
|
||||
retCode = 1
|
||||
end
|
||||
|
||||
self.debugOutput(string.format(
|
||||
"--- Curl ---\ntime = %s\n%s\nretCode = %s\ndata = [\n%s]\n",
|
||||
os.time(),
|
||||
curl,
|
||||
retCode,
|
||||
tostring(data))
|
||||
)
|
||||
return retCode, data
|
||||
end
|
||||
|
||||
function Module:parseHTTPResponse(data)
|
||||
data = data:gsub("^[%s%c]+", ""):gsub("[%s%c]+$", "")
|
||||
if data:match("^[a-fA-F0-9.:]+$") then
|
||||
return data
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
function Module:requestIPHTTP()
|
||||
local res
|
||||
local url = self._provider.url
|
||||
local parseResponseFunc = self._provider.parseResponseFunc
|
||||
if url then
|
||||
local retCode, data = self:httpRequest(url)
|
||||
if retCode == 0 and data then
|
||||
if type(parseResponseFunc) == "function" then
|
||||
res = parseResponseFunc(data)
|
||||
else
|
||||
res = self:parseHTTPResponse(data)
|
||||
end
|
||||
else
|
||||
self.syslog("warning", string.format(
|
||||
"%s: HTTP error when requesting an IP address", self.name))
|
||||
end
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
function Module:init(t)
|
||||
if t.interval ~= nil then
|
||||
self.runInterval = tonumber(t.interval)
|
||||
end
|
||||
if t.interval_failed ~= nil then
|
||||
self.runIntervalFailed = tonumber(t.interval_failed)
|
||||
end
|
||||
if t.request_attempts ~= nil then
|
||||
self.requestAttempts = tonumber(t.request_attempts)
|
||||
end
|
||||
if t.timeout ~= nil then
|
||||
self.timeout = tonumber(t.timeout)
|
||||
end
|
||||
if t.provider ~= nil then
|
||||
self._provider = self.providers[t.provider]
|
||||
else
|
||||
self._provider = self.providers.opendns1
|
||||
end
|
||||
if self.config.configDir then
|
||||
self.ipScript = string.format(
|
||||
"%s/public-ip-script.%s", self.config.configDir, self.config.serviceConfig.instance)
|
||||
if t.enable_ip_script ~= nil then
|
||||
self.enableIpScript = (tonumber(t.enable_ip_script) ~= 0)
|
||||
end
|
||||
end
|
||||
if t.qtype ~= nil then
|
||||
self._qtype = (tonumber(t.qtype) ~= 0)
|
||||
end
|
||||
self._currentIp = nil
|
||||
self._lastResolvedIp = nil
|
||||
self._DNSPacket = nil
|
||||
self._interval = self.runInterval
|
||||
self._IPFalseCounter = 0
|
||||
self._enabled = true
|
||||
if not self._provider then
|
||||
self._enabled = false
|
||||
else
|
||||
if self._provider.url and not unistd.access(self.curlExec, "x") then
|
||||
self._enabled = false
|
||||
self.syslog("err", string.format(
|
||||
"%s: %s is not available. You need to install curl.", self.name, self.curlExec))
|
||||
end
|
||||
if self._provider.type == "dns" then
|
||||
self._requestIP = self.requestIPDNS
|
||||
elseif self._provider.type == "http" then
|
||||
self._requestIP = self.requestIPHTTP
|
||||
else
|
||||
self._enabled = false
|
||||
end
|
||||
end
|
||||
if (self.config.serviceConfig.proxy_type and
|
||||
self.config.serviceConfig.proxy_host and
|
||||
self.config.serviceConfig.proxy_port) then
|
||||
self._proxyString = string.format(
|
||||
" --proxy %s://%s:%d",
|
||||
self.config.serviceConfig.proxy_type,
|
||||
self.config.serviceConfig.proxy_host,
|
||||
self.config.serviceConfig.proxy_port)
|
||||
end
|
||||
end
|
||||
|
||||
function Module:run(currentStatus, lastStatus, timeDiff, timeNow, inetChecked)
|
||||
if not self._enabled then
|
||||
return
|
||||
end
|
||||
if currentStatus == 0 then
|
||||
if self._counter == 0 or self._counter >= self._interval or currentStatus ~= lastStatus then
|
||||
local ip = self:_requestIP()
|
||||
if not ip then
|
||||
ip = ""
|
||||
self._IPFalseCounter = self._IPFalseCounter + 1
|
||||
if self._IPFalseCounter >= self.requestAttempts then
|
||||
self._interval = self.runIntervalFailed
|
||||
self._IPFalseCounter = 0
|
||||
else
|
||||
self._interval = self.runIntervalIPFailed
|
||||
end
|
||||
else
|
||||
self._interval = self.runInterval
|
||||
self._IPFalseCounter = 0
|
||||
end
|
||||
if ip ~= self._currentIp then
|
||||
self.status = ip
|
||||
if ip ~= "" then
|
||||
if self._counter > 0 and ip ~= self._lastResolvedIp then
|
||||
self.syslog(
|
||||
"notice",
|
||||
string.format("%s: public IP address changed to %s", self.name, ip)
|
||||
)
|
||||
self:runIpScript()
|
||||
end
|
||||
self._lastResolvedIp = ip
|
||||
end
|
||||
end
|
||||
self._currentIp = ip
|
||||
self._counter = 0
|
||||
end
|
||||
else
|
||||
self._currentIp = nil
|
||||
self.status = self._currentIp
|
||||
self._IPFalseCounter = 0
|
||||
self._counter = 0
|
||||
self._interval = self.runInterval
|
||||
end
|
||||
self._counter = self._counter + timeDiff
|
||||
end
|
||||
|
||||
function Module:onExit()
|
||||
return true
|
||||
end
|
||||
|
||||
return Module
|
||||
@ -0,0 +1,63 @@
|
||||
|
||||
local unistd = require("posix.unistd")
|
||||
|
||||
local Module = {
|
||||
name = "mod_reboot",
|
||||
runPrio = 20,
|
||||
config = {},
|
||||
syslog = function(level, msg) return true end,
|
||||
debugOutput = function(msg) return true end,
|
||||
writeValue = function(filePath, str) return false end,
|
||||
readValue = function(filePath) return nil end,
|
||||
deadPeriod = 3600,
|
||||
forceRebootDelay = 300,
|
||||
antiBootloopDelay = 300,
|
||||
status = nil,
|
||||
_deadCounter = 0,
|
||||
_rebooted = true,
|
||||
}
|
||||
|
||||
function Module:rebootDevice()
|
||||
self.syslog("warning", string.format("%s: reboot", self.name))
|
||||
os.execute("/sbin/reboot &")
|
||||
if self.forceRebootDelay > 0 then
|
||||
unistd.sleep(self.forceRebootDelay)
|
||||
self.syslog("warning", string.format("%s: force reboot", self.name))
|
||||
self.writeValue("/proc/sys/kernel/sysrq", "1")
|
||||
self.writeValue("/proc/sysrq-trigger", "b")
|
||||
end
|
||||
end
|
||||
|
||||
function Module:init(t)
|
||||
if t.dead_period ~= nil then
|
||||
self.deadPeriod = tonumber(t.dead_period)
|
||||
end
|
||||
if t.force_reboot_delay ~= nil then
|
||||
self.forceRebootDelay = tonumber(t.force_reboot_delay)
|
||||
end
|
||||
if tonumber(t.disconnected_at_startup) == 1 then
|
||||
self._rebooted = false
|
||||
end
|
||||
end
|
||||
|
||||
function Module:run(currentStatus, lastStatus, timeDiff, timeNow, inetChecked)
|
||||
if currentStatus == 1 then
|
||||
if not self._rebooted then
|
||||
if timeNow >= self.antiBootloopDelay and self._deadCounter >= self.deadPeriod then
|
||||
self:rebootDevice()
|
||||
self._rebooted = true
|
||||
else
|
||||
self._deadCounter = self._deadCounter + timeDiff
|
||||
end
|
||||
end
|
||||
else
|
||||
self._deadCounter = 0
|
||||
self._rebooted = false
|
||||
end
|
||||
end
|
||||
|
||||
function Module:onExit()
|
||||
return true
|
||||
end
|
||||
|
||||
return Module
|
||||
@ -0,0 +1,67 @@
|
||||
|
||||
local stdlib = require("posix.stdlib")
|
||||
local time = require("posix.time")
|
||||
local unistd = require("posix.unistd")
|
||||
|
||||
local Module = {
|
||||
name = "mod_regular_script",
|
||||
runPrio = 90,
|
||||
config = {},
|
||||
syslog = function(level, msg) return true end,
|
||||
debugOutput = function(msg) return true end,
|
||||
writeValue = function(filePath, str) return false end,
|
||||
readValue = function(filePath) return nil end,
|
||||
inetState = 2, -- 0: connected, 1: disconnected, 2: both
|
||||
runInterval = 3600,
|
||||
script = "",
|
||||
status = nil,
|
||||
_nextTime = nil,
|
||||
_firstRun = true,
|
||||
}
|
||||
|
||||
function Module:runExternalScript(scriptPath, currentStatus)
|
||||
if unistd.access(scriptPath, "r") then
|
||||
stdlib.setenv("INET_STATE", currentStatus)
|
||||
os.execute(string.format('/bin/sh "%s" &', scriptPath))
|
||||
end
|
||||
end
|
||||
|
||||
function Module:init(t)
|
||||
if t.inet_state ~= nil then
|
||||
self.inetState = tonumber(t.inet_state)
|
||||
end
|
||||
if t.interval ~= nil then
|
||||
self.runInterval = tonumber(t.interval)
|
||||
end
|
||||
if self.config.configDir then
|
||||
self.script = string.format(
|
||||
"%s/regular-script.%s", self.config.configDir, self.config.serviceConfig.instance)
|
||||
end
|
||||
end
|
||||
|
||||
function Module:run(currentStatus, lastStatus, timeDiff, timeNow, inetChecked)
|
||||
if not self._nextTime then
|
||||
if timeNow < self.runInterval then
|
||||
self._nextTime = self.runInterval
|
||||
else
|
||||
self._nextTime = timeNow - (timeNow % self.runInterval) + self.runInterval
|
||||
end
|
||||
end
|
||||
if self._firstRun then
|
||||
self.status = time.strftime ("%Y-%m-%d %H:%M:%S %z", time.localtime(time.time() + self._nextTime - timeNow))
|
||||
self._firstRun = false
|
||||
end
|
||||
if timeNow >= self._nextTime then
|
||||
self._nextTime = self._nextTime + self.runInterval
|
||||
if self.inetState == 2 or (self.inetState == 0 and currentStatus == 0) or (self.inetState == 1 and currentStatus == 1) then
|
||||
self.status = time.strftime ("%Y-%m-%d %H:%M:%S %z", time.localtime(time.time() + self._nextTime - timeNow))
|
||||
self:runExternalScript(self.script, currentStatus)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Module:onExit()
|
||||
return true
|
||||
end
|
||||
|
||||
return Module
|
||||
@ -0,0 +1,134 @@
|
||||
|
||||
local unistd = require("posix.unistd")
|
||||
|
||||
local Module = {
|
||||
name = "mod_user_scripts",
|
||||
runPrio = 80,
|
||||
config = {},
|
||||
syslog = function(level, msg) return true end,
|
||||
debugOutput = function(msg) return true end,
|
||||
writeValue = function(filePath, str) return false end,
|
||||
readValue = function(filePath) return nil end,
|
||||
deadPeriod = 0,
|
||||
alivePeriod = 0,
|
||||
upScript = "",
|
||||
downScript = "",
|
||||
upScriptAttempts = 1,
|
||||
upScriptAttemptInterval = 15,
|
||||
downScriptAttempts = 1,
|
||||
downScriptAttemptInterval = 15,
|
||||
status = nil,
|
||||
_deadCounter = 0,
|
||||
_aliveCounter = 0,
|
||||
_upScriptAttemptsCounter = 0,
|
||||
_upScriptAttemptIntervalCounter = 0,
|
||||
_downScriptAttemptsCounter = 0,
|
||||
_downScriptAttemptIntervalCounter = 0,
|
||||
_upScriptFirstAttempt = true,
|
||||
_downScriptFirstAttempt = true,
|
||||
_disconnectedAtStartup = false,
|
||||
_connectedAtStartup = false,
|
||||
}
|
||||
|
||||
function Module:runExternalScript(scriptPath)
|
||||
if unistd.access(scriptPath, "r") then
|
||||
os.execute(string.format('/bin/sh "%s" &', scriptPath))
|
||||
end
|
||||
end
|
||||
|
||||
function Module:init(t)
|
||||
if t.dead_period ~= nil then
|
||||
self.deadPeriod = tonumber(t.dead_period)
|
||||
end
|
||||
if t.alive_period ~= nil then
|
||||
self.alivePeriod = tonumber(t.alive_period)
|
||||
end
|
||||
if t.up_script_attempts ~= nil then
|
||||
self.upScriptAttempts = tonumber(t.up_script_attempts)
|
||||
end
|
||||
if t.up_script_attempt_interval ~= nil then
|
||||
self.upScriptAttemptInterval = tonumber(t.up_script_attempt_interval)
|
||||
end
|
||||
if t.down_script_attempts ~= nil then
|
||||
self.downScriptAttempts = tonumber(t.down_script_attempts)
|
||||
end
|
||||
if t.down_script_attempt_interval ~= nil then
|
||||
self.downScriptAttemptInterval = tonumber(t.down_script_attempt_interval)
|
||||
end
|
||||
if self.config.configDir then
|
||||
self.upScript = string.format(
|
||||
"%s/up-script.%s", self.config.configDir, self.config.serviceConfig.instance)
|
||||
self.downScript = string.format(
|
||||
"%s/down-script.%s", self.config.configDir, self.config.serviceConfig.instance)
|
||||
end
|
||||
if tonumber(t.connected_at_startup) == 1 then
|
||||
self._connectedAtStartup = true
|
||||
end
|
||||
if tonumber(t.disconnected_at_startup) == 1 then
|
||||
self._disconnectedAtStartup = true
|
||||
end
|
||||
end
|
||||
|
||||
function Module:runUpScriptFunc()
|
||||
self:runExternalScript(self.upScript)
|
||||
if self.upScriptAttempts > 0 then
|
||||
self._upScriptAttemptsCounter = self._upScriptAttemptsCounter + 1
|
||||
end
|
||||
end
|
||||
|
||||
function Module:runDownScriptFunc()
|
||||
self:runExternalScript(self.downScript)
|
||||
if self.downScriptAttempts > 0 then
|
||||
self._downScriptAttemptsCounter = self._downScriptAttemptsCounter + 1
|
||||
end
|
||||
end
|
||||
|
||||
function Module:run(currentStatus, lastStatus, timeDiff, timeNow, inetChecked)
|
||||
if currentStatus == 1 then
|
||||
self._upScriptAttemptsCounter = 0
|
||||
self._upScriptAttemptIntervalCounter = 0
|
||||
self._aliveCounter = 0
|
||||
self._connectedAtStartup = true
|
||||
self._upScriptFirstAttempt = true
|
||||
|
||||
if self._disconnectedAtStartup and self._deadCounter >= self.deadPeriod then
|
||||
if self.downScriptAttempts == 0 or self._downScriptAttemptsCounter < self.downScriptAttempts then
|
||||
if self._downScriptFirstAttempt or self._downScriptAttemptIntervalCounter >= self.downScriptAttemptInterval then
|
||||
self:runDownScriptFunc()
|
||||
self._downScriptAttemptIntervalCounter = 0
|
||||
self._downScriptFirstAttempt = false
|
||||
else
|
||||
self._downScriptAttemptIntervalCounter = self._downScriptAttemptIntervalCounter + timeDiff
|
||||
end
|
||||
end
|
||||
else
|
||||
self._deadCounter = self._deadCounter + timeDiff
|
||||
end
|
||||
elseif currentStatus == 0 then
|
||||
self._downScriptAttemptsCounter = 0
|
||||
self._downScriptAttemptIntervalCounter = 0
|
||||
self._deadCounter = 0
|
||||
self._disconnectedAtStartup = true
|
||||
self._downScriptFirstAttempt = true
|
||||
|
||||
if self._connectedAtStartup and self._aliveCounter >= self.alivePeriod then
|
||||
if self.upScriptAttempts == 0 or self._upScriptAttemptsCounter < self.upScriptAttempts then
|
||||
if self._upScriptFirstAttempt or self._upScriptAttemptIntervalCounter >= self.upScriptAttemptInterval then
|
||||
self:runUpScriptFunc()
|
||||
self._upScriptAttemptIntervalCounter = 0
|
||||
self._upScriptFirstAttempt = false
|
||||
else
|
||||
self._upScriptAttemptIntervalCounter = self._upScriptAttemptIntervalCounter + timeDiff
|
||||
end
|
||||
end
|
||||
else
|
||||
self._aliveCounter = self._aliveCounter + timeDiff
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Module:onExit()
|
||||
return true
|
||||
end
|
||||
|
||||
return Module
|
||||
46
ipt2socks/Makefile
Normal file
46
ipt2socks/Makefile
Normal file
@ -0,0 +1,46 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
#
|
||||
# Copyright (C) 2021 ImmortalWrt.org
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ipt2socks
|
||||
PKG_VERSION:=1.1.4
|
||||
PKG_RELEASE:=3
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/zfl9/ipt2socks/tar.gz/v$(PKG_VERSION)?
|
||||
PKG_HASH:=68dc76e63951d655c2fd9b420e175b5a75a50014d6db6e729398b41f2c988356
|
||||
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_USE_MIPS16:=0
|
||||
PKG_BUILD_FLAGS:=no-mips16
|
||||
|
||||
PKG_LICENSE:=AGPL-3.0
|
||||
PKG_LICENSE_FILE:=LICENSE
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/ipt2socks
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
TITLE:=Convert iptables to socks5
|
||||
URL:=https://github.com/zfl9/ipt2socks
|
||||
DEPENDS:=+libpthread
|
||||
endef
|
||||
|
||||
define Package/ipt2socks/description
|
||||
Utility for converting iptables (redirect/tproxy) to socks5.
|
||||
endef
|
||||
|
||||
TARGET_CFLAGS+= $(FPIC) -flto
|
||||
MAKE_FLAGS+= \
|
||||
CFLAGS="-std=c99 -pthread $(TARGET_CFLAGS)" \
|
||||
EVCFLAGS="$(TARGET_CFLAGS)"
|
||||
|
||||
define Package/ipt2socks/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/ipt2socks $(1)/usr/bin
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,ipt2socks))
|
||||
142
kismet/Makefile
Normal file
142
kismet/Makefile
Normal file
@ -0,0 +1,142 @@
|
||||
#
|
||||
# Copyright (C) 2006-2016 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:=kismet
|
||||
PKG_VERSION:=2016-07-R1
|
||||
PKG_RELEASE:=6
|
||||
|
||||
PKG_LICENSE:=LGPLv2.1
|
||||
PKG_CPE_ID:=cpe:/a:kismet:kismet
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.xz
|
||||
PKG_SOURCE_URL:=http://www.kismetwireless.net/code
|
||||
PKG_HASH:=bdb21f153311f1ff3b16621bf0d6740f66369bf0982b0a289c9a12af8847e237
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/kismet/Default
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
TITLE:=Kismet
|
||||
MAINTAINER:=Jean-Michel lacroix <lacroix@lepine-lacroix.info>
|
||||
DEPENDS:= +libstdcpp +libnl @BROKEN
|
||||
URL:=http://www.kismetwireless.net/
|
||||
SUBMENU:=Wireless
|
||||
endef
|
||||
|
||||
define Package/kismet/Default/description
|
||||
An 802.11 layer2 wireless network detector, sniffer, and intrusion
|
||||
detection system.
|
||||
endef
|
||||
|
||||
define Package/kismet-client/conffiles
|
||||
/etc/kismet/kismet.conf
|
||||
endef
|
||||
|
||||
define Package/kismet-drone/conffiles
|
||||
/etc/config/kismet_drone
|
||||
/etc/kismet/kismet_drone.conf
|
||||
endef
|
||||
|
||||
define Package/kismet-server/conffiles
|
||||
/etc/config/kismet_server
|
||||
/etc/kismet/kismet.conf
|
||||
endef
|
||||
|
||||
define Package/kismet-client
|
||||
$(call Package/kismet/Default)
|
||||
TITLE+= client
|
||||
DEPENDS+= +libncurses
|
||||
endef
|
||||
|
||||
define Package/kismet-client/description
|
||||
$(call Package/kismet/Default/description)
|
||||
This package contains the kismet text interface client.
|
||||
endef
|
||||
|
||||
define Package/kismet-drone
|
||||
$(call Package/kismet/Default)
|
||||
DEPENDS+= +libpcap +libpcre +libcap +wireless-tools
|
||||
TITLE+= drone
|
||||
endef
|
||||
|
||||
define Package/kismet-drone/description
|
||||
$(call Package/kismet/Default/description)
|
||||
This package contains the kismet remote sniffing.and monitoring drone.
|
||||
endef
|
||||
|
||||
define Package/kismet-server
|
||||
$(call Package/kismet/Default)
|
||||
DEPENDS+= +libpcap +libpcre +libcap +wireless-tools
|
||||
TITLE+= server
|
||||
endef
|
||||
|
||||
define Package/kismet-server/description
|
||||
$(call Package/kismet/Default/description)
|
||||
This package contains the kismet server.
|
||||
endef
|
||||
|
||||
CONFIGURE_ARGS += \
|
||||
--sysconfdir=/etc/kismet \
|
||||
|
||||
CONFIGURE_VARS += \
|
||||
CXXFLAGS="$$$$CXXFLAGS -fno-rtti" \
|
||||
LIBS="-lm" \
|
||||
CLIENTCLIBS="-lm"
|
||||
|
||||
define Build/Compile
|
||||
$(MAKE) -C $(PKG_BUILD_DIR) \
|
||||
LD="$(TARGET_CXX)" \
|
||||
all
|
||||
endef
|
||||
|
||||
define Package/kismet/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin/
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/scripts/kismet $(1)/usr/bin/kismet
|
||||
endef
|
||||
|
||||
define Package/kismet-client/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/kismet_client $(1)/usr/bin/
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/kismet/
|
||||
$(INSTALL_CONF) ./files/kismet.conf $(1)/etc/kismet/
|
||||
endef
|
||||
|
||||
define Package/kismet-drone/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/kismet_drone $(1)/usr/bin/
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/kismet/
|
||||
$(INSTALL_CONF) ./files/kismet_drone.conf $(1)/etc/kismet/
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/config
|
||||
$(INSTALL_CONF) ./files/kismet_drone.config $(1)/etc/config/kismet_drone
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_BIN) ./files/kismet_drone.init $(1)/etc/init.d/kismet_drone
|
||||
endef
|
||||
|
||||
define Package/kismet-server/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/kismet_server $(1)/usr/bin/
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/kismet/
|
||||
$(INSTALL_CONF) ./files/kismet.conf $(1)/etc/kismet/
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/config
|
||||
$(INSTALL_CONF) ./files/kismet_server.config $(1)/etc/config/kismet_server
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_BIN) ./files/kismet_server.init $(1)/etc/init.d/kismet_server
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,kismet-client))
|
||||
$(eval $(call BuildPackage,kismet-drone))
|
||||
$(eval $(call BuildPackage,kismet-server))
|
||||
304
kismet/files/kismet.conf
Normal file
304
kismet/files/kismet.conf
Normal file
@ -0,0 +1,304 @@
|
||||
# Kismet config file
|
||||
# Most of the "static" configs have been moved to here -- the command line
|
||||
# config was getting way too crowded and cryptic. We want functionality,
|
||||
# not continually reading --help!
|
||||
|
||||
# Version of Kismet config
|
||||
version=2009-newcore
|
||||
|
||||
# Name of server (Purely for organizational purposes)
|
||||
# If commented out, defaults to host name of system
|
||||
# servername=Kismet Server
|
||||
|
||||
# Prefix of where we log (as used in the logtemplate later)
|
||||
logprefix=/tmp
|
||||
|
||||
# Do we process the contents of data frames? If this is enabled, data
|
||||
# frames will be truncated to the headers only immediately after frame type
|
||||
# detection. This will disable IP detection, etc, however it is likely
|
||||
# safer (and definitely more polite) if monitoring networks you do not own.
|
||||
# hidedata=true
|
||||
|
||||
# Do we allow plugins to be used? This will load plugins from the system
|
||||
# and user plugin directiories when set to true (See the README for the default
|
||||
# plugin locations).
|
||||
allowplugins=false
|
||||
|
||||
# See the README for full information on the new source format
|
||||
# ncsource=interface:options
|
||||
# for example:
|
||||
# ncsource=wifi0:type=madwifi
|
||||
# ncsource=wlan0:name=intel,hop=false,channel=11
|
||||
ncsource=wlan0
|
||||
|
||||
# Comma-separated list of sources to enable. This is only needed if you defined
|
||||
# multiple sources and only want to enable some of them. By default, all defined
|
||||
# sources are enabled.
|
||||
# For example, if sources with name=prismsource and name=ciscosource are defined,
|
||||
# and you only want to enable those two:
|
||||
# enablesources=prismsource,ciscosource
|
||||
|
||||
# Control which channels we like to spend more time on. By default, the list
|
||||
# of channels is pulled from the driver automatically. By setting preferred channels,
|
||||
# if they are present in the channel list, they'll be set with a timing delay so that
|
||||
# more time is spent on them. Since 1, 6, 11 are the common default channels, it makes
|
||||
# sense to spend more time monitoring them.
|
||||
# For finer control, see further down in the config for the channellist= directives.
|
||||
preferredchannels=1,6,11
|
||||
|
||||
# How many channels per second do we hop? (1-10)
|
||||
channelvelocity=3
|
||||
|
||||
# By setting the dwell time for channel hopping we override the channelvelocity
|
||||
# setting above and dwell on each channel for the given number of seconds.
|
||||
#channeldwell=10
|
||||
|
||||
# Channels are defined as:
|
||||
# channellist=name:ch1,ch2,ch3
|
||||
# or
|
||||
# channellist=name:range-start-end-width-offset,ch,range,ch,...
|
||||
#
|
||||
# Channels may be a numeric channel or a frequency
|
||||
#
|
||||
# Channels may specify an additional wait period. For common default channels,
|
||||
# an additional wait period can be useful. Wait periods delay for that number
|
||||
# of times per second - so a configuration hopping 10 times per second with a
|
||||
# channel of 6:3 would delay 3/10ths of a second on channel 6.
|
||||
#
|
||||
# Channel lists may have up to 256 channels and ranges (combined). For power
|
||||
# users scanning more than 256 channels with a single card, ranges must be used.
|
||||
#
|
||||
# Ranges are meant for "power users" who wish to define a very large number of
|
||||
# channels. A range may specify channels or frequencies, and will automatically
|
||||
# sort themselves to cover channels in a non-overlapping fashion. An example
|
||||
# range for the normal 802.11b/g spectrum would be:
|
||||
#
|
||||
# range-1-11-3-1
|
||||
#
|
||||
# which indicates starting at 1, ending at 11, a channel width of 3 channels,
|
||||
# incrementing by one. A frequency based definition would be:
|
||||
#
|
||||
# range-2412-2462-22-5
|
||||
#
|
||||
# since 11g channels are 22 mhz wide and 5 mhz apart.
|
||||
#
|
||||
# Ranges have the flaw that they cannot be shared between sources in a non-overlapping
|
||||
# way, so multiple sources using the same range may hop in lockstep with each other
|
||||
# and duplicate the coverage.
|
||||
#
|
||||
# channellist=demo:1:3,6:3,11:3,range-5000-6000-20-10
|
||||
|
||||
# Default channel lists
|
||||
# These channel lists MUST BE PRESENT for Kismet to work properly. While it is
|
||||
# possible to change these, it is not recommended. These are used when the supported
|
||||
# channel list can not be found for the source; to force using these instead of
|
||||
# the detected supported channels, override with channellist= in the source definition
|
||||
#
|
||||
# IN GENERAL, if you think you want to modify these, what you REALLY want to do is
|
||||
# copy them and use channellist= in the packet source.
|
||||
channellist=IEEE80211b:1:3,6:3,11:3,2,7,3,8,4,9,5,10
|
||||
channellist=IEEE80211a:36,40,44,48,52,56,60,64,149,153,157,161,165
|
||||
channellist=IEEE80211ab:1:3,6:3,11:3,2,7,3,8,4,9,5,10,36,40,44,48,52,56,60,64,149,153,157,161,165
|
||||
|
||||
# Client/server listen config
|
||||
listen=tcp://127.0.0.1:2501
|
||||
#listen=tcp://0.0.0.0:2501
|
||||
|
||||
# People allowed to connect, comma separated IP addresses or network/mask
|
||||
# blocks. Netmasks can be expressed as dotted quad (/255.255.255.0) or as
|
||||
# numbers (/24)
|
||||
allowedhosts=127.0.0.1
|
||||
# Maximum number of concurrent GUI's
|
||||
maxclients=5
|
||||
# Maximum backlog before we start throwing out or killing clients. The
|
||||
# bigger this number, the more memory and the more power it will use.
|
||||
maxbacklog=5000
|
||||
|
||||
# Server + Drone config options. To have a Kismet server export live packets
|
||||
# as if it were a drone, uncomment these.
|
||||
# dronelisten=tcp://127.0.0.1:3501
|
||||
# droneallowedhosts=127.0.0.1
|
||||
# dronemaxclients=5
|
||||
# droneringlen=65535
|
||||
|
||||
# OUI file, expected format 00:11:22<tab>manufname
|
||||
# IEEE OUI file used to look up manufacturer info. We default to the
|
||||
# wireshark one since most people have that.
|
||||
#ouifile=/usr/share/manuf
|
||||
|
||||
# Do we have a GPS?
|
||||
gps=false
|
||||
# Do we use a locally serial attached GPS, or use a gpsd server, or
|
||||
# use a fixed virtual gps?
|
||||
# (Pick only one)
|
||||
gpstype=gpsd
|
||||
# Host:port that GPSD is running on. This can be localhost OR remote!
|
||||
gpshost=localhost:2947
|
||||
|
||||
|
||||
# gpstype=serial
|
||||
# What serial device do we look for the GPS on?
|
||||
# gpsdevice=/dev/rfcomm0
|
||||
|
||||
# gpstype=virtual
|
||||
# gpsposition=100,-50
|
||||
# gpsaltitude=1234
|
||||
|
||||
# Do we lock the mode? This overrides coordinates of lock "0", which will
|
||||
# generate some bad information until you get a GPS lock, but it will
|
||||
# fix problems with GPS units with broken NMEA that report lock 0
|
||||
gpsmodelock=false
|
||||
# Do we try to reconnect if we lose our link to the GPS, or do we just
|
||||
# let it die and be disabled?
|
||||
gpsreconnect=true
|
||||
|
||||
# Do we export packets over tun/tap virtual interfaces?
|
||||
tuntap_export=false
|
||||
# What virtual interface do we use
|
||||
tuntap_device=kistap0
|
||||
|
||||
# Packet filtering options:
|
||||
# filter_tracker - Packets filtered from the tracker are not processed or
|
||||
# recorded in any way.
|
||||
# filter_export - Controls what packets influence the exported CSV, network,
|
||||
# xml, gps, etc files.
|
||||
# All filtering options take arguments containing the type of address and
|
||||
# addresses to be filtered. Valid address types are 'ANY', 'BSSID',
|
||||
# 'SOURCE', and 'DEST'. Filtering can be inverted by the use of '!' before
|
||||
# the address. For example,
|
||||
# filter_tracker=ANY(!"00:00:DE:AD:BE:EF")
|
||||
# has the same effect as the previous mac_filter config file option.
|
||||
# filter_tracker=...
|
||||
# filter_dump=...
|
||||
# filter_export=...
|
||||
# filter_netclient=...
|
||||
|
||||
# Alerts to be reported and the throttling rates.
|
||||
# alert=name,throttle/unit,burst
|
||||
# The throttle/unit describes the number of alerts of this type that are
|
||||
# sent per time unit. Valid time units are second, minute, hour, and day.
|
||||
# Burst describes the number of alerts sent before throttling takes place.
|
||||
# For example:
|
||||
# alert=FOO,10/min,5
|
||||
# Would allow 5 alerts through before throttling is enabled, and will then
|
||||
# limit the number of alerts to 10 per minute.
|
||||
# A throttle rate of 0 disables throttling of the alert.
|
||||
# See the README for a list of alert types.
|
||||
alert=ADHOCCONFLICT,5/min,1/sec
|
||||
alert=AIRJACKSSID,5/min,1/sec
|
||||
alert=APSPOOF,10/min,1/sec
|
||||
alert=BCASTDISCON,5/min,2/sec
|
||||
alert=BSSTIMESTAMP,5/min,1/sec
|
||||
alert=CHANCHANGE,5/min,1/sec
|
||||
alert=CRYPTODROP,5/min,1/sec
|
||||
alert=DISASSOCTRAFFIC,10/min,1/sec
|
||||
alert=DEAUTHFLOOD,5/min,2/sec
|
||||
alert=DEAUTHCODEINVALID,5/min,1/sec
|
||||
alert=DISCONCODEINVALID,5/min,1/sec
|
||||
alert=DHCPNAMECHANGE,5/min,1/sec
|
||||
alert=DHCPOSCHANGE,5/min,1/sec
|
||||
alert=DHCPCLIENTID,5/min,1/sec
|
||||
alert=DHCPCONFLICT,10/min,1/sec
|
||||
alert=NETSTUMBLER,5/min,1/sec
|
||||
alert=LUCENTTEST,5/min,1/sec
|
||||
alert=LONGSSID,5/min,1/sec
|
||||
alert=MSFBCOMSSID,5/min,1/sec
|
||||
alert=MSFDLINKRATE,5/min,1/sec
|
||||
alert=MSFNETGEARBEACON,5/min,1/sec
|
||||
alert=NULLPROBERESP,5/min,1/sec
|
||||
alert=PROBENOJOIN,5/min,1/sec
|
||||
|
||||
# Controls behavior of the APSPOOF alert. SSID may be a literal match (ssid=) or
|
||||
# a regex (ssidregex=) if PCRE was available when kismet was built. The allowed
|
||||
# MAC list must be comma-separated and enclosed in quotes if there are multiple
|
||||
# MAC addresses allowed. MAC address masks are allowed.
|
||||
#apspoof=Foo1:ssidregex="(?i:foobar)",validmacs=00:11:22:33:44:55
|
||||
#apspoof=Foo2:ssid="Foobar",validmacs="00:11:22:33:44:55,aa:bb:cc:dd:ee:ff"
|
||||
|
||||
# Known WEP keys to decrypt, bssid,hexkey. This is only for networks where
|
||||
# the keys are already known, and it may impact throughput on slower hardware.
|
||||
# Multiple wepkey lines may be used for multiple BSSIDs.
|
||||
# wepkey=00:DE:AD:C0:DE:00,FEEDFACEDEADBEEF01020304050607080900
|
||||
|
||||
# Is transmission of the keys to the client allowed? This may be a security
|
||||
# risk for some. If you disable this, you will not be able to query keys from
|
||||
# a client.
|
||||
allowkeytransmit=true
|
||||
|
||||
# How often (in seconds) do we write all our data files (0 to disable)
|
||||
writeinterval=10
|
||||
|
||||
# Do we use sound?
|
||||
# Not to be confused with GUI sound parameter, this controls wether or not the
|
||||
# server itself will play sound. Primarily for headless or automated systems.
|
||||
enablesound=false
|
||||
# Path to sound player
|
||||
soundbin=play
|
||||
|
||||
sound=newnet,true
|
||||
sound=newcryptnet,true
|
||||
sound=packet,true
|
||||
sound=gpslock,true
|
||||
sound=gpslost,true
|
||||
sound=alert,true
|
||||
|
||||
# Does the server have speech? (Again, not to be confused with the GUI's speech)
|
||||
enablespeech=false
|
||||
# Binary used for speech (if not in path, full path must be specified)
|
||||
speechbin=flite
|
||||
# Specify raw or festival; Flite (and anything else that doesn't need formatting
|
||||
# around the string to speak) is 'raw', festival requires the string be wrapped in
|
||||
# SayText("...")
|
||||
speechtype=raw
|
||||
|
||||
# How do we speak? Valid options:
|
||||
# speech Normal speech
|
||||
# nato NATO spellings (alpha, bravo, charlie)
|
||||
# spell Spell the letters out (aye, bee, sea)
|
||||
speechencoding=nato
|
||||
|
||||
speech=new,"New network detected s.s.i.d. %1 channel %2"
|
||||
speech=alert,"Alert %1"
|
||||
speech=gpslost,"G.P.S. signal lost"
|
||||
speech=gpslock,"G.P.S. signal O.K."
|
||||
|
||||
# How many alerts do we backlog for new clients? Only change this if you have
|
||||
# a -very- low memory system and need those extra bytes, or if you have a high
|
||||
# memory system and a huge number of alert conditions.
|
||||
alertbacklog=50
|
||||
|
||||
# File types to log, comma separated. Built-in log file types:
|
||||
# alert Text file of alerts
|
||||
# gpsxml XML per-packet GPS log
|
||||
# nettxt Networks in text format
|
||||
# netxml Networks in XML format
|
||||
# pcapdump tcpdump/wireshark compatible pcap log file
|
||||
# string All strings seen (increases CPU load)
|
||||
logtypes=pcapdump,gpsxml,netxml,alert
|
||||
|
||||
# Format of the pcap dump (PPI or 80211)
|
||||
pcapdumpformat=ppi
|
||||
# pcapdumpformat=80211
|
||||
|
||||
# Default log title
|
||||
logdefault=Kismet
|
||||
|
||||
# logtemplate - Filename logging template.
|
||||
# This is, at first glance, really nasty and ugly, but you'll hardly ever
|
||||
# have to touch it so don't complain too much.
|
||||
#
|
||||
# %p is replaced by the logging prefix + '/'
|
||||
# %n is replaced by the logging instance name
|
||||
# %d is replaced by the starting date as Mon-DD-YYYY
|
||||
# %D is replaced by the current date as YYYYMMDD
|
||||
# %t is replaced by the starting time as HH-MM-SS
|
||||
# %i is replaced by the increment log in the case of multiple logs
|
||||
# %l is replaced by the log type (pcapdump, strings, etc)
|
||||
# %h is replaced by the home directory
|
||||
|
||||
logtemplate=%p%n-%D-%t-%i.%l
|
||||
|
||||
# Where state info, etc, is stored. You shouldn't ever need to change this.
|
||||
# This is a directory.
|
||||
configdir=%h/.kismet/
|
||||
|
||||
69
kismet/files/kismet_drone.conf
Normal file
69
kismet/files/kismet_drone.conf
Normal file
@ -0,0 +1,69 @@
|
||||
# Kismet drone config file
|
||||
|
||||
version=newcore.1
|
||||
|
||||
# Name of drone server (informational)
|
||||
servername=Kismet-Drone
|
||||
|
||||
# Drone configuration
|
||||
# Protocol, interface, and port to listen on
|
||||
dronelisten=tcp://127.0.0.1:2502
|
||||
# Hosts allowed to connect, comma separated. May include netmasks.
|
||||
# allowedhosts=127.0.0.1,10.10.10.0/255.255.255.0
|
||||
droneallowedhosts=127.0.0.1
|
||||
# Maximum number of drone clients
|
||||
dronemaxclients=10
|
||||
droneringlen=65535
|
||||
|
||||
# Do we have a GPS?
|
||||
gps=true
|
||||
# Do we use a locally serial attached GPS, or use a gpsd server?
|
||||
# (Pick only one)
|
||||
gpstype=gpsd
|
||||
# gpstype=serial
|
||||
# What serial device do we look for the GPS on?
|
||||
gpsdevice=/dev/rfcomm0
|
||||
# Host:port that GPSD is running on. This can be localhost OR remote!
|
||||
gpshost=localhost:2947
|
||||
# Do we lock the mode? This overrides coordinates of lock "0", which will
|
||||
# generate some bad information until you get a GPS lock, but it will
|
||||
# fix problems with GPS units with broken NMEA that report lock 0
|
||||
gpsmodelock=false
|
||||
# Do we try to reconnect if we lose our link to the GPS, or do we just
|
||||
# let it die and be disabled?
|
||||
gpsreconnect=true
|
||||
|
||||
# See the README for full information on the new source format
|
||||
# ncsource=interface:options
|
||||
ncsource=null
|
||||
# for example:
|
||||
# ncsource=wlan0
|
||||
# ncsource=wifi0:type=madwifi
|
||||
# ncsource=wlan0:name=intel,hop=false,channel=11
|
||||
|
||||
# Special per-source options
|
||||
# sourceopts=[sourcename|*]:opt1,opt2
|
||||
# sourceopts=*:fuzzycrypt,weakvalidate
|
||||
|
||||
# Comma-separated list of sources to enable, if you don't want to enable all
|
||||
# the sources you defined.
|
||||
# enablesource=source1,source2
|
||||
|
||||
# How many channels per second do we hop? (1-10)
|
||||
channelvelocity=5
|
||||
|
||||
# By setting the dwell time for channel hopping we override the channelvelocity
|
||||
# setting above and dwell on each channel for the given number of seconds.
|
||||
#channeldwell=10
|
||||
|
||||
# Users outside the US might want to use this list:
|
||||
# channellist=IEEE80211b:1,7,13,2,8,3,14,9,4,10,5,11,6,12
|
||||
channellist=IEEE80211b:1:3,6:3,11:3,2,7,3,8,4,9,5,10
|
||||
|
||||
# US IEEE 80211a
|
||||
channellist=IEEE80211a:36,40,44,48,52,56,60,64,149,153,157,161,165
|
||||
|
||||
# Combo
|
||||
channellist=IEEE80211ab:1:3,6:3,11:3,2,7,3,8,4,9,5,10,36,40,44,48,52,56,60,64,149,153,157,161,165
|
||||
|
||||
|
||||
2
kismet/files/kismet_drone.config
Normal file
2
kismet/files/kismet_drone.config
Normal file
@ -0,0 +1,2 @@
|
||||
config kismet_drone
|
||||
option enabled 0
|
||||
26
kismet/files/kismet_drone.init
Executable file
26
kismet/files/kismet_drone.init
Executable file
@ -0,0 +1,26 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
# Copyright (C) 2013-2014 OpenWrt.org
|
||||
|
||||
START=99
|
||||
STOP=99
|
||||
|
||||
USE_PROCD=1
|
||||
PROG=/usr/bin/kismet_drone
|
||||
NAME=kismet_drone
|
||||
|
||||
kismet_drone_instance() {
|
||||
procd_open_instance
|
||||
procd_set_param command "${PROG}"
|
||||
procd_append_param command -f /etc/kismet/kismet_drone.conf -s
|
||||
procd_set_param respawn
|
||||
procd_close_instance
|
||||
}
|
||||
|
||||
start_service() {
|
||||
config_load "${NAME}"
|
||||
config_foreach kismet_drone_instance
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
service_stop "${PROG}"
|
||||
}
|
||||
2
kismet/files/kismet_server.config
Normal file
2
kismet/files/kismet_server.config
Normal file
@ -0,0 +1,2 @@
|
||||
config kismet_server
|
||||
option enabled 0
|
||||
26
kismet/files/kismet_server.init
Executable file
26
kismet/files/kismet_server.init
Executable file
@ -0,0 +1,26 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
# Copyright (C) 2013-2014 OpenWrt.org
|
||||
|
||||
START=99
|
||||
STOP=99
|
||||
|
||||
USE_PROCD=1
|
||||
PROG=/usr/bin/kismet_server
|
||||
NAME=kismet_server
|
||||
|
||||
kismet_server_instance() {
|
||||
procd_open_instance
|
||||
procd_set_param command "${PROG}"
|
||||
procd_append_param command -f /etc/kismet/kismet.conf -s
|
||||
procd_set_param respawn
|
||||
procd_close_instance
|
||||
}
|
||||
|
||||
start_service() {
|
||||
config_load "${NAME}"
|
||||
config_foreach kismet_server_instance
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
service_stop "${PROG}"
|
||||
}
|
||||
12
kismet/patches/010-dont-add-host-include-paths.patch
Normal file
12
kismet/patches/010-dont-add-host-include-paths.patch
Normal file
@ -0,0 +1,12 @@
|
||||
--- a/configure
|
||||
+++ b/configure
|
||||
@@ -5981,9 +5981,6 @@ fi
|
||||
|
||||
|
||||
|
||||
-# Add additional cflags since some distros bury panel.h
|
||||
-CPPFLAGS="$CPPFLAGS -I/usr/include/ncurses"
|
||||
-
|
||||
termcontrol="none";
|
||||
|
||||
if test "$wantclient" = "yes"; then
|
||||
32
kismet/patches/020-musl-include-fixes.patch
Normal file
32
kismet/patches/020-musl-include-fixes.patch
Normal file
@ -0,0 +1,32 @@
|
||||
--- a/configfile.cc
|
||||
+++ b/configfile.cc
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
+#include <time.h>
|
||||
#include "configfile.h"
|
||||
#include "util.h"
|
||||
|
||||
--- a/dumpfile_tuntap.cc
|
||||
+++ b/dumpfile_tuntap.cc
|
||||
@@ -20,8 +20,8 @@
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
-#include "dumpfile_tuntap.h"
|
||||
#include "ifcontrol.h"
|
||||
+#include "dumpfile_tuntap.h"
|
||||
#include "ipc_remote.h"
|
||||
|
||||
#ifndef SYS_CYGWIN
|
||||
--- a/ifcontrol.h
|
||||
+++ b/ifcontrol.h
|
||||
@@ -39,7 +39,6 @@
|
||||
#include <net/if_arp.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <netinet/in.h>
|
||||
-#include <netinet/if_ether.h>
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
64
kismet/patches/030-libcxx.patch
Normal file
64
kismet/patches/030-libcxx.patch
Normal file
@ -0,0 +1,64 @@
|
||||
--- a/ipc_remote.cc
|
||||
+++ b/ipc_remote.cc
|
||||
@@ -914,7 +914,7 @@ int RootIPCRemote::OpenFDPassSock() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
- if (bind(ipc_fd_fd, (const struct sockaddr *) &unixsock, sizeof(unixsock))) {
|
||||
+ if (::bind(ipc_fd_fd, (const struct sockaddr *) &unixsock, sizeof(unixsock))) {
|
||||
close(ipc_fd_fd);
|
||||
_MSG("Failed to bind socket to pass file descriptors: " +
|
||||
string(strerror(errno)), MSGFLAG_ERROR);
|
||||
--- a/packetsource_wext.cc
|
||||
+++ b/packetsource_wext.cc
|
||||
@@ -405,7 +405,7 @@ void PacketSource_Wext::OpenWpaSupplican
|
||||
wpa_local.sun_family = AF_UNIX;
|
||||
snprintf(wpa_local.sun_path, sizeof(wpa_local.sun_path),
|
||||
"%s", wpa_local_path.c_str());
|
||||
- if (bind(wpa_sock, (struct sockaddr *) &wpa_local, sizeof(wpa_local)) < 0) {
|
||||
+ if (::bind(wpa_sock, (struct sockaddr *) &wpa_local, sizeof(wpa_local)) < 0) {
|
||||
_MSG("Source '" + parent + "' failed to bind local socket for "
|
||||
"wpa_supplicant, disabling scan_wpa: " + string(strerror(errno)),
|
||||
MSGFLAG_PRINTERROR);
|
||||
--- a/tcpclient.cc
|
||||
+++ b/tcpclient.cc
|
||||
@@ -75,7 +75,7 @@ int TcpClient::Connect(const char *in_re
|
||||
local_sock.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
local_sock.sin_port = htons(0);
|
||||
|
||||
- if (bind(cli_fd, (struct sockaddr *) &local_sock, sizeof(local_sock)) < 0) {
|
||||
+ if (::bind(cli_fd, (struct sockaddr *) &local_sock, sizeof(local_sock)) < 0) {
|
||||
snprintf(errstr, 1024, "TCP client bind() failed: %s", strerror(errno));
|
||||
globalreg->messagebus->InjectMessage(errstr, MSGFLAG_ERROR);
|
||||
close(cli_fd);
|
||||
@@ -170,7 +170,7 @@ int TcpClient::ConnectSync(const char *i
|
||||
local_sock.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
local_sock.sin_port = htons(0);
|
||||
|
||||
- if (bind(cli_fd, (struct sockaddr *) &local_sock, sizeof(local_sock)) < 0) {
|
||||
+ if (::bind(cli_fd, (struct sockaddr *) &local_sock, sizeof(local_sock)) < 0) {
|
||||
snprintf(errstr, 1024, "TCP client bind() failed: %s", strerror(errno));
|
||||
globalreg->messagebus->InjectMessage(errstr, MSGFLAG_ERROR);
|
||||
close(cli_fd);
|
||||
--- a/tcpserver.cc
|
||||
+++ b/tcpserver.cc
|
||||
@@ -84,7 +84,7 @@ int TcpServer::EnableServer() {
|
||||
}
|
||||
|
||||
// Bind the socket
|
||||
- if (bind(serv_fd, (struct sockaddr *) &serv_sock, sizeof(serv_sock)) < 0) {
|
||||
+ if (::bind(serv_fd, (struct sockaddr *) &serv_sock, sizeof(serv_sock)) < 0) {
|
||||
snprintf(errstr, STATUS_MAX, "TCP server bind() failed: %s",
|
||||
strerror(errno));
|
||||
globalreg->messagebus->InjectMessage(errstr, MSGFLAG_ERROR);
|
||||
--- a/unixdomainserver.cc
|
||||
+++ b/unixdomainserver.cc
|
||||
@@ -64,7 +64,7 @@ int UnixDomainServer::EnableServer() {
|
||||
}
|
||||
|
||||
// Bind the socket
|
||||
- if (bind(serv_fd, (struct sockaddr *) &serv_sock, sizeof(serv_sock)) < 0) {
|
||||
+ if (::bind(serv_fd, (struct sockaddr *) &serv_sock, sizeof(serv_sock)) < 0) {
|
||||
_MSG("Unix domain server bind() failed: " + string(strerror(errno)),
|
||||
MSGFLAG_ERROR);
|
||||
return -1;
|
||||
12
kismet/patches/040-uclibc.patch
Normal file
12
kismet/patches/040-uclibc.patch
Normal file
@ -0,0 +1,12 @@
|
||||
--- a/ipc_remote.cc
|
||||
+++ b/ipc_remote.cc
|
||||
@@ -941,8 +941,8 @@ int RootIPCRemote::OpenFDPassSock() {
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
- struct cmsghdr header;
|
||||
int fd;
|
||||
+ struct cmsghdr header;
|
||||
} __attribute__((packed)) cmsg_fd;
|
||||
|
||||
int RootIPCRemote::SendDescriptor(int in_fd) {
|
||||
44
lua-neturl/Makefile
Normal file
44
lua-neturl/Makefile
Normal file
@ -0,0 +1,44 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
#
|
||||
# Copyright (C) 2022-2023 ImmortalWrt.org
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=neturl
|
||||
PKG_VERSION:=1.2-1
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/golgote/neturl/tar.gz/v$(PKG_VERSION)?
|
||||
PKG_HASH:=fc4ea1b114125ae821bef385936cd12429485204576e77b6283692bd0cc9b1ab
|
||||
|
||||
PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
|
||||
PKG_LICENSE:=MIT
|
||||
PKG_LICNESE_FILES:=LICENSE.txt
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/lua-neturl
|
||||
SUBMENU:=Lua
|
||||
SECTION:=lang
|
||||
CATEGORY:=Languages
|
||||
TITLE:=URL and Query string parser, builder, normalizer for Lua
|
||||
URL:=https://github.com/golgote/neturl
|
||||
DEPENDS:=+lua
|
||||
PKGARCH:=all
|
||||
endef
|
||||
|
||||
define Package/lua-neturl/description
|
||||
This small Lua library provides a few functions to parse URL with
|
||||
querystring and build new URL easily.
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/lua-neturl/install
|
||||
$(INSTALL_DIR) $(1)/usr/lib/lua
|
||||
$(CP) $(PKG_BUILD_DIR)/lib/net/url.lua $(1)/usr/lib/lua/
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,lua-neturl))
|
||||
20
lua-neturl/patches/010-userinfo-regex.patch
Normal file
20
lua-neturl/patches/010-userinfo-regex.patch
Normal file
@ -0,0 +1,20 @@
|
||||
--- a/lib/net/url.lua
|
||||
+++ b/lib/net/url.lua
|
||||
@@ -340,7 +340,7 @@ function M:setAuthority(authority)
|
||||
self.password = v
|
||||
return ''
|
||||
end)
|
||||
- if string.find(userinfo, "^[%w%+%.]+$") then
|
||||
+ if string.find(userinfo, "^[%p%w%+%.]+$") then
|
||||
self.user = userinfo
|
||||
else
|
||||
-- incorrect userinfo
|
||||
@@ -369,7 +369,7 @@ function M.parse(url)
|
||||
comp.fragment = v
|
||||
return ''
|
||||
end)
|
||||
- url =url:gsub('^([%w][%w%+%-%.]*)%:', function(v)
|
||||
+ url =url:gsub('^([%w][%w%+%-%_%.]*)%:', function(v)
|
||||
comp.scheme = v:lower()
|
||||
return ''
|
||||
end)
|
||||
18
luci-app-bandix/Makefile
Normal file
18
luci-app-bandix/Makefile
Normal file
@ -0,0 +1,18 @@
|
||||
# See /LICENSE for more information.
|
||||
# This is free software, licensed under the GNU General Public License v2.
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
|
||||
LUCI_TITLE:=LuCI Bandix app for network traffic monitoring
|
||||
LUCI_DEPENDS:=+luci-base +luci-lib-jsonc +curl +bandix
|
||||
|
||||
PKG_MAINTAINER:=timsaya
|
||||
|
||||
PKG_VERSION:=0.12.8
|
||||
PKG_RELEASE:=1
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
File diff suppressed because it is too large
Load Diff
1525
luci-app-bandix/htdocs/luci-static/resources/view/bandix/dns.js
Normal file
1525
luci-app-bandix/htdocs/luci-static/resources/view/bandix/dns.js
Normal file
File diff suppressed because it is too large
Load Diff
8053
luci-app-bandix/htdocs/luci-static/resources/view/bandix/index.js
Normal file
8053
luci-app-bandix/htdocs/luci-static/resources/view/bandix/index.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,831 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require form';
|
||||
'require ui';
|
||||
'require uci';
|
||||
'require rpc';
|
||||
'require tools.widgets as widgets';
|
||||
|
||||
|
||||
// 暗色模式检测已改为使用 CSS 媒体查询 @media (prefers-color-scheme: dark)
|
||||
|
||||
// 简单的 Markdown 解析函数
|
||||
function parseMarkdown(text) {
|
||||
if (!text) return '';
|
||||
|
||||
// 转义 HTML 特殊字符
|
||||
function escapeHtml(html) {
|
||||
var div = document.createElement('div');
|
||||
div.textContent = html;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// 先处理代码块(避免其他格式化影响代码)
|
||||
var codeBlocks = [];
|
||||
var codeBlockIndex = 0;
|
||||
text = text.replace(/```([\s\S]*?)```/g, function (match, code) {
|
||||
var id = 'CODE_BLOCK_' + codeBlockIndex++;
|
||||
codeBlocks.push({
|
||||
id: id,
|
||||
content: escapeHtml(code.trim())
|
||||
});
|
||||
return id;
|
||||
});
|
||||
|
||||
// 处理行内代码
|
||||
text = text.replace(/`([^`]+)`/g, function (match, code) {
|
||||
return '<code style="background: rgba(0,0,0,0.1); padding: 2px 4px; border-radius: 3px; font-family: monospace; font-size: 0.9em;">' + escapeHtml(code) + '</code>';
|
||||
});
|
||||
|
||||
// 处理换行(GitHub markdown 使用 \r\n)
|
||||
text = text.replace(/\r\n/g, '\n');
|
||||
|
||||
// 处理标题
|
||||
text = text.replace(/^### (.*$)/gm, '<h3 style="font-size: 1.1em; font-weight: 600; margin: 12px 0 8px 0;">$1</h3>');
|
||||
text = text.replace(/^## (.*$)/gm, '<h2 style="font-size: 1.2em; font-weight: 600; margin: 14px 0 10px 0;">$1</h2>');
|
||||
text = text.replace(/^# (.*$)/gm, '<h1 style="font-size: 1.3em; font-weight: 600; margin: 16px 0 12px 0;">$1</h1>');
|
||||
|
||||
// 处理粗体
|
||||
text = text.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
||||
text = text.replace(/__(.+?)__/g, '<strong>$1</strong>');
|
||||
|
||||
// 处理斜体
|
||||
text = text.replace(/\*(.+?)\*/g, function (match, content) {
|
||||
// 避免匹配代码块中的 *
|
||||
if (match.indexOf('<code') === -1 && match.indexOf('</code>') === -1) {
|
||||
return '<em>' + content + '</em>';
|
||||
}
|
||||
return match;
|
||||
});
|
||||
text = text.replace(/_(.+?)_/g, function (match, content) {
|
||||
// 避免匹配代码块中的 _
|
||||
if (match.indexOf('<code') === -1 && match.indexOf('</code>') === -1) {
|
||||
return '<em>' + content + '</em>';
|
||||
}
|
||||
return match;
|
||||
});
|
||||
|
||||
// 处理链接
|
||||
text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener" style="color: #3b82f6; text-decoration: underline;">$1</a>');
|
||||
|
||||
// 处理图片
|
||||
text = text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" style="max-width: 100%; height: auto; border-radius: 4px; margin: 8px 0;" />');
|
||||
|
||||
// 处理无序列表
|
||||
var lines = text.split('\n');
|
||||
var inList = false;
|
||||
var result = [];
|
||||
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var line = lines[i];
|
||||
var listMatch = line.match(/^[\s]*[-*+]\s+(.+)$/);
|
||||
|
||||
if (listMatch) {
|
||||
if (!inList) {
|
||||
result.push('<ul style="margin: 8px 0; padding-left: 20px;">');
|
||||
inList = true;
|
||||
}
|
||||
result.push('<li style="margin: 4px 0;">' + listMatch[1] + '</li>');
|
||||
} else {
|
||||
if (inList) {
|
||||
result.push('</ul>');
|
||||
inList = false;
|
||||
}
|
||||
result.push(line);
|
||||
}
|
||||
}
|
||||
if (inList) {
|
||||
result.push('</ul>');
|
||||
}
|
||||
text = result.join('\n');
|
||||
|
||||
// 处理有序列表
|
||||
lines = text.split('\n');
|
||||
inList = false;
|
||||
result = [];
|
||||
|
||||
for (i = 0; i < lines.length; i++) {
|
||||
line = lines[i];
|
||||
var orderedMatch = line.match(/^[\s]*\d+\.\s+(.+)$/);
|
||||
|
||||
if (orderedMatch) {
|
||||
if (!inList) {
|
||||
result.push('<ol style="margin: 8px 0; padding-left: 20px;">');
|
||||
inList = true;
|
||||
}
|
||||
result.push('<li style="margin: 4px 0;">' + orderedMatch[1] + '</li>');
|
||||
} else {
|
||||
if (inList) {
|
||||
result.push('</ol>');
|
||||
inList = false;
|
||||
}
|
||||
result.push(line);
|
||||
}
|
||||
}
|
||||
if (inList) {
|
||||
result.push('</ol>');
|
||||
}
|
||||
text = result.join('\n');
|
||||
|
||||
// 处理段落(空行分隔)
|
||||
text = text.split(/\n\s*\n/).map(function (para) {
|
||||
para = para.trim();
|
||||
if (para && !para.match(/^<[h|u|o|l|i]/)) {
|
||||
return '<p style="margin: 8px 0; line-height: 1.6;">' + para + '</p>';
|
||||
}
|
||||
return para;
|
||||
}).join('\n');
|
||||
|
||||
// 处理单行换行(两个空格或反斜杠结尾)
|
||||
text = text.replace(/ \n/g, '<br />\n');
|
||||
text = text.replace(/\\\n/g, '<br />\n');
|
||||
|
||||
// 恢复代码块
|
||||
for (var j = 0; j < codeBlocks.length; j++) {
|
||||
var block = codeBlocks[j];
|
||||
text = text.replace(block.id, '<pre style="background: rgba(0,0,0,0.05); padding: 12px; border-radius: 4px; overflow-x: auto; font-family: monospace; font-size: 0.875rem; margin: 8px 0;"><code>' + block.content + '</code></pre>');
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
// 声明 RPC 调用方法
|
||||
var callClearData = rpc.declare({
|
||||
object: 'luci.bandix',
|
||||
method: 'clearData',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callRestartService = rpc.declare({
|
||||
object: 'luci.bandix',
|
||||
method: 'restartService',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callGetVersion = rpc.declare({
|
||||
object: 'luci.bandix',
|
||||
method: 'getVersion',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callCheckUpdate = rpc.declare({
|
||||
object: 'luci.bandix',
|
||||
method: 'checkUpdate',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callInstallUpdate = rpc.declare({
|
||||
object: 'luci.bandix',
|
||||
method: 'installUpdate',
|
||||
params: ['package_type', 'download_url']
|
||||
});
|
||||
|
||||
return view.extend({
|
||||
load: function () {
|
||||
var self = this;
|
||||
return Promise.all([
|
||||
uci.load('bandix'),
|
||||
uci.load('network'),
|
||||
uci.load('luci'),
|
||||
uci.load('argon').catch(function () {
|
||||
// argon 配置可能不存在,忽略错误
|
||||
return null;
|
||||
}),
|
||||
callGetVersion().then(function (result) {
|
||||
// 存储版本信息到 view 实例
|
||||
self.versionInfo = result || {};
|
||||
return result;
|
||||
}).catch(function (err) {
|
||||
// 如果获取失败,使用默认值
|
||||
self.versionInfo = {
|
||||
luci_app_version: _('Unknown'),
|
||||
bandix_version: _('Unknown')
|
||||
};
|
||||
return null;
|
||||
})
|
||||
]);
|
||||
},
|
||||
|
||||
render: function (data) {
|
||||
var m, s, o;
|
||||
|
||||
// 确保UCI section存在,否则表单不会显示
|
||||
if (!uci.get('bandix', 'general')) {
|
||||
uci.add('bandix', 'general', 'general');
|
||||
}
|
||||
if (!uci.get('bandix', 'traffic')) {
|
||||
uci.add('bandix', 'traffic', 'traffic');
|
||||
}
|
||||
if (!uci.get('bandix', 'connections')) {
|
||||
uci.add('bandix', 'connections', 'connections');
|
||||
}
|
||||
if (!uci.get('bandix', 'dns')) {
|
||||
uci.add('bandix', 'dns', 'dns');
|
||||
}
|
||||
|
||||
// 创建表单
|
||||
m = new form.Map('bandix');
|
||||
|
||||
// 1. 基本设置部分 (general)
|
||||
s = m.section(form.NamedSection, 'general', 'general', _('Basic Settings'));
|
||||
s.description = _('Configure basic parameters for Bandix service');
|
||||
s.addremove = false;
|
||||
|
||||
// 添加端口设置选项
|
||||
o = s.option(form.Value, 'port', _('Port'),
|
||||
_('Port for Bandix service to listen on'));
|
||||
o.default = '8686';
|
||||
o.datatype = 'port';
|
||||
o.placeholder = '8686';
|
||||
o.rmempty = false;
|
||||
|
||||
// 使用 OpenWrt 自带的设备选择器
|
||||
o = s.option(widgets.DeviceSelect, 'iface', _('Monitor Interface'),
|
||||
_('Select the LAN network interface to monitor'));
|
||||
o.rmempty = false;
|
||||
o.noaliases = true;
|
||||
o.nobridges = false;
|
||||
o.nocreate = true;
|
||||
|
||||
// 添加日志级别选择选项
|
||||
o = s.option(form.ListValue, 'log_level', _('Log Level'),
|
||||
_('Set the log level for Bandix service'));
|
||||
o.value('trace', 'Trace');
|
||||
o.value('debug', 'Debug');
|
||||
o.value('info', 'Info');
|
||||
o.value('warn', 'Warn');
|
||||
o.value('error', 'Error');
|
||||
o.default = 'info';
|
||||
o.rmempty = false;
|
||||
|
||||
// 添加数据目录设置
|
||||
o = s.option(form.Value, 'data_dir', _('Data Directory'),
|
||||
_('If you change the directory, please manually add it to the backup directory'));
|
||||
o.default = '/usr/share/bandix';
|
||||
o.datatype = 'string';
|
||||
o.placeholder = '/usr/share/bandix';
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.option(form.ListValue, 'tc_backend', _('TC backend'),
|
||||
_('Select TC attach backend. Recommendation: use auto by default; tcx is preferred on kernel >= 6.6; netlink is safer on older kernels.'));
|
||||
o.value('auto', 'auto');
|
||||
o.value('tcx', 'tcx');
|
||||
o.value('netlink', 'netlink');
|
||||
o.default = 'auto';
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.option(form.ListValue, 'tc_order', _('TC order'));
|
||||
o.value('first', 'first');
|
||||
o.value('default', 'default');
|
||||
o.value('last', 'last');
|
||||
o.value('before', 'before');
|
||||
o.value('after', 'after');
|
||||
o.default = 'default';
|
||||
o.rmempty = false;
|
||||
o.depends('tc_backend', 'tcx');
|
||||
|
||||
o = s.option(form.Value, 'netlink_priority', _('Netlink priority'),
|
||||
_('Only used when backend is netlink. Range: 0..65535 (0 means default).'));
|
||||
o.datatype = 'range(0,65535)';
|
||||
o.default = '0';
|
||||
o.placeholder = '0';
|
||||
o.rmempty = false;
|
||||
o.depends('tc_backend', 'netlink');
|
||||
|
||||
o = s.option(form.Value, 'tcx_anchor_ingress_id', _('TCX ingress anchor program id'),
|
||||
_('Used when tc_order is before/after. Must be a valid ingress program id on the same interface.'));
|
||||
o.datatype = 'uinteger';
|
||||
o.rmempty = true;
|
||||
o.depends({ tc_backend: 'tcx', tc_order: 'before' });
|
||||
o.depends({ tc_backend: 'tcx', tc_order: 'after' });
|
||||
|
||||
o = s.option(form.Value, 'tcx_anchor_egress_id', _('TCX egress anchor program id'),
|
||||
_('Used when tc_order is before/after. Must be a valid egress program id on the same interface.'));
|
||||
o.datatype = 'uinteger';
|
||||
o.rmempty = true;
|
||||
o.depends({ tc_backend: 'tcx', tc_order: 'before' });
|
||||
o.depends({ tc_backend: 'tcx', tc_order: 'after' });
|
||||
|
||||
// 添加版本信息显示(合并显示)
|
||||
o = s.option(form.DummyValue, 'version', _('Version'));
|
||||
o.cfgvalue = function () {
|
||||
var versionInfo = this.map.view.versionInfo || {};
|
||||
var luciVersion = versionInfo.luci_app_version || _('Unknown');
|
||||
var bandixVersion = versionInfo.bandix_version || _('Unknown');
|
||||
return 'luci-app-bandix: ' + luciVersion + ' / bandix: ' + bandixVersion;
|
||||
};
|
||||
|
||||
// 添加检查更新按钮
|
||||
var checkUpdateOption = s.option(form.Button, 'check_update', _('Check for Updates'));
|
||||
checkUpdateOption.inputtitle = _('Check for Updates');
|
||||
checkUpdateOption.inputstyle = 'apply';
|
||||
checkUpdateOption.onclick = function () {
|
||||
var button = this;
|
||||
var originalText = button.inputtitle;
|
||||
|
||||
// 显示加载状态
|
||||
button.inputtitle = _('Checking...');
|
||||
button.disabled = true;
|
||||
|
||||
return callCheckUpdate().then(function (result) {
|
||||
// 恢复按钮状态
|
||||
button.inputtitle = originalText;
|
||||
button.disabled = false;
|
||||
|
||||
if (!result) {
|
||||
ui.addNotification(null, E('p', _('Failed to check for updates')), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
var messages = [];
|
||||
var hasUpdate = false;
|
||||
|
||||
// 显示调试信息(架构、包管理器和资产数量)
|
||||
if (result.detected_arch) {
|
||||
var debugInfo = E('div', {
|
||||
'style': 'font-size: 0.75rem; color: #6b7280; margin-bottom: 12px; padding: 8px; background: rgba(0,0,0,0.02); border-radius: 4px;'
|
||||
}, [
|
||||
E('div', {}, _('Detected Architecture') + ': ' + result.detected_arch),
|
||||
E('div', {}, _('Package Manager') + ': ' + (result.detected_pkg_manager || _('Unknown'))),
|
||||
E('div', {}, _('LuCI Assets') + ': ' + (result.luci_asset_count || 0)),
|
||||
E('div', {}, _('Bandix Assets') + ': ' + (result.bandix_asset_count || 0))
|
||||
]);
|
||||
messages.push(debugInfo);
|
||||
}
|
||||
|
||||
// 检查 luci-app-bandix 更新
|
||||
if (result.luci_has_update === true || result.luci_has_update === '1' || result.luci_has_update === 1) {
|
||||
hasUpdate = true;
|
||||
var luciContainer = E('div', { 'style': 'margin-bottom: 20px;' });
|
||||
|
||||
var luciMsg = E('p', { 'style': 'font-weight: 600; margin-bottom: 8px;' },
|
||||
_('LuCI App has update: ') + (result.current_luci_version || _('Unknown')) +
|
||||
' → ' + (result.latest_luci_version || _('Unknown'))
|
||||
);
|
||||
luciContainer.appendChild(luciMsg);
|
||||
|
||||
// 显示更新日志(解析 Markdown)
|
||||
if (result.luci_release_body) {
|
||||
var changelogDiv = E('div', {
|
||||
'style': 'background: rgba(0,0,0,0.05); padding: 12px; border-radius: 4px; margin: 8px 0; max-height: 300px; overflow-y: auto; font-size: 0.875rem; line-height: 1.6;'
|
||||
});
|
||||
changelogDiv.innerHTML = parseMarkdown(result.luci_release_body);
|
||||
luciContainer.appendChild(changelogDiv);
|
||||
}
|
||||
|
||||
// 检查是否有错误(找不到匹配格式的包)
|
||||
if (result.luci_error) {
|
||||
var errorMsg = E('div', {
|
||||
'style': 'background: rgba(239, 68, 68, 0.1); color: #dc2626; padding: 10px; border-radius: 4px; margin: 8px 0; font-size: 0.875rem;'
|
||||
}, result.luci_error);
|
||||
luciContainer.appendChild(errorMsg);
|
||||
}
|
||||
|
||||
// 添加下载安装按钮(只有在有下载链接时才显示)
|
||||
if (result.luci_download_url) {
|
||||
// 创建状态提示区域
|
||||
var statusDiv = E('div', {
|
||||
'style': 'margin-top: 8px; padding: 10px; border-radius: 4px; display: none;'
|
||||
});
|
||||
luciContainer.appendChild(statusDiv);
|
||||
|
||||
var installBtn = E('button', {
|
||||
'class': 'btn cbi-button-action',
|
||||
'style': 'margin-top: 8px;',
|
||||
'click': function () {
|
||||
var installButton = this;
|
||||
var originalBtnText = installButton.textContent;
|
||||
|
||||
installButton.textContent = _('Installing...');
|
||||
installButton.disabled = true;
|
||||
|
||||
// 显示开始安装的提示
|
||||
statusDiv.style.display = 'block';
|
||||
statusDiv.style.background = 'rgba(59, 130, 246, 0.1)';
|
||||
statusDiv.style.color = '#1e40af';
|
||||
statusDiv.textContent = _('Starting installation... The page will refresh automatically in 30 seconds.');
|
||||
|
||||
// 对于 luci-app-bandix 安装,由于会重启 uhttpd/rpcd 导致连接断开,
|
||||
// 不等待响应,固定 30 秒后刷新页面,避免刷新过早未完成安装
|
||||
callInstallUpdate('luci', result.luci_download_url).catch(function (err) {
|
||||
});
|
||||
|
||||
setTimeout(function () {
|
||||
window.location.reload();
|
||||
}, 30000);
|
||||
}
|
||||
}, _('Download and Install'));
|
||||
luciContainer.appendChild(installBtn);
|
||||
}
|
||||
|
||||
// 添加手动下载链接
|
||||
if (result.luci_update_url) {
|
||||
var manualLink = E('a', {
|
||||
'href': result.luci_update_url,
|
||||
'target': '_blank',
|
||||
'style': 'display: inline-block; margin-left: 8px; margin-top: 8px;'
|
||||
}, _('Manual Download'));
|
||||
luciContainer.appendChild(manualLink);
|
||||
}
|
||||
|
||||
messages.push(luciContainer);
|
||||
} else {
|
||||
messages.push(E('p', {}, _('LuCI App is up to date: ') + (result.current_luci_version || result.latest_luci_version || _('Unknown'))));
|
||||
}
|
||||
|
||||
// 检查 bandix 更新
|
||||
if (result.bandix_has_update === true || result.bandix_has_update === '1' || result.bandix_has_update === 1) {
|
||||
hasUpdate = true;
|
||||
var bandixContainer = E('div', { 'style': 'margin-bottom: 20px;' });
|
||||
|
||||
var bandixMsg = E('p', { 'style': 'font-weight: 600; margin-bottom: 8px;' },
|
||||
_('Bandix has update: ') + (result.current_bandix_version || _('Unknown')) +
|
||||
' → ' + (result.latest_bandix_version || _('Unknown'))
|
||||
);
|
||||
bandixContainer.appendChild(bandixMsg);
|
||||
|
||||
// 显示更新日志(解析 Markdown)
|
||||
if (result.bandix_release_body) {
|
||||
var changelogDiv = E('div', {
|
||||
'style': 'background: rgba(0,0,0,0.05); padding: 12px; border-radius: 4px; margin: 8px 0; max-height: 300px; overflow-y: auto; font-size: 0.875rem; line-height: 1.6;'
|
||||
});
|
||||
changelogDiv.innerHTML = parseMarkdown(result.bandix_release_body);
|
||||
bandixContainer.appendChild(changelogDiv);
|
||||
}
|
||||
|
||||
// 检查是否有错误(找不到匹配格式的包)
|
||||
if (result.bandix_error) {
|
||||
var errorMsg = E('div', {
|
||||
'style': 'background: rgba(239, 68, 68, 0.1); color: #dc2626; padding: 10px; border-radius: 4px; margin: 8px 0; font-size: 0.875rem;'
|
||||
}, result.bandix_error);
|
||||
bandixContainer.appendChild(errorMsg);
|
||||
}
|
||||
|
||||
// 添加下载安装按钮(只有在有下载链接时才显示)
|
||||
if (result.bandix_download_url) {
|
||||
// 创建状态提示区域
|
||||
var statusDiv = E('div', {
|
||||
'style': 'margin-top: 8px; padding: 10px; border-radius: 4px; display: none;'
|
||||
});
|
||||
bandixContainer.appendChild(statusDiv);
|
||||
|
||||
var installBtn = E('button', {
|
||||
'class': 'btn cbi-button-action',
|
||||
'style': 'margin-top: 8px;',
|
||||
'click': function () {
|
||||
var installButton = this;
|
||||
var originalBtnText = installButton.textContent;
|
||||
|
||||
installButton.textContent = _('Installing...');
|
||||
installButton.disabled = true;
|
||||
|
||||
// 显示开始安装的提示
|
||||
statusDiv.style.display = 'block';
|
||||
statusDiv.style.background = 'rgba(59, 130, 246, 0.1)';
|
||||
statusDiv.style.color = '#1e40af';
|
||||
statusDiv.textContent = _('Starting installation...');
|
||||
|
||||
return callInstallUpdate('bandix', result.bandix_download_url).then(function (installResult) {
|
||||
// 判断成功:success 为 true 或 step 为 completed
|
||||
var isSuccess = installResult && (
|
||||
installResult.success === true ||
|
||||
installResult.success === 1 ||
|
||||
installResult.success === "true" ||
|
||||
installResult.success === "1" ||
|
||||
installResult.step === "completed"
|
||||
);
|
||||
|
||||
if (isSuccess) {
|
||||
installButton.textContent = originalBtnText;
|
||||
installButton.disabled = false;
|
||||
// 显示成功提示
|
||||
var successMsg = _('Bandix updated successfully!');
|
||||
if (installResult.message) {
|
||||
successMsg = installResult.message;
|
||||
}
|
||||
if (installResult.restart_exit_code !== undefined) {
|
||||
if (installResult.restart_exit_code === 0) {
|
||||
successMsg += ' ' + _('Service restarted successfully.');
|
||||
} else {
|
||||
successMsg += ' ' + _('Service restart may have failed. Please check manually.');
|
||||
}
|
||||
} else {
|
||||
successMsg += ' ' + _('Please restart the service.');
|
||||
}
|
||||
statusDiv.style.background = 'rgba(34, 197, 94, 0.1)';
|
||||
statusDiv.style.color = '#15803d';
|
||||
statusDiv.textContent = successMsg + ' ' + _('Page will refresh in 3 seconds.');
|
||||
// 3秒后刷新页面
|
||||
setTimeout(function () {
|
||||
window.location.reload();
|
||||
}, 3000);
|
||||
} else {
|
||||
installButton.textContent = originalBtnText;
|
||||
installButton.disabled = false;
|
||||
var errorMsg = installResult && installResult.error ? installResult.error : _('Installation failed');
|
||||
if (installResult && installResult.step) {
|
||||
errorMsg = _('Installation failed at step: ') + installResult.step + '. ' + errorMsg;
|
||||
}
|
||||
if (installResult && installResult.output) {
|
||||
// 截取输出信息的前500个字符,避免提示过长
|
||||
var output = installResult.output;
|
||||
if (output.length > 500) {
|
||||
output = output.substring(0, 500) + '...';
|
||||
}
|
||||
errorMsg += '\n' + _('Details: ') + output;
|
||||
}
|
||||
statusDiv.style.background = 'rgba(239, 68, 68, 0.1)';
|
||||
statusDiv.style.color = '#dc2626';
|
||||
statusDiv.innerHTML = errorMsg.replace(/\n/g, '<br>');
|
||||
}
|
||||
}).catch(function (err) {
|
||||
installButton.textContent = originalBtnText;
|
||||
installButton.disabled = false;
|
||||
var errorMsg = _('Installation failed: ') + (err.message || err || _('Unknown error'));
|
||||
statusDiv.style.background = 'rgba(239, 68, 68, 0.1)';
|
||||
statusDiv.style.color = '#dc2626';
|
||||
statusDiv.textContent = errorMsg;
|
||||
});
|
||||
}
|
||||
}, _('Download and Install'));
|
||||
bandixContainer.appendChild(installBtn);
|
||||
}
|
||||
|
||||
// 添加手动下载链接
|
||||
if (result.bandix_update_url) {
|
||||
var manualLink = E('a', {
|
||||
'href': result.bandix_update_url,
|
||||
'target': '_blank',
|
||||
'style': 'display: inline-block; margin-left: 8px; margin-top: 8px;'
|
||||
}, _('Manual Download'));
|
||||
bandixContainer.appendChild(manualLink);
|
||||
}
|
||||
|
||||
messages.push(bandixContainer);
|
||||
} else {
|
||||
messages.push(E('p', {}, _('Bandix is up to date: ') + (result.current_bandix_version || result.latest_bandix_version || _('Unknown'))));
|
||||
}
|
||||
|
||||
// 显示结果(添加关闭按钮)
|
||||
var title = hasUpdate ? _('Updates Available') : _('No Updates Available');
|
||||
|
||||
// 添加红色提示文字(如果有更新)
|
||||
if (hasUpdate) {
|
||||
var warningMsg = E('div', {
|
||||
'style': 'background: rgba(239, 68, 68, 0.1); color: #dc2626; padding: 12px; border-radius: 4px; margin-bottom: 16px; font-weight: 600;'
|
||||
}, _('Please clear your browser cache manually after updating.'));
|
||||
messages.unshift(warningMsg);
|
||||
}
|
||||
|
||||
// 创建弹窗内容,包含关闭按钮
|
||||
var modalContent = E('div', {}, messages);
|
||||
|
||||
// 添加关闭按钮
|
||||
var closeBtn = E('button', {
|
||||
'class': 'btn',
|
||||
'style': 'margin-top: 16px;',
|
||||
'click': ui.hideModal
|
||||
}, _('Close'));
|
||||
|
||||
modalContent.appendChild(closeBtn);
|
||||
|
||||
ui.showModal(title, modalContent);
|
||||
}).catch(function (err) {
|
||||
// 恢复按钮状态
|
||||
button.inputtitle = originalText;
|
||||
button.disabled = false;
|
||||
ui.addNotification(null, E('p', _('Failed to check for updates: ') + (err.message || err)), 'error');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// 添加清空数据按钮
|
||||
o = s.option(form.Button, 'clear_data', _('Clear Traffic Data'));
|
||||
o.inputtitle = _('Clear Traffic Data');
|
||||
o.inputstyle = 'reset';
|
||||
o.onclick = function () {
|
||||
return ui.showModal(_('Clear Traffic Data'), [
|
||||
E('p', _('Are you sure you want to clear all traffic data? This action cannot be undone.')),
|
||||
E('div', { 'class': 'right' }, [
|
||||
E('button', {
|
||||
'class': 'btn',
|
||||
'click': ui.hideModal
|
||||
}, _('Cancel')),
|
||||
' ',
|
||||
E('button', {
|
||||
'class': 'btn cbi-button-negative',
|
||||
'click': function () {
|
||||
ui.hideModal();
|
||||
return callClearData()
|
||||
.then(function (result) {
|
||||
if (result && !result.success) {
|
||||
ui.addNotification(null, E('p', _('Failed to clear traffic data: ') + (result.error || 'Unknown error')), 'error');
|
||||
}
|
||||
})
|
||||
.catch(function (err) {
|
||||
ui.addNotification(null, E('p', _('Failed to clear traffic data: ') + err.message), 'error');
|
||||
});
|
||||
}
|
||||
}, _('Confirm'))
|
||||
])
|
||||
]);
|
||||
};
|
||||
|
||||
// 添加重启服务按钮
|
||||
o = s.option(form.Button, 'restart_service', _('Restart Service'));
|
||||
o.inputtitle = _('Restart Bandix Service');
|
||||
o.inputstyle = 'apply';
|
||||
o.onclick = function () {
|
||||
return ui.showModal(_('Restart Service'), [
|
||||
E('p', _('Are you sure you want to restart the Bandix service?')),
|
||||
E('div', { 'class': 'right' }, [
|
||||
E('button', {
|
||||
'class': 'btn',
|
||||
'click': ui.hideModal
|
||||
}, _('Cancel')),
|
||||
' ',
|
||||
E('button', {
|
||||
'class': 'btn cbi-button-action',
|
||||
'click': function () {
|
||||
ui.hideModal();
|
||||
return callRestartService()
|
||||
.then(function (result) {
|
||||
if (result && !result.success) {
|
||||
ui.addNotification(null, E('p', _('Failed to restart service: ') + (result.error || 'Unknown error')), 'error');
|
||||
}
|
||||
})
|
||||
.catch(function (err) {
|
||||
ui.addNotification(null, E('p', _('Failed to restart service: ') + err.message), 'error');
|
||||
});
|
||||
}
|
||||
}, _('Confirm'))
|
||||
])
|
||||
]);
|
||||
};
|
||||
|
||||
|
||||
// 添加意见反馈信息
|
||||
o = s.option(form.Button, 'feedback_info', _('Feedback'));
|
||||
o.inputtitle = _('Feedback');
|
||||
o.inputstyle = 'link';
|
||||
o.onclick = function () {
|
||||
window.open('https://github.com/timsaya', '_blank');
|
||||
return false;
|
||||
};
|
||||
|
||||
// 2. 流量监控设置部分 (traffic)
|
||||
s = m.section(form.NamedSection, 'traffic', 'traffic', _('Traffic Monitor Settings'));
|
||||
s.description = _('Configure traffic monitoring related parameters');
|
||||
s.addremove = false;
|
||||
|
||||
o = s.option(form.Flag, 'enabled', _('Enable Traffic Monitoring'),
|
||||
_('Enable Bandix Traffic Monitor Service'));
|
||||
o.default = '0';
|
||||
o.rmempty = false;
|
||||
|
||||
// 添加网速单位选择选项
|
||||
o = s.option(form.ListValue, 'speed_unit', _('Speed Units'),
|
||||
_('Select the speed display unit format'));
|
||||
o.value('bytes', _('Bytes Units (B/s, KB/s, MB/s)'));
|
||||
o.value('bits', _('Bits Units (bps, Kbps, Mbps)'));
|
||||
o.default = 'bytes';
|
||||
o.rmempty = false;
|
||||
|
||||
// 添加离线超时时间(秒)
|
||||
// o = s.option(form.Value, 'offline_timeout', _('Offline Timeout'),
|
||||
// _('Set the timeout for device offline detection (seconds). Devices inactive for longer than this time will be marked as offline'));
|
||||
// o.datatype = 'uinteger';
|
||||
// o.placeholder = '120';
|
||||
// o.default = '120';
|
||||
// o.rmempty = true;
|
||||
|
||||
// 添加持久化历史数据选项
|
||||
o = s.option(form.Flag, 'traffic_enable_storage', _('Persist History Data'),
|
||||
_('Enable data persistence functionality, data will only be persisted to disk when this option is enabled'));
|
||||
o.default = '0';
|
||||
o.rmempty = false;
|
||||
|
||||
// 添加数据 flush 间隔(秒)
|
||||
o = s.option(form.ListValue, 'traffic_flush_interval', _('Data Flush Interval'),
|
||||
_('Set the interval for flushing data to disk'));
|
||||
o.value('60', _('1 minute'));
|
||||
o.value('300', _('5 minutes'));
|
||||
o.value('600', _('10 minutes'));
|
||||
o.value('900', _('15 minutes'));
|
||||
o.value('1800', _('30 minutes'));
|
||||
o.value('3600', _('1 hour'));
|
||||
o.value('7200', _('2 hours'));
|
||||
o.value('43200', _('12 hours'));
|
||||
o.value('86400', _('24 hours'));
|
||||
o.default = '600';
|
||||
o.rmempty = false;
|
||||
o.depends('traffic_enable_storage', '1');
|
||||
|
||||
o = s.option(form.Flag, 'traffic_neighbor_flush_enable', _('Enable Neighbor Flush'),
|
||||
_('Enable periodic flush of neighbor table data. Generally not recommended if the device list works normally.'));
|
||||
o.default = '0';
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.option(form.ListValue, 'traffic_neighbor_flush_interval', _('Neighbor Flush Interval'),
|
||||
_('Set the interval for flushing neighbor table data'));
|
||||
o.value('600', _('10 minutes'));
|
||||
o.value('900', _('15 minutes'));
|
||||
o.value('1800', _('30 minutes'));
|
||||
o.value('3600', _('1 hour'));
|
||||
o.value('7200', _('2 hours'));
|
||||
o.value('43200', _('12 hours'));
|
||||
o.value('86400', _('24 hours'));
|
||||
o.default = '600';
|
||||
o.rmempty = false;
|
||||
o.depends('traffic_neighbor_flush_enable', '1');
|
||||
|
||||
// 添加历史流量周期(秒)
|
||||
o = s.option(form.ListValue, 'traffic_realtime_window', _('Realtime Traffic Period'),
|
||||
_('Does not occupy storage space, stored only in memory'));
|
||||
o.value('600', _('10 minutes'));
|
||||
o.value('900', _('15 minutes'));
|
||||
o.value('1800', _('30 minutes'));
|
||||
o.value('3600', _('1 hour'));
|
||||
o.default = '600';
|
||||
o.rmempty = false;
|
||||
|
||||
// 添加流量导出URL选项
|
||||
o = s.option(form.Value, 'traffic_export_url', _('Traffic Push URL'),
|
||||
_('Push traffic data to a remote HTTP endpoint (POST JSON). Leave empty to disable.'));
|
||||
o.datatype = 'string';
|
||||
o.placeholder = '';
|
||||
o.rmempty = true;
|
||||
|
||||
// 添加设备事件导出URL选项
|
||||
o = s.option(form.Value, 'traffic_event_url', _('Device Event Push URL'),
|
||||
_('Push device online/offline events to a remote HTTP endpoint (POST JSON on state change). Leave empty to disable.'));
|
||||
o.datatype = 'string';
|
||||
o.placeholder = '';
|
||||
o.rmempty = true;
|
||||
|
||||
// 添加额外子网选项
|
||||
o = s.option(form.Value, 'traffic_additional_subnets', _('Additional Subnets'),
|
||||
_('Additional local subnets (comma-separated CIDR notation, e.g. \'192.168.2.0/24,10.0.0.0/8\'). Leave empty to use only interface subnet.'));
|
||||
o.datatype = 'string';
|
||||
o.placeholder = '192.168.2.0/24,10.0.0.0/8';
|
||||
o.rmempty = true;
|
||||
|
||||
o = s.option(form.Flag, 'traffic_exclude_iface_device', _('Exclude Interface Device'),
|
||||
_('Exclude the router itself (interface device) from traffic statistics'));
|
||||
o.default = '0';
|
||||
o.rmempty = false;
|
||||
|
||||
// 3. 连接监控设置部分 (connections)
|
||||
s = m.section(form.NamedSection, 'connections', 'connections', _('Connection Monitor Settings'));
|
||||
s.description = _('Configure connection monitoring related parameters');
|
||||
s.addremove = false;
|
||||
|
||||
o = s.option(form.Flag, 'enabled', _('Enable Connection Monitoring'),
|
||||
_('Enable Bandix connection monitoring'));
|
||||
o.default = '0';
|
||||
o.rmempty = false;
|
||||
|
||||
// 4. DNS监控设置部分 (dns)
|
||||
s = m.section(form.NamedSection, 'dns', 'dns', _('DNS Monitor Settings'));
|
||||
s.description = _('Configure DNS monitoring related parameters');
|
||||
s.addremove = false;
|
||||
|
||||
o = s.option(form.Flag, 'enabled', _('Enable DNS Monitoring'),
|
||||
_('Enable Bandix DNS monitoring'));
|
||||
o.default = '0';
|
||||
o.rmempty = false;
|
||||
|
||||
// 添加DNS最大记录数选项
|
||||
o = s.option(form.Value, 'dns_max_records', _('DNS Max Records'),
|
||||
_('Set the maximum number of DNS query records to keep. Older records will be deleted when this limit is exceeded'));
|
||||
o.datatype = 'uinteger';
|
||||
o.placeholder = '10000';
|
||||
o.default = '10000';
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.option(form.Flag, 'dns_enable_storage', _('Persist DNS Data'),
|
||||
_('Enable DNS data persistence functionality, data will only be persisted to disk when this option is enabled'));
|
||||
o.default = '0';
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.option(form.ListValue, 'dns_flush_interval', _('DNS Data Flush Interval'),
|
||||
_('Set the interval for flushing DNS data to disk'));
|
||||
o.value('60', _('1 minute'));
|
||||
o.value('300', _('5 minutes'));
|
||||
o.value('600', _('10 minutes'));
|
||||
o.value('900', _('15 minutes'));
|
||||
o.value('1800', _('30 minutes'));
|
||||
o.value('3600', _('1 hour'));
|
||||
o.value('7200', _('2 hours'));
|
||||
o.value('43200', _('12 hours'));
|
||||
o.value('86400', _('24 hours'));
|
||||
o.default = '900';
|
||||
o.rmempty = false;
|
||||
o.depends('dns_enable_storage', '1');
|
||||
|
||||
// 将 view 实例关联到 form map,以便在 cfgvalue 中访问
|
||||
m.view = this;
|
||||
|
||||
return m.render();
|
||||
}
|
||||
});
|
||||
1257
luci-app-bandix/po/es/bandix.po
Normal file
1257
luci-app-bandix/po/es/bandix.po
Normal file
File diff suppressed because it is too large
Load Diff
1257
luci-app-bandix/po/fr/bandix.po
Normal file
1257
luci-app-bandix/po/fr/bandix.po
Normal file
File diff suppressed because it is too large
Load Diff
1257
luci-app-bandix/po/ja/bandix.po
Normal file
1257
luci-app-bandix/po/ja/bandix.po
Normal file
File diff suppressed because it is too large
Load Diff
1257
luci-app-bandix/po/pl/bandix.po
Normal file
1257
luci-app-bandix/po/pl/bandix.po
Normal file
File diff suppressed because it is too large
Load Diff
1257
luci-app-bandix/po/ru/bandix.po
Normal file
1257
luci-app-bandix/po/ru/bandix.po
Normal file
File diff suppressed because it is too large
Load Diff
1284
luci-app-bandix/po/zh_Hans/bandix.po
Normal file
1284
luci-app-bandix/po/zh_Hans/bandix.po
Normal file
File diff suppressed because it is too large
Load Diff
1257
luci-app-bandix/po/zh_Hant/bandix.po
Normal file
1257
luci-app-bandix/po/zh_Hant/bandix.po
Normal file
File diff suppressed because it is too large
Load Diff
1798
luci-app-bandix/root/usr/libexec/rpcd/luci.bandix
Executable file
1798
luci-app-bandix/root/usr/libexec/rpcd/luci.bandix
Executable file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,46 @@
|
||||
{
|
||||
"admin/network/bandix": {
|
||||
"title": "Bandix",
|
||||
"order": 90,
|
||||
"action": {
|
||||
"type": "alias",
|
||||
"path": "admin/network/bandix/index"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-bandix" ],
|
||||
"uci": { "bandix": true }
|
||||
}
|
||||
},
|
||||
"admin/network/bandix/index": {
|
||||
"title": "Status",
|
||||
"order": 10,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "bandix/index"
|
||||
}
|
||||
},
|
||||
"admin/network/bandix/connection": {
|
||||
"title": "Connections",
|
||||
"order": 15,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "bandix/connection"
|
||||
}
|
||||
},
|
||||
"admin/network/bandix/dns": {
|
||||
"title": "DNS",
|
||||
"order": 20,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "bandix/dns"
|
||||
}
|
||||
},
|
||||
"admin/network/bandix/settings": {
|
||||
"title": "Settings",
|
||||
"order": 25,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "bandix/settings"
|
||||
}
|
||||
}
|
||||
}
|
||||
12
luci-app-bandix/root/usr/share/luci/status/bandix.json
Normal file
12
luci-app-bandix/root/usr/share/luci/status/bandix.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"bandix": {
|
||||
"title": "Bandix 流量监控",
|
||||
"description": "局域网设备流量监控和统计服务",
|
||||
"instances": {
|
||||
"instance": {
|
||||
"type": "process",
|
||||
"init": "bandix"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
{
|
||||
"luci-app-bandix": {
|
||||
"description": "Grant access to bandix traffic monitoring",
|
||||
"read": {
|
||||
"ubus": {
|
||||
"luci.bandix": [
|
||||
"getStatus",
|
||||
"getMetrics",
|
||||
"getMetricsDay",
|
||||
"getMetricsWeek",
|
||||
"getMetricsMonth",
|
||||
"getConnection",
|
||||
"getConnectionFlows",
|
||||
"setHostname",
|
||||
"deleteDevice",
|
||||
"getDnsQueries",
|
||||
"getDnsStats",
|
||||
"getScheduleLimits",
|
||||
"setScheduleLimit",
|
||||
"deleteScheduleLimit",
|
||||
"clearData",
|
||||
"restartService",
|
||||
"getVersion",
|
||||
"getSystemArch",
|
||||
"checkUpdate",
|
||||
"installUpdate",
|
||||
"getTrafficUsageRanking",
|
||||
"getTrafficUsageIncrements",
|
||||
"getRateLimitWhitelist"
|
||||
]
|
||||
},
|
||||
"uci": [
|
||||
"bandix"
|
||||
]
|
||||
},
|
||||
"write": {
|
||||
"ubus": {
|
||||
"luci.bandix": [
|
||||
"getStatus",
|
||||
"getMetrics",
|
||||
"getMetricsDay",
|
||||
"getMetricsWeek",
|
||||
"getMetricsMonth",
|
||||
"getConnection",
|
||||
"getConnectionFlows",
|
||||
"setHostname",
|
||||
"deleteDevice",
|
||||
"getDnsQueries",
|
||||
"getDnsStats",
|
||||
"getScheduleLimits",
|
||||
"setScheduleLimit",
|
||||
"deleteScheduleLimit",
|
||||
"clearData",
|
||||
"restartService",
|
||||
"getVersion",
|
||||
"getSystemArch",
|
||||
"checkUpdate",
|
||||
"installUpdate",
|
||||
"getTrafficUsageRanking",
|
||||
"getTrafficUsageIncrements",
|
||||
"getRateLimitWhitelist",
|
||||
"getRateLimitWhitelistEnabled",
|
||||
"setRateLimitWhitelistEnabled",
|
||||
"addRateLimitWhitelist",
|
||||
"deleteRateLimitWhitelist",
|
||||
"setDefaultRateLimit",
|
||||
"updateScheduleLimit"
|
||||
]
|
||||
},
|
||||
"uci": [
|
||||
"bandix"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
31
luci-app-banmac-ipt/Makefile
Normal file
31
luci-app-banmac-ipt/Makefile
Normal file
@ -0,0 +1,31 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-banmac-ipt
|
||||
PKG_VERSION:=20211118
|
||||
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/luci-app-banmac-ipt
|
||||
SECTION:=luci
|
||||
CATEGORY:=LuCI
|
||||
SUBMENU:=3. Applications
|
||||
TITLE:=LuCI Support for Ban Client
|
||||
PKGARCH:=all
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/luci-app-banmac-ipt/install
|
||||
$(INSTALL_DIR) $(1)/usr/lib/lua/luci
|
||||
cp -pR ./luasrc/* $(1)/usr/lib/lua/luci
|
||||
$(INSTALL_DIR) $(1)/usr/banmac
|
||||
$(INSTALL_BIN) ./files/banmac/* $(1)/usr/banmac
|
||||
$(INSTALL_DIR) $(1)/etc
|
||||
$(INSTALL_CONF) ./files/banmaclog $(1)/etc/banmaclog
|
||||
$(INSTALL_DIR) $(1)/etc/config
|
||||
$(INSTALL_CONF) ./files/banmac.conf $(1)/etc/config/banmac
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,luci-app-banmac-ipt))
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user