update 2026-05-27 23:25:57

This commit is contained in:
action 2026-05-27 23:25:57 +08:00
parent 84f707b52b
commit d0159b49a5
759 changed files with 362365 additions and 0 deletions

View 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))

View 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-----

View 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-----

View 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-----

View 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

View File

@ -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

View 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
View 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
View 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
View 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

Binary file not shown.

10
alist/src/public/dist/assets/logo.svg vendored Normal file
View 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
View 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
View 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))

View File

@ -0,0 +1,3 @@
#!/bin/sh
[ -d /usr/lib/cups/filter ] && chmod 0754 /usr/lib/cups/filter/*

45
brook/Makefile Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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))

View 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
View 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
View 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
View 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
}

View 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

View 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
View 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
View 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
View 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
View 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
View 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))

View 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
View 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
View 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
View File

@ -0,0 +1,6 @@
config gost 'config'
option enabled '0'
option config_file ''
#list arguments ''

46
gost/files/gost.init Normal file
View 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
View 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
View 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))

View File

@ -0,0 +1,3 @@
config webdav 'config'
option enabled '0'

View 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
}

View 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
View 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
View 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))

View 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)))

View File

@ -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

View 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)))

View File

@ -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

View 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)))

View File

@ -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

View File

@ -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" ]
}
}
}
}

View File

@ -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 };

View 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)))

View 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'

View 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"
}

View File

@ -0,0 +1 @@
# Shell commands to run when disconnected from the Internet

View File

@ -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.

View File

@ -0,0 +1 @@
# Shell commands that are run regularly

View File

@ -0,0 +1 @@
# Shell commands that run when connected to the Internet

View 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)

View File

@ -0,0 +1 @@
return require("internet-detector.main")

View 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
View 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
View 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
View 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/

View 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

View File

@ -0,0 +1,2 @@
config kismet_drone
option enabled 0

26
kismet/files/kismet_drone.init Executable file
View 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}"
}

View File

@ -0,0 +1,2 @@
config kismet_server
option enabled 0

26
kismet/files/kismet_server.init Executable file
View 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}"
}

View 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

View 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

View 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;

View 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
View 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))

View 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
View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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();
}
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
}
}

View File

@ -0,0 +1,12 @@
{
"bandix": {
"title": "Bandix 流量监控",
"description": "局域网设备流量监控和统计服务",
"instances": {
"instance": {
"type": "process",
"init": "bandix"
}
}
}
}

View File

@ -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"
]
}
}
}

View 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