mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-15 04:53:00 +08:00
✨ Sync 2026-11-11 01:22:09
This commit is contained in:
1 parent
9c5aa2e662
commit
cc21462d8a
48 files changed
+9758
-57
No files matched your search
+6
-2
@@ -1,8 +1,8 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=fancontrol
|
PKG_NAME:=fancontrol
|
||||||
PKG_VERSION:=2.0.0
|
PKG_VERSION:=2.2.0
|
||||||
PKG_RELEASE:=2
|
PKG_RELEASE:=3
|
||||||
|
|
||||||
PKG_LICENSE:=MIT
|
PKG_LICENSE:=MIT
|
||||||
PKG_LICENSE_FILES:=LICENSE
|
PKG_LICENSE_FILES:=LICENSE
|
||||||
@@ -22,6 +22,10 @@ define Package/fancontrol/description
|
|||||||
Temperature-based continuous PWM fan controller using the Linux hwmon ABI.
|
Temperature-based continuous PWM fan controller using the Linux hwmon ABI.
|
||||||
endef
|
endef
|
||||||
|
|
||||||
|
define Package/fancontrol/conffiles
|
||||||
|
/etc/config/fancontrol
|
||||||
|
endef
|
||||||
|
|
||||||
define Build/Prepare
|
define Build/Prepare
|
||||||
mkdir -p $(PKG_BUILD_DIR)
|
mkdir -p $(PKG_BUILD_DIR)
|
||||||
$(CP) ./src/Makefile ./src/fancontrol.c $(PKG_BUILD_DIR)
|
$(CP) ./src/Makefile ./src/fancontrol.c $(PKG_BUILD_DIR)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ config fancontrol 'settings'
|
|||||||
option fan_file 'auto'
|
option fan_file 'auto'
|
||||||
option fan_hwmon 'auto'
|
option fan_hwmon 'auto'
|
||||||
option enable_file 'auto'
|
option enable_file 'auto'
|
||||||
|
option cooling_device 'auto'
|
||||||
option start_temp '45'
|
option start_temp '45'
|
||||||
option full_speed_temp '85'
|
option full_speed_temp '85'
|
||||||
option hysteresis '3'
|
option hysteresis '3'
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ start_service() {
|
|||||||
append_option fan_file -F
|
append_option fan_file -F
|
||||||
append_option fan_hwmon -N
|
append_option fan_hwmon -N
|
||||||
append_option enable_file -E
|
append_option enable_file -E
|
||||||
|
append_option cooling_device -C
|
||||||
append_option start_temp -t
|
append_option start_temp -t
|
||||||
append_option full_speed_temp -x
|
append_option full_speed_temp -x
|
||||||
append_option hysteresis -H
|
append_option hysteresis -H
|
||||||
|
|||||||
+385
-9
@@ -16,11 +16,12 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#define PROGRAM_NAME "fancontrol"
|
#define PROGRAM_NAME "fancontrol"
|
||||||
#define PROGRAM_VERSION "2.0.0"
|
#define PROGRAM_VERSION "2.2.0"
|
||||||
#define AUTO_VALUE "auto"
|
#define AUTO_VALUE "auto"
|
||||||
#define DEFAULT_SYSFS_ROOT "/sys"
|
#define DEFAULT_SYSFS_ROOT "/sys"
|
||||||
#define DEFAULT_STATUS_FILE "/var/run/fancontrol.status"
|
#define DEFAULT_STATUS_FILE "/var/run/fancontrol.status"
|
||||||
#define MAX_HWMON_CHANNELS 32
|
#define MAX_HWMON_CHANNELS 32
|
||||||
|
#define THERMAL_TEMP_INVALID (-274000)
|
||||||
|
|
||||||
struct fan_config {
|
struct fan_config {
|
||||||
char thermal_file[PATH_MAX];
|
char thermal_file[PATH_MAX];
|
||||||
@@ -28,6 +29,7 @@ struct fan_config {
|
|||||||
char fan_file[PATH_MAX];
|
char fan_file[PATH_MAX];
|
||||||
char fan_hwmon[128];
|
char fan_hwmon[128];
|
||||||
char enable_file[PATH_MAX];
|
char enable_file[PATH_MAX];
|
||||||
|
char cooling_device[PATH_MAX];
|
||||||
int start_temp;
|
int start_temp;
|
||||||
int full_speed_temp;
|
int full_speed_temp;
|
||||||
int hysteresis;
|
int hysteresis;
|
||||||
@@ -43,11 +45,21 @@ struct fan_config {
|
|||||||
bool once;
|
bool once;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct kernel_trip {
|
||||||
|
char path[PATH_MAX];
|
||||||
|
int temperature;
|
||||||
|
};
|
||||||
|
|
||||||
struct fan_runtime {
|
struct fan_runtime {
|
||||||
char thermal_file[PATH_MAX];
|
char thermal_file[PATH_MAX];
|
||||||
char fan_file[PATH_MAX];
|
char fan_file[PATH_MAX];
|
||||||
char enable_file[PATH_MAX];
|
char enable_file[PATH_MAX];
|
||||||
char rpm_file[PATH_MAX];
|
char rpm_file[PATH_MAX];
|
||||||
|
char kernel_state_file[PATH_MAX];
|
||||||
|
struct kernel_trip *kernel_trips;
|
||||||
|
size_t kernel_trip_count;
|
||||||
|
int kernel_lock_fd;
|
||||||
|
bool kernel_state_saved;
|
||||||
bool fan_active;
|
bool fan_active;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -64,6 +76,7 @@ static void usage(FILE *stream, const char *name)
|
|||||||
" -F path PWM sysfs path or 'auto'\n"
|
" -F path PWM sysfs path or 'auto'\n"
|
||||||
" -N name preferred hwmon name or 'auto'\n"
|
" -N name preferred hwmon name or 'auto'\n"
|
||||||
" -E path pwm enable sysfs path or 'auto'\n"
|
" -E path pwm enable sysfs path or 'auto'\n"
|
||||||
|
" -C path kernel cooling device, 'auto' or 'none'\n"
|
||||||
" -t degrees fan start temperature\n"
|
" -t degrees fan start temperature\n"
|
||||||
" -x degrees full-speed temperature\n"
|
" -x degrees full-speed temperature\n"
|
||||||
" -H degrees stop hysteresis\n"
|
" -H degrees stop hysteresis\n"
|
||||||
@@ -169,7 +182,7 @@ static int write_long_file(const char *path, int value)
|
|||||||
if (length < 0 || (size_t)length >= sizeof(buffer))
|
if (length < 0 || (size_t)length >= sizeof(buffer))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
fd = open(path, O_WRONLY | O_CLOEXEC);
|
fd = open(path, O_WRONLY | O_TRUNC | O_CLOEXEC);
|
||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
@@ -443,6 +456,7 @@ static int resolve_paths(const struct fan_config *config,
|
|||||||
struct fan_runtime *runtime)
|
struct fan_runtime *runtime)
|
||||||
{
|
{
|
||||||
memset(runtime, 0, sizeof(*runtime));
|
memset(runtime, 0, sizeof(*runtime));
|
||||||
|
runtime->kernel_lock_fd = -1;
|
||||||
copy_string(runtime->enable_file, sizeof(runtime->enable_file),
|
copy_string(runtime->enable_file, sizeof(runtime->enable_file),
|
||||||
config->enable_file);
|
config->enable_file);
|
||||||
|
|
||||||
@@ -497,6 +511,336 @@ static int resolve_paths(const struct fan_config *config,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int append_kernel_trip(struct fan_runtime *runtime, const char *path,
|
||||||
|
int temperature)
|
||||||
|
{
|
||||||
|
struct kernel_trip *trips;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < runtime->kernel_trip_count; i++)
|
||||||
|
if (strcmp(runtime->kernel_trips[i].path, path) == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
trips = realloc(runtime->kernel_trips,
|
||||||
|
(runtime->kernel_trip_count + 1) * sizeof(*trips));
|
||||||
|
if (!trips)
|
||||||
|
return -1;
|
||||||
|
runtime->kernel_trips = trips;
|
||||||
|
if (copy_string(trips[runtime->kernel_trip_count].path, PATH_MAX, path))
|
||||||
|
return -1;
|
||||||
|
trips[runtime->kernel_trip_count++].temperature = temperature;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int restore_kernel_trips(struct fan_runtime *runtime)
|
||||||
|
{
|
||||||
|
bool failed = false;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
if (!runtime->kernel_state_saved)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
for (i = 0; i < runtime->kernel_trip_count; i++) {
|
||||||
|
const struct kernel_trip *trip = &runtime->kernel_trips[i];
|
||||||
|
long current;
|
||||||
|
|
||||||
|
if (read_long_file(trip->path, ¤t)) {
|
||||||
|
if (errno == ENOENT)
|
||||||
|
continue;
|
||||||
|
} else if (current != THERMAL_TEMP_INVALID) {
|
||||||
|
/* Preserve changes made by another administrator. */
|
||||||
|
continue;
|
||||||
|
} else if (!write_long_file(trip->path, trip->temperature)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
syslog(LOG_ERR, "failed to restore kernel fan trip %s: %s",
|
||||||
|
trip->path, strerror(errno));
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
|
if (failed || (unlink(runtime->kernel_state_file) && errno != ENOENT))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
free(runtime->kernel_trips);
|
||||||
|
runtime->kernel_trips = NULL;
|
||||||
|
runtime->kernel_trip_count = 0;
|
||||||
|
runtime->kernel_state_saved = false;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int load_kernel_state(struct fan_runtime *runtime)
|
||||||
|
{
|
||||||
|
char line[PATH_MAX + 64];
|
||||||
|
FILE *stream;
|
||||||
|
int result = 0;
|
||||||
|
|
||||||
|
stream = fopen(runtime->kernel_state_file, "r");
|
||||||
|
if (!stream)
|
||||||
|
return errno == ENOENT ? 0 : -1;
|
||||||
|
|
||||||
|
while (fgets(line, sizeof(line), stream)) {
|
||||||
|
int temperature, offset = 0, zone, trip, length = 0;
|
||||||
|
const char *path;
|
||||||
|
size_t root_length = strlen(sysfs_root);
|
||||||
|
|
||||||
|
line[strcspn(line, "\r\n")] = '\0';
|
||||||
|
if (sscanf(line, "%d %n", &temperature, &offset) != 1 ||
|
||||||
|
temperature <= THERMAL_TEMP_INVALID)
|
||||||
|
goto invalid;
|
||||||
|
path = line + offset;
|
||||||
|
if (strncmp(path, sysfs_root, root_length) ||
|
||||||
|
sscanf(path + root_length,
|
||||||
|
"/class/thermal/thermal_zone%d/trip_point_%d_temp%n",
|
||||||
|
&zone, &trip, &length) != 2 ||
|
||||||
|
zone < 0 || trip < 0 || path[root_length + length] ||
|
||||||
|
append_kernel_trip(runtime, path, temperature))
|
||||||
|
goto invalid;
|
||||||
|
continue;
|
||||||
|
invalid:
|
||||||
|
syslog(LOG_ERR, "invalid kernel fan state in %s",
|
||||||
|
runtime->kernel_state_file);
|
||||||
|
result = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (ferror(stream))
|
||||||
|
result = -1;
|
||||||
|
fclose(stream);
|
||||||
|
if (!result)
|
||||||
|
runtime->kernel_state_saved = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int save_kernel_state(struct fan_runtime *runtime)
|
||||||
|
{
|
||||||
|
char temporary[PATH_MAX];
|
||||||
|
FILE *stream;
|
||||||
|
bool failed = false;
|
||||||
|
size_t i;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
if (snprintf(temporary, sizeof(temporary), "%s.%ld",
|
||||||
|
runtime->kernel_state_file, (long)getpid()) >= (int)sizeof(temporary))
|
||||||
|
return -1;
|
||||||
|
fd = open(temporary, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0600);
|
||||||
|
if (fd < 0)
|
||||||
|
return -1;
|
||||||
|
stream = fdopen(fd, "w");
|
||||||
|
if (!stream) {
|
||||||
|
close(fd);
|
||||||
|
unlink(temporary);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
for (i = 0; i < runtime->kernel_trip_count; i++)
|
||||||
|
if (fprintf(stream, "%d %s\n", runtime->kernel_trips[i].temperature,
|
||||||
|
runtime->kernel_trips[i].path) < 0)
|
||||||
|
failed = true;
|
||||||
|
if (fflush(stream) || fsync(fd))
|
||||||
|
failed = true;
|
||||||
|
if (fclose(stream))
|
||||||
|
failed = true;
|
||||||
|
if (failed || rename(temporary, runtime->kernel_state_file)) {
|
||||||
|
unlink(temporary);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
runtime->kernel_state_saved = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cooling devices have no hwmon/OF link in sysfs. Only infer a unique pair. */
|
||||||
|
static int find_kernel_cooling_device(const struct fan_config *config,
|
||||||
|
const struct fan_runtime *runtime,
|
||||||
|
char *result, size_t size)
|
||||||
|
{
|
||||||
|
char pattern[PATH_MAX], path[PATH_MAX], name[128];
|
||||||
|
glob_t paths = { 0 };
|
||||||
|
char *slash;
|
||||||
|
int fan_count = 0, cooling_count = 0;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
if (strcmp(config->cooling_device, "none") == 0)
|
||||||
|
return 0;
|
||||||
|
if (!is_auto(config->cooling_device))
|
||||||
|
return copy_string(result, size, config->cooling_device) ? -1 : 1;
|
||||||
|
|
||||||
|
if (copy_string(path, sizeof(path), runtime->fan_file))
|
||||||
|
return -1;
|
||||||
|
slash = strrchr(path, '/');
|
||||||
|
if (!slash || copy_string(slash + 1, sizeof(path) - (size_t)(slash + 1 - path),
|
||||||
|
"name") || read_text_file(path, name, sizeof(name)) ||
|
||||||
|
strcmp(name, "pwmfan"))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (snprintf(pattern, sizeof(pattern), "%s/class/hwmon/hwmon*/name",
|
||||||
|
sysfs_root) >= (int)sizeof(pattern))
|
||||||
|
return -1;
|
||||||
|
if (glob(pattern, 0, NULL, &paths) == 0) {
|
||||||
|
for (i = 0; i < paths.gl_pathc; i++)
|
||||||
|
if (!read_text_file(paths.gl_pathv[i], name, sizeof(name)) &&
|
||||||
|
strcmp(name, "pwmfan") == 0)
|
||||||
|
fan_count++;
|
||||||
|
}
|
||||||
|
globfree(&paths);
|
||||||
|
memset(&paths, 0, sizeof(paths));
|
||||||
|
if (snprintf(pattern, sizeof(pattern), "%s/class/thermal/cooling_device*/type",
|
||||||
|
sysfs_root) >= (int)sizeof(pattern))
|
||||||
|
return -1;
|
||||||
|
if (glob(pattern, 0, NULL, &paths) == 0) {
|
||||||
|
for (i = 0; i < paths.gl_pathc; i++) {
|
||||||
|
if (read_text_file(paths.gl_pathv[i], name, sizeof(name)) ||
|
||||||
|
strcmp(name, "pwm-fan"))
|
||||||
|
continue;
|
||||||
|
if (copy_string(result, size, paths.gl_pathv[i])) {
|
||||||
|
globfree(&paths);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
*strrchr(result, '/') = '\0';
|
||||||
|
cooling_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
globfree(&paths);
|
||||||
|
if (!cooling_count)
|
||||||
|
return 0;
|
||||||
|
if (fan_count != 1 || cooling_count != 1) {
|
||||||
|
syslog(LOG_ERR, "multiple PWM fans or cooling devices; select cooling_device explicitly");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_cooling_binding(const char *path)
|
||||||
|
{
|
||||||
|
const char *name = strrchr(path, '/');
|
||||||
|
int index;
|
||||||
|
char trailing;
|
||||||
|
|
||||||
|
return name && sscanf(name + 1, "cdev%d%c", &index, &trailing) == 1 && index >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int collect_kernel_trips(const char *cooling_device, struct fan_runtime *runtime)
|
||||||
|
{
|
||||||
|
char pattern[PATH_MAX], path[PATH_MAX], zone[PATH_MAX], type[128];
|
||||||
|
struct stat selected, bound;
|
||||||
|
glob_t bindings = { 0 };
|
||||||
|
int result = -1;
|
||||||
|
size_t i, j;
|
||||||
|
|
||||||
|
if (stat(cooling_device, &selected))
|
||||||
|
return -1;
|
||||||
|
if (snprintf(pattern, sizeof(pattern), "%s/class/thermal/thermal_zone*/cdev*",
|
||||||
|
sysfs_root) >= (int)sizeof(pattern))
|
||||||
|
return -1;
|
||||||
|
if (glob(pattern, 0, NULL, &bindings))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
for (i = 0; i < bindings.gl_pathc; i++) {
|
||||||
|
const char *binding = bindings.gl_pathv[i];
|
||||||
|
long trip, temperature;
|
||||||
|
|
||||||
|
if (!is_cooling_binding(binding) || stat(binding, &bound) ||
|
||||||
|
selected.st_dev != bound.st_dev || selected.st_ino != bound.st_ino)
|
||||||
|
continue;
|
||||||
|
if (snprintf(path, sizeof(path), "%s_trip_point", binding) >= (int)sizeof(path) ||
|
||||||
|
read_long_file(path, &trip) || trip < 0 || trip > INT_MAX ||
|
||||||
|
copy_string(zone, sizeof(zone), binding))
|
||||||
|
goto out;
|
||||||
|
*strrchr(zone, '/') = '\0';
|
||||||
|
if (snprintf(path, sizeof(path), "%s/policy", zone) >= (int)sizeof(path) ||
|
||||||
|
read_text_file(path, type, sizeof(type)))
|
||||||
|
goto out;
|
||||||
|
if (strcmp(type, "user_space") == 0)
|
||||||
|
continue;
|
||||||
|
if (strcmp(type, "step_wise")) {
|
||||||
|
syslog(LOG_ERR, "cannot take over fan from kernel governor %s in %s", type, zone);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (snprintf(path, sizeof(path), "%s/trip_point_%ld_type", zone, trip) >=
|
||||||
|
(int)sizeof(path) || read_text_file(path, type, sizeof(type)))
|
||||||
|
goto out;
|
||||||
|
if (strcmp(type, "active")) {
|
||||||
|
syslog(LOG_ERR, "refusing to disable %s fan trip in %s", type, zone);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
/* A trip may also control CPU frequency or another fan. Leave it alone. */
|
||||||
|
for (j = 0; j < bindings.gl_pathc; j++) {
|
||||||
|
const char *other = bindings.gl_pathv[j];
|
||||||
|
long other_trip;
|
||||||
|
|
||||||
|
if (!is_cooling_binding(other) || strncmp(other, zone, strlen(zone)) ||
|
||||||
|
other[strlen(zone)] != '/' || stat(other, &bound) ||
|
||||||
|
(selected.st_dev == bound.st_dev && selected.st_ino == bound.st_ino))
|
||||||
|
continue;
|
||||||
|
if (snprintf(path, sizeof(path), "%s_trip_point", other) >= (int)sizeof(path) ||
|
||||||
|
read_long_file(path, &other_trip))
|
||||||
|
goto out;
|
||||||
|
if (other_trip == trip) {
|
||||||
|
syslog(LOG_ERR, "fan trip %ld in %s is shared with another cooling device", trip, zone);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (snprintf(path, sizeof(path), "%s/trip_point_%ld_temp", zone, trip) >=
|
||||||
|
(int)sizeof(path) || read_long_file(path, &temperature))
|
||||||
|
goto out;
|
||||||
|
if (temperature == THERMAL_TEMP_INVALID)
|
||||||
|
continue;
|
||||||
|
if (temperature <= THERMAL_TEMP_INVALID || temperature > INT_MAX ||
|
||||||
|
access(path, W_OK) || append_kernel_trip(runtime, path, (int)temperature))
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
result = 0;
|
||||||
|
out:
|
||||||
|
globfree(&bindings);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int claim_kernel_fan_control(const struct fan_config *config,
|
||||||
|
struct fan_runtime *runtime)
|
||||||
|
{
|
||||||
|
char path[PATH_MAX];
|
||||||
|
struct flock lock = { .l_type = F_WRLCK, .l_whence = SEEK_SET };
|
||||||
|
int found;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
if (snprintf(runtime->kernel_state_file, sizeof(runtime->kernel_state_file),
|
||||||
|
"%s.thermal", status_file) >= (int)sizeof(runtime->kernel_state_file) ||
|
||||||
|
snprintf(path, sizeof(path), "%s.lock", status_file) >= (int)sizeof(path))
|
||||||
|
return -1;
|
||||||
|
runtime->kernel_lock_fd = open(path, O_RDWR | O_CREAT | O_CLOEXEC, 0600);
|
||||||
|
if (runtime->kernel_lock_fd < 0 || fcntl(runtime->kernel_lock_fd, F_SETLK, &lock)) {
|
||||||
|
syslog(LOG_ERR, "cannot lock controller state %s: %s", path, strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
/* Recover the original thresholds if an earlier process was killed. */
|
||||||
|
if (load_kernel_state(runtime) || restore_kernel_trips(runtime))
|
||||||
|
return -1;
|
||||||
|
found = find_kernel_cooling_device(config, runtime, path, sizeof(path));
|
||||||
|
if (found <= 0)
|
||||||
|
return found;
|
||||||
|
if (collect_kernel_trips(path, runtime))
|
||||||
|
return -1;
|
||||||
|
if (!runtime->kernel_trip_count)
|
||||||
|
return 0;
|
||||||
|
/* Persist every original value before changing any sysfs attributes. */
|
||||||
|
if (save_kernel_state(runtime))
|
||||||
|
return -1;
|
||||||
|
for (i = 0; i < runtime->kernel_trip_count; i++) {
|
||||||
|
long temperature;
|
||||||
|
|
||||||
|
if (write_long_file(runtime->kernel_trips[i].path, THERMAL_TEMP_INVALID)) {
|
||||||
|
syslog(LOG_ERR, "failed to pause kernel fan trip %s: %s",
|
||||||
|
runtime->kernel_trips[i].path, strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (read_long_file(runtime->kernel_trips[i].path, &temperature) ||
|
||||||
|
temperature != THERMAL_TEMP_INVALID) {
|
||||||
|
syslog(LOG_ERR, "kernel did not pause fan trip %s",
|
||||||
|
runtime->kernel_trips[i].path);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
syslog(LOG_NOTICE, "paused %zu kernel fan trips; hot, critical and passive trips remain active",
|
||||||
|
runtime->kernel_trip_count);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int calculate_pwm(const struct fan_config *config, int64_t temp_mc,
|
static int calculate_pwm(const struct fan_config *config, int64_t temp_mc,
|
||||||
bool fan_active)
|
bool fan_active)
|
||||||
{
|
{
|
||||||
@@ -572,7 +916,8 @@ static int install_signal_handlers(void)
|
|||||||
|
|
||||||
sigemptyset(&action.sa_mask);
|
sigemptyset(&action.sa_mask);
|
||||||
return sigaction(SIGINT, &action, NULL) ||
|
return sigaction(SIGINT, &action, NULL) ||
|
||||||
sigaction(SIGTERM, &action, NULL);
|
sigaction(SIGTERM, &action, NULL) ||
|
||||||
|
sigaction(SIGHUP, &action, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void set_defaults(struct fan_config *config)
|
static void set_defaults(struct fan_config *config)
|
||||||
@@ -583,6 +928,7 @@ static void set_defaults(struct fan_config *config)
|
|||||||
copy_string(config->fan_file, sizeof(config->fan_file), AUTO_VALUE);
|
copy_string(config->fan_file, sizeof(config->fan_file), AUTO_VALUE);
|
||||||
copy_string(config->fan_hwmon, sizeof(config->fan_hwmon), AUTO_VALUE);
|
copy_string(config->fan_hwmon, sizeof(config->fan_hwmon), AUTO_VALUE);
|
||||||
copy_string(config->enable_file, sizeof(config->enable_file), AUTO_VALUE);
|
copy_string(config->enable_file, sizeof(config->enable_file), AUTO_VALUE);
|
||||||
|
copy_string(config->cooling_device, sizeof(config->cooling_device), AUTO_VALUE);
|
||||||
config->start_temp = 45;
|
config->start_temp = 45;
|
||||||
config->full_speed_temp = 85;
|
config->full_speed_temp = 85;
|
||||||
config->hysteresis = 3;
|
config->hysteresis = 3;
|
||||||
@@ -600,7 +946,7 @@ static void parse_options(int argc, char **argv, struct fan_config *config)
|
|||||||
{
|
{
|
||||||
int option;
|
int option;
|
||||||
|
|
||||||
while ((option = getopt(argc, argv, "T:Z:F:N:E:t:x:H:s:m:k:K:f:q:d:i:D1vh")) != -1) {
|
while ((option = getopt(argc, argv, "T:Z:F:N:E:C:t:x:H:s:m:k:K:f:q:d:i:D1vh")) != -1) {
|
||||||
switch (option) {
|
switch (option) {
|
||||||
case 'T':
|
case 'T':
|
||||||
if (copy_string(config->thermal_file,
|
if (copy_string(config->thermal_file,
|
||||||
@@ -623,6 +969,10 @@ static void parse_options(int argc, char **argv, struct fan_config *config)
|
|||||||
if (copy_string(config->enable_file, sizeof(config->enable_file), optarg))
|
if (copy_string(config->enable_file, sizeof(config->enable_file), optarg))
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
break;
|
break;
|
||||||
|
case 'C':
|
||||||
|
if (copy_string(config->cooling_device, sizeof(config->cooling_device), optarg))
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
break;
|
||||||
case 't':
|
case 't':
|
||||||
config->start_temp = parse_int(optarg, -100, 200, "start temperature");
|
config->start_temp = parse_int(optarg, -100, 200, "start temperature");
|
||||||
break;
|
break;
|
||||||
@@ -700,6 +1050,7 @@ int main(int argc, char **argv)
|
|||||||
struct fan_runtime runtime;
|
struct fan_runtime runtime;
|
||||||
const char *environment;
|
const char *environment;
|
||||||
bool sensor_failed = false;
|
bool sensor_failed = false;
|
||||||
|
bool control_ready = false;
|
||||||
int exit_code = EXIT_SUCCESS;
|
int exit_code = EXIT_SUCCESS;
|
||||||
|
|
||||||
set_defaults(&config);
|
set_defaults(&config);
|
||||||
@@ -723,9 +1074,20 @@ int main(int argc, char **argv)
|
|||||||
if (resolve_paths(&config, &runtime))
|
if (resolve_paths(&config, &runtime))
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
|
|
||||||
if (runtime.enable_file[0] && write_long_file(runtime.enable_file, 1))
|
if (claim_kernel_fan_control(&config, &runtime)) {
|
||||||
|
syslog(LOG_ERR, "cannot safely take control of the fan from the kernel");
|
||||||
|
if (runtime.kernel_state_saved)
|
||||||
|
set_pwm(&runtime, config.fail_safe_pwm);
|
||||||
|
exit_code = EXIT_FAILURE;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
control_ready = true;
|
||||||
|
if (runtime.enable_file[0] && write_long_file(runtime.enable_file, 1)) {
|
||||||
syslog(LOG_WARNING, "failed to select manual PWM mode through %s: %s",
|
syslog(LOG_WARNING, "failed to select manual PWM mode through %s: %s",
|
||||||
runtime.enable_file, strerror(errno));
|
runtime.enable_file, strerror(errno));
|
||||||
|
exit_code = EXIT_FAILURE;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
syslog(LOG_NOTICE, "using temperature input %s and PWM output %s",
|
syslog(LOG_NOTICE, "using temperature input %s and PWM output %s",
|
||||||
runtime.thermal_file, runtime.fan_file);
|
runtime.thermal_file, runtime.fan_file);
|
||||||
@@ -739,8 +1101,10 @@ int main(int argc, char **argv)
|
|||||||
|
|
||||||
if (read_long_file(runtime.thermal_file, &raw_temp)) {
|
if (read_long_file(runtime.thermal_file, &raw_temp)) {
|
||||||
target_pwm = config.fail_safe_pwm;
|
target_pwm = config.fail_safe_pwm;
|
||||||
if (set_pwm(&runtime, target_pwm))
|
if (set_pwm(&runtime, target_pwm)) {
|
||||||
exit_code = EXIT_FAILURE;
|
exit_code = EXIT_FAILURE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
runtime.fan_active = target_pwm > 0;
|
runtime.fan_active = target_pwm > 0;
|
||||||
write_status(&runtime, temp_mc, target_pwm, rpm,
|
write_status(&runtime, temp_mc, target_pwm, rpm,
|
||||||
"temperature-read-failed");
|
"temperature-read-failed");
|
||||||
@@ -759,14 +1123,19 @@ int main(int argc, char **argv)
|
|||||||
|
|
||||||
if (target_pwm > 0 && !runtime.fan_active &&
|
if (target_pwm > 0 && !runtime.fan_active &&
|
||||||
config.kick_pwm > 0 && config.kick_ms > 0) {
|
config.kick_pwm > 0 && config.kick_ms > 0) {
|
||||||
if (!set_pwm(&runtime, config.kick_pwm))
|
if (set_pwm(&runtime, config.kick_pwm)) {
|
||||||
|
exit_code = EXIT_FAILURE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
sleep_ms((unsigned int)config.kick_ms);
|
sleep_ms((unsigned int)config.kick_ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!stop_requested &&
|
if (!stop_requested &&
|
||||||
(read_long_file(runtime.fan_file, &actual_pwm) ||
|
(read_long_file(runtime.fan_file, &actual_pwm) ||
|
||||||
actual_pwm != target_pwm) && set_pwm(&runtime, target_pwm))
|
actual_pwm != target_pwm) && set_pwm(&runtime, target_pwm)) {
|
||||||
exit_code = EXIT_FAILURE;
|
exit_code = EXIT_FAILURE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
runtime.fan_active = target_pwm > 0;
|
runtime.fan_active = target_pwm > 0;
|
||||||
if (runtime.rpm_file[0])
|
if (runtime.rpm_file[0])
|
||||||
@@ -783,8 +1152,15 @@ int main(int argc, char **argv)
|
|||||||
sleep_ms((unsigned int)config.interval * 1000);
|
sleep_ms((unsigned int)config.interval * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config.once && set_pwm(&runtime, config.exit_pwm))
|
cleanup:
|
||||||
|
if (control_ready && !config.once && set_pwm(&runtime, config.exit_pwm))
|
||||||
exit_code = EXIT_FAILURE;
|
exit_code = EXIT_FAILURE;
|
||||||
|
if (restore_kernel_trips(&runtime))
|
||||||
|
exit_code = EXIT_FAILURE;
|
||||||
|
free(runtime.kernel_trips);
|
||||||
|
if (runtime.kernel_lock_fd >= 0)
|
||||||
|
close(runtime.kernel_lock_fd);
|
||||||
|
if (control_ready)
|
||||||
unlink(status_file);
|
unlink(status_file);
|
||||||
closelog();
|
closelog();
|
||||||
return exit_code;
|
return exit_code;
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ PKG_LICENSE:=Apache-2.0
|
|||||||
PKG_MAINTAINER:=JiaY-shi <shi05275@163.com>
|
PKG_MAINTAINER:=JiaY-shi <shi05275@163.com>
|
||||||
|
|
||||||
PKG_NAME:=luci-app-fancontrol
|
PKG_NAME:=luci-app-fancontrol
|
||||||
PKG_VERSION:=2.0.0
|
PKG_VERSION:=2.2.0
|
||||||
PKG_RELEASE:=3
|
PKG_RELEASE:=4
|
||||||
|
|
||||||
include $(TOPDIR)/feeds/luci/luci.mk
|
include $(TOPDIR)/feeds/luci/luci.mk
|
||||||
# call BuildPackage - OpenWrt buildroot signature
|
# call BuildPackage - OpenWrt buildroot signature
|
||||||
@@ -406,6 +406,16 @@ return view.extend({
|
|||||||
o.validate = validateAutoPath;
|
o.validate = validateAutoPath;
|
||||||
o.description = _('Optional pwmN_enable path used to select manual PWM mode. With "auto", the matching control is used when available.');
|
o.description = _('Optional pwmN_enable path used to select manual PWM mode. With "auto", the matching control is used when available.');
|
||||||
|
|
||||||
|
o = s.taboption('hardware', form.Value, 'cooling_device', _('Kernel cooling device'));
|
||||||
|
o.default = 'auto';
|
||||||
|
o.value('auto');
|
||||||
|
o.value('none');
|
||||||
|
o.rmempty = false;
|
||||||
|
o.validate = function(sectionId, value) {
|
||||||
|
return value === 'none' ? true : validateAutoPath(sectionId, value);
|
||||||
|
};
|
||||||
|
o.description = _('Use auto for a single pwm-fan device, an explicit cooling_device path for multiple fans, or none when kernel fan control is already handled elsewhere.');
|
||||||
|
|
||||||
o = s.taboption('hardware', form.Value, 'temp_div', _('Temperature divisor'));
|
o = s.taboption('hardware', form.Value, 'temp_div', _('Temperature divisor'));
|
||||||
o.default = '1000';
|
o.default = '1000';
|
||||||
o.value('1');
|
o.value('1');
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: luci-app-fancontrol 2.0.0\n"
|
"Project-Id-Version: luci-app-fancontrol 2.2.0\n"
|
||||||
"PO-Revision-Date: 2026-07-30 00:00+0800\n"
|
"PO-Revision-Date: 2026-07-30 00:00+0800\n"
|
||||||
"Last-Translator: JiaY-shi <shi05275@163.com>\n"
|
"Last-Translator: JiaY-shi <shi05275@163.com>\n"
|
||||||
"Language-Team: Chinese (Simplified Han script)\n"
|
"Language-Team: Chinese (Simplified Han script)\n"
|
||||||
@@ -10,6 +10,12 @@ msgstr ""
|
|||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
|
|
||||||
|
msgid "Kernel cooling device"
|
||||||
|
msgstr "内核散热设备"
|
||||||
|
|
||||||
|
msgid "Use auto for a single pwm-fan device, an explicit cooling_device path for multiple fans, or none when kernel fan control is already handled elsewhere."
|
||||||
|
msgstr "单个 pwm-fan 设备使用 auto;多个风扇需指定对应的 cooling_device 路径;已通过其他方式处理内核风扇控制时使用 none。"
|
||||||
|
|
||||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:173
|
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:173
|
||||||
msgid ""
|
msgid ""
|
||||||
"After starting, the fan stops only after temperature falls this many degrees "
|
"After starting, the fan stops only after temperature falls this many degrees "
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ return view.extend({
|
|||||||
(value === '2' && L.hasSystemFeature('offloading_hw')) ? '1' : null);
|
(value === '2' && L.hasSystemFeature('offloading_hw')) ? '1' : null);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (fw4 && L.hasSystemFeature('offloading_hw')) {
|
if (fw4 && L.hasSystemFeature('offloading_hw') && !L.hasSystemFeature('zte_npu')) {
|
||||||
o = s.option(form.ListValue, 'flow_offloading_hw_ipv6', _('Hardware offloading protocols'));
|
o = s.option(form.ListValue, 'flow_offloading_hw_ipv6', _('Hardware offloading protocols'));
|
||||||
o.value('1', _('IPv4+v6 acceleration'));
|
o.value('1', _('IPv4+v6 acceleration'));
|
||||||
o.value('0', _('IPv4 acceleration'));
|
o.value('0', _('IPv4 acceleration'));
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
PKG_MAINTAINER:=Thaolga <https://github.com/Thaolga/luci-app-nekobox>
|
PKG_MAINTAINER:=Thaolga <https://github.com/Thaolga/luci-app-nekobox>
|
||||||
PKG_NAME:=luci-app-nekobox
|
PKG_NAME:=luci-app-nekobox
|
||||||
PKG_VERSION:=2.0.9
|
PKG_VERSION:=2.0.9
|
||||||
PKG_RELEASE:=8
|
PKG_RELEASE:=10
|
||||||
|
|
||||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
|
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -7,8 +7,8 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=luci-app-passwall
|
PKG_NAME:=luci-app-passwall
|
||||||
PKG_VERSION:=26.11.3
|
PKG_VERSION:=26.11.11
|
||||||
PKG_RELEASE:=374
|
PKG_RELEASE:=375
|
||||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||||
|
|
||||||
PKG_CONFIG_DEPENDS:= \
|
PKG_CONFIG_DEPENDS:= \
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ local trojan_type = {}
|
|||||||
local vmess_type = {}
|
local vmess_type = {}
|
||||||
local vless_type = {}
|
local vless_type = {}
|
||||||
local hysteria2_type = {}
|
local hysteria2_type = {}
|
||||||
|
local other_type = {}
|
||||||
local xray_version = api.get_app_version("xray")
|
local xray_version = api.get_app_version("xray")
|
||||||
if has_ss_rust then
|
if has_ss_rust then
|
||||||
local s = "shadowsocks-rust"
|
local s = "shadowsocks-rust"
|
||||||
@@ -22,6 +23,7 @@ if has_singbox then
|
|||||||
table.insert(vmess_type, s)
|
table.insert(vmess_type, s)
|
||||||
table.insert(vless_type, s)
|
table.insert(vless_type, s)
|
||||||
table.insert(hysteria2_type, s)
|
table.insert(hysteria2_type, s)
|
||||||
|
table.insert(other_type, s)
|
||||||
end
|
end
|
||||||
if has_xray then
|
if has_xray then
|
||||||
local s = "xray"
|
local s = "xray"
|
||||||
@@ -29,6 +31,7 @@ if has_xray then
|
|||||||
table.insert(ss_type, s)
|
table.insert(ss_type, s)
|
||||||
table.insert(vmess_type, s)
|
table.insert(vmess_type, s)
|
||||||
table.insert(vless_type, s)
|
table.insert(vless_type, s)
|
||||||
|
table.insert(other_type, s)
|
||||||
if api.compare_versions(xray_version, ">=", "26.1.13") then
|
if api.compare_versions(xray_version, ">=", "26.1.13") then
|
||||||
table.insert(hysteria2_type, s)
|
table.insert(hysteria2_type, s)
|
||||||
end
|
end
|
||||||
@@ -100,7 +103,15 @@ if #hysteria2_type > 0 then
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if #ss_type > 0 or #trojan_type > 0 or #vmess_type > 0 or #vless_type > 0 or #hysteria2_type > 0 then
|
if #other_type > 0 then
|
||||||
|
o = s:option(ListValue, "other_type", translatef("%s Node Use Type", translate("Other")))
|
||||||
|
o:value("", translate("Auto"))
|
||||||
|
for key, value in pairs(other_type) do
|
||||||
|
o:value(value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #ss_type > 0 or #trojan_type > 0 or #vmess_type > 0 or #vless_type > 0 or #hysteria2_type > 0 or #other_type > 0 then
|
||||||
o.description = string.format("<font color='red'>%s</font>",
|
o.description = string.format("<font color='red'>%s</font>",
|
||||||
translate("The configured type also applies to the core specified when manually importing nodes."))
|
translate("The configured type also applies to the core specified when manually importing nodes."))
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ local trojan_type = {}
|
|||||||
local vmess_type = {}
|
local vmess_type = {}
|
||||||
local vless_type = {}
|
local vless_type = {}
|
||||||
local hysteria2_type = {}
|
local hysteria2_type = {}
|
||||||
|
local other_type = {}
|
||||||
local xray_version = api.get_app_version("xray")
|
local xray_version = api.get_app_version("xray")
|
||||||
if has_ss_rust then
|
if has_ss_rust then
|
||||||
local s = "shadowsocks-rust"
|
local s = "shadowsocks-rust"
|
||||||
@@ -35,6 +36,7 @@ if has_singbox then
|
|||||||
table.insert(vmess_type, s)
|
table.insert(vmess_type, s)
|
||||||
table.insert(vless_type, s)
|
table.insert(vless_type, s)
|
||||||
table.insert(hysteria2_type, s)
|
table.insert(hysteria2_type, s)
|
||||||
|
table.insert(other_type, s)
|
||||||
end
|
end
|
||||||
if has_xray then
|
if has_xray then
|
||||||
local s = "xray"
|
local s = "xray"
|
||||||
@@ -42,6 +44,7 @@ if has_xray then
|
|||||||
table.insert(ss_type, s)
|
table.insert(ss_type, s)
|
||||||
table.insert(vmess_type, s)
|
table.insert(vmess_type, s)
|
||||||
table.insert(vless_type, s)
|
table.insert(vless_type, s)
|
||||||
|
table.insert(other_type, s)
|
||||||
if api.compare_versions(xray_version, ">=", "26.1.13") then
|
if api.compare_versions(xray_version, ">=", "26.1.13") then
|
||||||
table.insert(hysteria2_type, s)
|
table.insert(hysteria2_type, s)
|
||||||
end
|
end
|
||||||
@@ -222,7 +225,18 @@ if #hysteria2_type > 0 then
|
|||||||
for key, value in pairs(hysteria2_type) do
|
for key, value in pairs(hysteria2_type) do
|
||||||
o:value(value)
|
o:value(value)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #other_type > 0 then
|
||||||
|
o = s:option(ListValue, "other_type", translatef("%s Node Use Type", translate("Other")))
|
||||||
|
o.default = "global"
|
||||||
|
o:value("global", translate("Use global config"))
|
||||||
|
for key, value in pairs(other_type) do
|
||||||
|
o:value(value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #hysteria2_type > 0 then
|
||||||
o = s:option(Value, "hysteria_up_mbps", "Hy/Hy2 " .. translate("Max upload Mbps"))
|
o = s:option(Value, "hysteria_up_mbps", "Hy/Hy2 " .. translate("Max upload Mbps"))
|
||||||
o.datatype = "uinteger"
|
o.datatype = "uinteger"
|
||||||
|
|
||||||
|
|||||||
@@ -706,6 +706,10 @@ if singbox_tags:find("with_wireguard") then
|
|||||||
|
|
||||||
o = s:option(Value, "wireguard_reserved", translate("Reserved"), translate("Decimal numbers separated by \",\" or Base64-encoded strings."))
|
o = s:option(Value, "wireguard_reserved", translate("Reserved"), translate("Decimal numbers separated by \",\" or Base64-encoded strings."))
|
||||||
o:depends({ protocol = "wireguard" })
|
o:depends({ protocol = "wireguard" })
|
||||||
|
|
||||||
|
o = s:option(Value, "wireguard_keepAlive", translate("Keep Alive"))
|
||||||
|
o.default = "0"
|
||||||
|
o:depends({ protocol = "wireguard" })
|
||||||
end
|
end
|
||||||
|
|
||||||
-- [[ TCP部分(模拟) ]]--
|
-- [[ TCP部分(模拟) ]]--
|
||||||
|
|||||||
@@ -507,6 +507,7 @@ function gen_outbound(flag, node, tag, proxy_table)
|
|||||||
pre_shared_key = node.wireguard_preSharedKey,
|
pre_shared_key = node.wireguard_preSharedKey,
|
||||||
reserved = node.wireguard_reserved,
|
reserved = node.wireguard_reserved,
|
||||||
mtu = tonumber(node.wireguard_mtu),
|
mtu = tonumber(node.wireguard_mtu),
|
||||||
|
persistent_keepalive_interval = tonumber(node.wireguard_keepAlive),
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -2438,7 +2439,8 @@ function gen_config(var)
|
|||||||
port = value.server_port,
|
port = value.server_port,
|
||||||
public_key = value.peer_public_key,
|
public_key = value.peer_public_key,
|
||||||
pre_shared_key = value.pre_shared_key,
|
pre_shared_key = value.pre_shared_key,
|
||||||
allowed_ips = {"0.0.0.0/0"},
|
allowed_ips = {"0.0.0.0/0", "::/0"},
|
||||||
|
persistent_keepalive_interval = value.persistent_keepalive_interval,
|
||||||
reserved = value.reserved
|
reserved = value.reserved
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -436,7 +436,7 @@ function gen_outbound(flag, node, tag, proxy_table)
|
|||||||
peers = (node.protocol == "wireguard") and {
|
peers = (node.protocol == "wireguard") and {
|
||||||
{
|
{
|
||||||
publicKey = node.wireguard_public_key,
|
publicKey = node.wireguard_public_key,
|
||||||
endpoint = node.address .. ":" .. node.port,
|
endpoint = (api.is_ipv6(node.address) and ("[" .. node.address .. "]") or node.address) .. ":" .. node.port,
|
||||||
preSharedKey = node.wireguard_preSharedKey,
|
preSharedKey = node.wireguard_preSharedKey,
|
||||||
keepAlive = node.wireguard_keepAlive and tonumber(node.wireguard_keepAlive) or nil
|
keepAlive = node.wireguard_keepAlive and tonumber(node.wireguard_keepAlive) or nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ local trojan_type = api.get_core("trojan_type", {{has_singbox,"sing-box"},{has_x
|
|||||||
local vmess_type = api.get_core("vmess_type", {{has_xray,"xray"},{has_singbox,"sing-box"}})
|
local vmess_type = api.get_core("vmess_type", {{has_xray,"xray"},{has_singbox,"sing-box"}})
|
||||||
local vless_type = api.get_core("vless_type", {{has_xray,"xray"},{has_singbox,"sing-box"}})
|
local vless_type = api.get_core("vless_type", {{has_xray,"xray"},{has_singbox,"sing-box"}})
|
||||||
local hysteria2_type = api.get_core("hysteria2_type", {{has_hysteria2,"hysteria2"},{has_singbox,"sing-box"}, {has_xray,"xray"}})
|
local hysteria2_type = api.get_core("hysteria2_type", {{has_hysteria2,"hysteria2"},{has_singbox,"sing-box"}, {has_xray,"xray"}})
|
||||||
|
local other_type = api.get_core("other_type", {{has_singbox,"sing-box"},{has_xray,"xray"}})
|
||||||
|
|
||||||
local current_node = map:get(section)
|
local current_node = map:get(section)
|
||||||
-%>
|
-%>
|
||||||
@@ -27,6 +28,7 @@ local current_node = map:get(section)
|
|||||||
let vmess_type = "<%=vmess_type%>"
|
let vmess_type = "<%=vmess_type%>"
|
||||||
let vless_type = "<%=vless_type%>"
|
let vless_type = "<%=vless_type%>"
|
||||||
let hysteria2_type = "<%=hysteria2_type%>"
|
let hysteria2_type = "<%=hysteria2_type%>"
|
||||||
|
let other_type = "<%=other_type%>"
|
||||||
|
|
||||||
function padright(str, cnt, pad) {
|
function padright(str, cnt, pad) {
|
||||||
return str + Array(cnt + 1).join(pad);
|
return str + Array(cnt + 1).join(pad);
|
||||||
@@ -191,6 +193,8 @@ local current_node = map:get(section)
|
|||||||
dom_prefix = "xray_"
|
dom_prefix = "xray_"
|
||||||
} else if (v_type === "sing-box") {
|
} else if (v_type === "sing-box") {
|
||||||
dom_prefix = "singbox_"
|
dom_prefix = "singbox_"
|
||||||
|
} else if (v_type === "Socks") {
|
||||||
|
dom_prefix = "socks_"
|
||||||
}
|
}
|
||||||
var _address = ""
|
var _address = ""
|
||||||
if (dom_prefix && dom_prefix != null) {
|
if (dom_prefix && dom_prefix != null) {
|
||||||
@@ -741,6 +745,74 @@ local current_node = map:get(section)
|
|||||||
params = params.substring(1);
|
params = params.substring(1);
|
||||||
}
|
}
|
||||||
url += params;
|
url += params;
|
||||||
|
} else if ((v_type === "Xray" || v_type === "sing-box") && opt.get(dom_prefix + "protocol").value === "wireguard") {
|
||||||
|
protocol = "wireguard";
|
||||||
|
var v_password = opt.get(dom_prefix + "wireguard_secret_key");
|
||||||
|
var v_port = opt.get(dom_prefix + "port");
|
||||||
|
url = encodeURIComponent(v_password.value) +
|
||||||
|
"@" + _address +
|
||||||
|
":" + v_port.value + "?";
|
||||||
|
|
||||||
|
var params = "";
|
||||||
|
params += opt.query("publickey", dom_prefix + "wireguard_public_key");
|
||||||
|
params += opt.query("presharedkey", dom_prefix + "wireguard_preSharedKey");
|
||||||
|
var addresses = opt.getlist(dom_prefix + "wireguard_local_address").map(value => value.trim()).filter(Boolean);
|
||||||
|
params += "&address=" + encodeURIComponent(addresses.join(","));
|
||||||
|
params += opt.query("reserved", dom_prefix + "wireguard_reserved");
|
||||||
|
params += opt.query("mtu", dom_prefix + "wireguard_mtu");
|
||||||
|
params += opt.query("persistent-keepalive", dom_prefix + "wireguard_keepAlive");
|
||||||
|
if (v_type === "Xray" && opt.get(dom_prefix + "use_finalmask")?.checked) {
|
||||||
|
params += opt.query("fm", dom_prefix + "finalmask");
|
||||||
|
}
|
||||||
|
params += "#" + encodeURIComponent(v_alias.value);
|
||||||
|
if (params[0] == "&") {
|
||||||
|
params = params.substring(1);
|
||||||
|
}
|
||||||
|
url += params;
|
||||||
|
} else if (v_type === "Socks" || ((v_type === "sing-box" || v_type === "Xray") && opt.get(dom_prefix + "protocol").value === "socks")) {
|
||||||
|
protocol = "socks";
|
||||||
|
var v_username = opt.get(dom_prefix + "username");
|
||||||
|
var v_password = opt.get(dom_prefix + "password");
|
||||||
|
var v_port = opt.get(dom_prefix + "port");
|
||||||
|
url = _address + ":" + v_port.value;
|
||||||
|
if (v_username.value || v_password.value) {
|
||||||
|
url = b64encutf8safe(v_username.value + ":" + v_password.value) + "@" + url;
|
||||||
|
}
|
||||||
|
var params = "";
|
||||||
|
params += opt.query("uot", dom_prefix + "uot");
|
||||||
|
params += opt.query("tfo", dom_prefix + "tcp_fast_open");
|
||||||
|
if (params) {
|
||||||
|
url += "?" + params.substring(1);
|
||||||
|
}
|
||||||
|
url += "#" + encodeURIComponent(v_alias.value);
|
||||||
|
} else if ((v_type === "sing-box" || v_type === "Xray") && opt.get(dom_prefix + "protocol").value === "http") {
|
||||||
|
protocol = opt.get(dom_prefix + "tls").checked ? "https" : "http";
|
||||||
|
var v_username = opt.get(dom_prefix + "username");
|
||||||
|
var v_password = opt.get(dom_prefix + "password");
|
||||||
|
var v_port = opt.get(dom_prefix + "port");
|
||||||
|
url = _address + ":" + v_port.value;
|
||||||
|
if (v_username.value || v_password.value) {
|
||||||
|
url = encodeURIComponent(v_username.value) + ":" + encodeURIComponent(v_password.value) + "@" + url;
|
||||||
|
}
|
||||||
|
var params = "";
|
||||||
|
if (protocol === "https") {
|
||||||
|
params += opt.query("sni", dom_prefix + "tls_serverName");
|
||||||
|
params += opt.query("alpn", dom_prefix + "alpn");
|
||||||
|
params += opt.query("insecure", dom_prefix + "tls_allowInsecure");
|
||||||
|
params += opt.query("pcs", dom_prefix + "tls_pinSHA256");
|
||||||
|
params += opt.query("vcn", dom_prefix + "tls_CertByName");
|
||||||
|
if (opt.get(dom_prefix + "utls")?.checked) {
|
||||||
|
params += opt.query("fp", dom_prefix + "fingerprint");
|
||||||
|
}
|
||||||
|
if (opt.get(dom_prefix + "ech")?.checked) {
|
||||||
|
params += opt.query("ech", dom_prefix + "ech_config");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
params += opt.query("tfo", dom_prefix + "tcp_fast_open");
|
||||||
|
if (params) {
|
||||||
|
url += "?" + params.substring(1);
|
||||||
|
}
|
||||||
|
url += "#" + encodeURIComponent(v_alias.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url) {
|
if (url) {
|
||||||
@@ -1828,6 +1900,126 @@ local current_node = map:get(section)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (link_type === "wireguard") {
|
||||||
|
if (other_type == "sing-box" && has_singbox) {
|
||||||
|
dom_prefix = "singbox_";
|
||||||
|
opt.set('type', "sing-box");
|
||||||
|
} else if (other_type == "xray" && has_xray) {
|
||||||
|
dom_prefix = "xray_";
|
||||||
|
opt.set('type', "Xray");
|
||||||
|
}
|
||||||
|
opt.set(dom_prefix + 'protocol', "wireguard");
|
||||||
|
var info = link_split[1].split('@');
|
||||||
|
var m = parseNodeUrl("wireguard://" + info[1]);
|
||||||
|
var host_port = info[1].split(/[/?#]/)[0];
|
||||||
|
opt.set(dom_prefix + 'address', unbracketIP(m.hostname));
|
||||||
|
opt.set(dom_prefix + 'port', host_port.match(/:(\d+)$/)?.[1] || '51820');
|
||||||
|
opt.set(dom_prefix + 'wireguard_secret_key', decodeURIComponent(info[0]));
|
||||||
|
var queryParam = {};
|
||||||
|
if (m.search.length > 1) {
|
||||||
|
var queryArray = m.search.substr(1).split('&');
|
||||||
|
for (var i = 0; i < queryArray.length; i++) {
|
||||||
|
var index = queryArray[i].indexOf('=');
|
||||||
|
if (index > 0) {
|
||||||
|
queryParam[decodeURIComponent(queryArray[i].substr(0, index)).toLowerCase()] = decodeURIComponent(queryArray[i].substr(index + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opt.set(dom_prefix + 'wireguard_public_key', queryParam.publickey);
|
||||||
|
opt.set(dom_prefix + 'wireguard_preSharedKey', queryParam.presharedkey || '');
|
||||||
|
var addresses = ((queryParam.address || queryParam.ip || '') + ',' + (queryParam.ipv6 || '')).split(/[,\s]+/).filter(Boolean);
|
||||||
|
var fullName = opt.base + '.' + dom_prefix + 'wireguard_local_address';
|
||||||
|
var lists = document.querySelectorAll('.cbi-dynlist');
|
||||||
|
for (var i = 0; i < lists.length; i++) {
|
||||||
|
var label = lists[i].closest('.cbi-value')?.querySelector('label.cbi-value-title');
|
||||||
|
if (label?.getAttribute('for') === fullName) {
|
||||||
|
L.dom.callClassMethod(lists[i], 'setValue', addresses);
|
||||||
|
lists[i].dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opt.set(dom_prefix + 'wireguard_reserved', queryParam.reserved || '');
|
||||||
|
opt.set(dom_prefix + 'wireguard_mtu', queryParam.mtu || '1420');
|
||||||
|
opt.set(dom_prefix + 'wireguard_keepAlive', queryParam['persistent-keepalive'] || queryParam.keepalive || '0');
|
||||||
|
if (dom_prefix === "xray_") {
|
||||||
|
opt.set(dom_prefix + 'use_finalmask', !!queryParam.fm);
|
||||||
|
opt.set(dom_prefix + 'finalmask', queryParam.fm || '');
|
||||||
|
}
|
||||||
|
if (m.hash) {
|
||||||
|
opt.set('remarks', decodeURIComponent(m.hash.substr(1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (link_type === "socks" || link_type === "socks5" || link_type === "socks5h" || link_type === "http" || link_type === "https") {
|
||||||
|
if (other_type == "sing-box" && has_singbox) {
|
||||||
|
dom_prefix = "singbox_";
|
||||||
|
opt.set('type', "sing-box");
|
||||||
|
} else if (other_type == "xray" && has_xray) {
|
||||||
|
dom_prefix = "xray_";
|
||||||
|
opt.set('type', "Xray");
|
||||||
|
}
|
||||||
|
var protocol = (link_type === "http" || link_type === "https") ? "http" : "socks";
|
||||||
|
opt.set(dom_prefix + 'protocol', protocol);
|
||||||
|
var content = link_split[1];
|
||||||
|
var index = content.search(/[?#]/);
|
||||||
|
var suffix = index >= 0 ? content.substr(index) : '';
|
||||||
|
content = index >= 0 ? content.substr(0, index) : content;
|
||||||
|
var base64_auth = false;
|
||||||
|
if (protocol === "socks" && content.indexOf('@') === -1 && content.indexOf(':') === -1) {
|
||||||
|
content = b64decutf8safe(decodeURIComponent(content));
|
||||||
|
base64_auth = true;
|
||||||
|
}
|
||||||
|
var auth = '';
|
||||||
|
var index = content.lastIndexOf('@');
|
||||||
|
if (index >= 0) {
|
||||||
|
auth = content.substr(0, index);
|
||||||
|
content = content.substr(index + 1);
|
||||||
|
}
|
||||||
|
if (protocol === "socks" && auth.indexOf(':') === -1) {
|
||||||
|
auth = b64decutf8safe(decodeURIComponent(auth));
|
||||||
|
base64_auth = true;
|
||||||
|
}
|
||||||
|
var index = auth.indexOf(':');
|
||||||
|
var username = index >= 0 ? auth.substr(0, index) : auth;
|
||||||
|
var password = index >= 0 ? auth.substr(index + 1) : '';
|
||||||
|
if (!base64_auth) {
|
||||||
|
username = decodeURIComponent(username);
|
||||||
|
password = decodeURIComponent(password);
|
||||||
|
}
|
||||||
|
var m = new URL(link_type + "://" + content + suffix);
|
||||||
|
opt.set(dom_prefix + 'address', unbracketIP(m.hostname));
|
||||||
|
opt.set(dom_prefix + 'port', m.port || (protocol === "socks" ? "1080" : (link_type === "https" ? "443" : "80")));
|
||||||
|
opt.set(dom_prefix + 'username', username);
|
||||||
|
opt.set(dom_prefix + 'password', password);
|
||||||
|
opt.set(dom_prefix + 'transport', dom_prefix === "xray_" ? "raw" : "tcp");
|
||||||
|
var queryParam = {};
|
||||||
|
if (m.search.length > 1) {
|
||||||
|
var queryArray = m.search.substr(1).split('&');
|
||||||
|
for (var i = 0; i < queryArray.length; i++) {
|
||||||
|
var index = queryArray[i].indexOf('=');
|
||||||
|
if (index > 0) {
|
||||||
|
queryParam[decodeURIComponent(queryArray[i].substr(0, index)).toLowerCase()] = decodeURIComponent(queryArray[i].substr(index + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opt.set(dom_prefix + 'uot', queryParam.uot === '1');
|
||||||
|
opt.set(dom_prefix + 'tcp_fast_open', queryParam.tfo === '1');
|
||||||
|
opt.set(dom_prefix + 'tls', link_type === "https");
|
||||||
|
opt.set(dom_prefix + 'reality', false);
|
||||||
|
opt.set(dom_prefix + 'tls_serverName', queryParam.sni || '');
|
||||||
|
opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default');
|
||||||
|
opt.set(dom_prefix + 'tls_allowInsecure', (queryParam.allowinsecure || queryParam.insecure || queryParam.allow_insecure || '0') === '1');
|
||||||
|
opt.set(dom_prefix + 'tls_pinSHA256', queryParam.pcs || '');
|
||||||
|
opt.set(dom_prefix + 'tls_CertByName', queryParam.vcn || '');
|
||||||
|
opt.set(dom_prefix + 'utls', !!queryParam.fp);
|
||||||
|
opt.set(dom_prefix + 'fingerprint', queryParam.fp || 'chrome');
|
||||||
|
opt.set(dom_prefix + 'ech', !!queryParam.ech);
|
||||||
|
opt.set(dom_prefix + 'ech_config', queryParam.ech || '');
|
||||||
|
if (m.hash) {
|
||||||
|
opt.set('remarks', decodeURIComponent(m.hash.substr(1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (switch_type) {
|
if (switch_type) {
|
||||||
sessionStorage.setItem("fromUrl", JSON.stringify({
|
sessionStorage.setItem("fromUrl", JSON.stringify({
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
|
|||||||
@@ -1355,12 +1355,18 @@ msgstr "是否允许不安全连接。当勾选时,将跳过证书验证。"
|
|||||||
msgid "%s Node Use Type"
|
msgid "%s Node Use Type"
|
||||||
msgstr "%s 节点使用类型"
|
msgstr "%s 节点使用类型"
|
||||||
|
|
||||||
|
msgid "Other"
|
||||||
|
msgstr "其他"
|
||||||
|
|
||||||
msgid "UDP relay mode"
|
msgid "UDP relay mode"
|
||||||
msgstr "UDP 中继模式"
|
msgstr "UDP 中继模式"
|
||||||
|
|
||||||
msgid "Congestion control algorithm"
|
msgid "Congestion control algorithm"
|
||||||
msgstr "拥塞控制算法"
|
msgstr "拥塞控制算法"
|
||||||
|
|
||||||
|
msgid "Keep Alive"
|
||||||
|
msgstr "保活间隔"
|
||||||
|
|
||||||
msgid "Heartbeat interval(second)"
|
msgid "Heartbeat interval(second)"
|
||||||
msgstr "保活心跳包发送间隔(单位:秒)"
|
msgstr "保活心跳包发送间隔(单位:秒)"
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ local DEFAULT_TROJAN_TYPE = api.get_core("trojan_type", {{has_singbox,"sing-box
|
|||||||
local DEFAULT_VMESS_TYPE = api.get_core("vmess_type", {{has_xray,"xray"},{has_singbox,"sing-box"}})
|
local DEFAULT_VMESS_TYPE = api.get_core("vmess_type", {{has_xray,"xray"},{has_singbox,"sing-box"}})
|
||||||
local DEFAULT_VLESS_TYPE = api.get_core("vless_type", {{has_xray,"xray"},{has_singbox,"sing-box"}})
|
local DEFAULT_VLESS_TYPE = api.get_core("vless_type", {{has_xray,"xray"},{has_singbox,"sing-box"}})
|
||||||
local DEFAULT_HYSTERIA2_TYPE = api.get_core("hysteria2_type", {{has_hysteria2,"hysteria2"},{has_singbox,"sing-box"},{has_xray,"xray"}})
|
local DEFAULT_HYSTERIA2_TYPE = api.get_core("hysteria2_type", {{has_hysteria2,"hysteria2"},{has_singbox,"sing-box"},{has_xray,"xray"}})
|
||||||
|
local DEFAULT_OTHER_TYPE = api.get_core("other_type", {{has_singbox,"sing-box"},{has_xray,"xray"}})
|
||||||
local core_has = {
|
local core_has = {
|
||||||
["xray"] = has_xray,
|
["xray"] = has_xray,
|
||||||
["sing-box"] = has_singbox,
|
["sing-box"] = has_singbox,
|
||||||
@@ -569,6 +570,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
|||||||
local sub_vmess_type = DEFAULT_VMESS_TYPE
|
local sub_vmess_type = DEFAULT_VMESS_TYPE
|
||||||
local sub_vless_type = DEFAULT_VLESS_TYPE
|
local sub_vless_type = DEFAULT_VLESS_TYPE
|
||||||
local sub_hysteria2_type = DEFAULT_HYSTERIA2_TYPE
|
local sub_hysteria2_type = DEFAULT_HYSTERIA2_TYPE
|
||||||
|
local sub_other_type = DEFAULT_OTHER_TYPE
|
||||||
local sub_hy_up_mbps, sub_hy_down_mbps = 1000, 1000
|
local sub_hy_up_mbps, sub_hy_down_mbps = 1000, 1000
|
||||||
if sub_cfg then
|
if sub_cfg then
|
||||||
if sub_cfg.allowInsecure and sub_cfg.allowInsecure ~= "1" then
|
if sub_cfg.allowInsecure and sub_cfg.allowInsecure ~= "1" then
|
||||||
@@ -594,6 +596,10 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
|||||||
if hysteria2_type ~= "global" and core_has[hysteria2_type] then
|
if hysteria2_type ~= "global" and core_has[hysteria2_type] then
|
||||||
sub_hysteria2_type = hysteria2_type
|
sub_hysteria2_type = hysteria2_type
|
||||||
end
|
end
|
||||||
|
local other_type = sub_cfg.other_type or "global"
|
||||||
|
if other_type ~= "global" and core_has[other_type] then
|
||||||
|
sub_other_type = other_type
|
||||||
|
end
|
||||||
sub_hy_up_mbps = sub_cfg.hysteria_up_mbps
|
sub_hy_up_mbps = sub_cfg.hysteria_up_mbps
|
||||||
sub_hy_down_mbps = sub_cfg.hysteria_down_mbps
|
sub_hy_down_mbps = sub_cfg.hysteria_down_mbps
|
||||||
end
|
end
|
||||||
@@ -1664,6 +1670,115 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
|||||||
result.snell_mode = params.mode or "default"
|
result.snell_mode = params.mode or "default"
|
||||||
end
|
end
|
||||||
result.snell_reuse = (params.reuse == "1") and "1" or "0"
|
result.snell_reuse = (params.reuse == "1") and "1" or "0"
|
||||||
|
elseif szType == "wireguard" then
|
||||||
|
if sub_other_type == "sing-box" and has_singbox then
|
||||||
|
result.type = "sing-box"
|
||||||
|
elseif sub_other_type == "xray" and has_xray then
|
||||||
|
result.type = "Xray"
|
||||||
|
else
|
||||||
|
log("跳过 WireGuard 节点,因未安装 WireGuard 核心程序 Xray 或 Sing-box。")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
result.protocol = "wireguard"
|
||||||
|
|
||||||
|
local alias = ""
|
||||||
|
if content:find("#") then
|
||||||
|
local idx_sp = content:find("#")
|
||||||
|
alias = content:sub(idx_sp + 1, -1)
|
||||||
|
content = content:sub(0, idx_sp - 1)
|
||||||
|
end
|
||||||
|
result.remarks = UrlDecode(alias)
|
||||||
|
local contents = split(content, "@")
|
||||||
|
result.wireguard_secret_key = UrlDecode(contents[1]:gsub("%+", "%%2B"))
|
||||||
|
local query = split(contents[2], "%?")
|
||||||
|
local host_port = query[1]
|
||||||
|
local params = {}
|
||||||
|
for _, v in pairs(split(query[2], '&')) do
|
||||||
|
local s = v:find("=", 1, true)
|
||||||
|
if s and s > 1 then
|
||||||
|
params[UrlDecode(v:sub(1, s - 1)):lower()] = UrlDecode(v:sub(s + 1):gsub("%+", "%%2B"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
result.address, result.port = parse_host_port(host_port, "51820")
|
||||||
|
result.wireguard_public_key = params.publickey
|
||||||
|
result.wireguard_preSharedKey = params.presharedkey
|
||||||
|
result.wireguard_local_address = {}
|
||||||
|
for value in ((params.address or params.ip or "") .. "," .. (params.ipv6 or "")):gmatch("[^,%s]+") do
|
||||||
|
result.wireguard_local_address[#result.wireguard_local_address + 1] = value
|
||||||
|
end
|
||||||
|
result.wireguard_reserved = params.reserved
|
||||||
|
result.wireguard_mtu = params.mtu or "1420"
|
||||||
|
result.wireguard_keepAlive = params["persistent-keepalive"] or params.keepalive or "0"
|
||||||
|
result.use_finalmask = (params.fm and params.fm ~= "") and "1" or nil
|
||||||
|
result.finalmask = (params.fm and params.fm ~= "") and api.base64Encode(params.fm) or nil
|
||||||
|
elseif szType == "socks" or szType == "socks5" or szType == "socks5h" or szType == "http" or szType == "https" then
|
||||||
|
if sub_other_type == "sing-box" and has_singbox then
|
||||||
|
result.type = "sing-box"
|
||||||
|
elseif sub_other_type == "xray" and has_xray then
|
||||||
|
result.type = "Xray"
|
||||||
|
else
|
||||||
|
log("跳过 " .. szType .. " 节点,因未安装 " .. szType .. " 核心程序 Xray 或 Sing-box。")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
result.protocol = (szType == "http" or szType == "https") and "http" or "socks"
|
||||||
|
|
||||||
|
local alias = ""
|
||||||
|
if content:find("#") then
|
||||||
|
local idx_sp = content:find("#")
|
||||||
|
alias = content:sub(idx_sp + 1, -1)
|
||||||
|
content = content:sub(0, idx_sp - 1)
|
||||||
|
end
|
||||||
|
result.remarks = UrlDecode(alias)
|
||||||
|
local query = split(content, "%?")
|
||||||
|
local Info = query[1]
|
||||||
|
local base64_auth = false
|
||||||
|
if result.protocol == "socks" and not Info:find("@", 1, true) and not Info:find(":", 1, true) then
|
||||||
|
Info = base64Decode(UrlDecode(Info:gsub("%+", "%%2B")))
|
||||||
|
base64_auth = true
|
||||||
|
end
|
||||||
|
local auth, host_port = Info:match("^(.*)@(.*)$")
|
||||||
|
host_port = host_port or Info
|
||||||
|
auth = auth or ""
|
||||||
|
if result.protocol == "socks" and not auth:find(":", 1, true) then
|
||||||
|
auth = base64Decode(UrlDecode(auth:gsub("%+", "%%2B")))
|
||||||
|
base64_auth = true
|
||||||
|
end
|
||||||
|
local idx = auth:find(":", 1, true)
|
||||||
|
result.username = idx and auth:sub(1, idx - 1) or auth
|
||||||
|
result.password = idx and auth:sub(idx + 1) or ""
|
||||||
|
if not base64_auth then
|
||||||
|
result.username = UrlDecode(result.username:gsub("%+", "%%2B"))
|
||||||
|
result.password = UrlDecode(result.password:gsub("%+", "%%2B"))
|
||||||
|
end
|
||||||
|
local params = {}
|
||||||
|
for _, v in pairs(split(query[2], '&')) do
|
||||||
|
local s = v:find("=", 1, true)
|
||||||
|
if s and s > 1 then
|
||||||
|
params[UrlDecode(v:sub(1, s - 1)):lower()] = UrlDecode(v:sub(s + 1):gsub("%+", "%%2B"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local port = result.protocol == "socks" and "1080" or (szType == "https" and "443" or "80")
|
||||||
|
result.address, result.port = parse_host_port(host_port, port)
|
||||||
|
result.transport = result.type == "Xray" and "raw" or "tcp"
|
||||||
|
result.uot = params.uot
|
||||||
|
result.tcp_fast_open = params.tfo
|
||||||
|
result.tls = szType == "https" and "1" or "0"
|
||||||
|
if result.tls == "1" then
|
||||||
|
result.tls_serverName = params.sni
|
||||||
|
result.alpn = params.alpn
|
||||||
|
local insecure = params.allowinsecure or params.insecure or params.allow_insecure
|
||||||
|
result.tls_allowInsecure = (insecure == "1" or insecure == "0") and insecure or (sub_allowinsecure and "1" or "0")
|
||||||
|
result.tls_pinSHA256 = params.pcs
|
||||||
|
result.tls_CertByName = params.vcn
|
||||||
|
if params.fp and params.fp ~= "" then
|
||||||
|
result.utls = "1"
|
||||||
|
result.fingerprint = params.fp
|
||||||
|
end
|
||||||
|
if params.ech and params.ech ~= "" then
|
||||||
|
result.ech = "1"
|
||||||
|
result.ech_config = params.ech
|
||||||
|
end
|
||||||
|
end
|
||||||
else
|
else
|
||||||
log("暂时不支持 " .. szType .. " 类型的节点订阅,跳过此节点。")
|
log("暂时不支持 " .. szType .. " 类型的节点订阅,跳过此节点。")
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=luci-app-passwall2
|
PKG_NAME:=luci-app-passwall2
|
||||||
PKG_VERSION:=26.11.3
|
PKG_VERSION:=26.11.11
|
||||||
PKG_RELEASE:=175
|
PKG_RELEASE:=176
|
||||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||||
|
|
||||||
PKG_CONFIG_DEPENDS:= \
|
PKG_CONFIG_DEPENDS:= \
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ local trojan_type = {}
|
|||||||
local vmess_type = {}
|
local vmess_type = {}
|
||||||
local vless_type = {}
|
local vless_type = {}
|
||||||
local hysteria2_type = {}
|
local hysteria2_type = {}
|
||||||
|
local other_type = {}
|
||||||
local xray_version = api.get_app_version("xray")
|
local xray_version = api.get_app_version("xray")
|
||||||
if has_ss_rust then
|
if has_ss_rust then
|
||||||
local s = "shadowsocks-rust"
|
local s = "shadowsocks-rust"
|
||||||
@@ -21,6 +22,7 @@ if has_singbox then
|
|||||||
table.insert(vmess_type, s)
|
table.insert(vmess_type, s)
|
||||||
table.insert(vless_type, s)
|
table.insert(vless_type, s)
|
||||||
table.insert(hysteria2_type, s)
|
table.insert(hysteria2_type, s)
|
||||||
|
table.insert(other_type, s)
|
||||||
end
|
end
|
||||||
if has_xray then
|
if has_xray then
|
||||||
local s = "xray"
|
local s = "xray"
|
||||||
@@ -28,6 +30,7 @@ if has_xray then
|
|||||||
table.insert(ss_type, s)
|
table.insert(ss_type, s)
|
||||||
table.insert(vmess_type, s)
|
table.insert(vmess_type, s)
|
||||||
table.insert(vless_type, s)
|
table.insert(vless_type, s)
|
||||||
|
table.insert(other_type, s)
|
||||||
if api.compare_versions(xray_version, ">=", "26.1.13") then
|
if api.compare_versions(xray_version, ">=", "26.1.13") then
|
||||||
table.insert(hysteria2_type, s)
|
table.insert(hysteria2_type, s)
|
||||||
end
|
end
|
||||||
@@ -95,7 +98,15 @@ if #hysteria2_type > 0 then
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if #ss_type > 0 or #trojan_type > 0 or #vmess_type > 0 or #vless_type > 0 or #hysteria2_type > 0 then
|
if #other_type > 0 then
|
||||||
|
o = s:option(ListValue, "other_type", translatef("%s Node Use Type", translate("Other")))
|
||||||
|
o:value("", translate("Auto"))
|
||||||
|
for key, value in pairs(other_type) do
|
||||||
|
o:value(value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #ss_type > 0 or #trojan_type > 0 or #vmess_type > 0 or #vless_type > 0 or #hysteria2_type > 0 or #other_type > 0 then
|
||||||
o.description = string.format("<font color='red'>%s</font>",
|
o.description = string.format("<font color='red'>%s</font>",
|
||||||
translate("The configured type also applies to the core specified when manually importing nodes."))
|
translate("The configured type also applies to the core specified when manually importing nodes."))
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ local trojan_type = {}
|
|||||||
local vmess_type = {}
|
local vmess_type = {}
|
||||||
local vless_type = {}
|
local vless_type = {}
|
||||||
local hysteria2_type = {}
|
local hysteria2_type = {}
|
||||||
|
local other_type = {}
|
||||||
local xray_version = api.get_app_version("xray")
|
local xray_version = api.get_app_version("xray")
|
||||||
if has_ss_rust then
|
if has_ss_rust then
|
||||||
local s = "shadowsocks-rust"
|
local s = "shadowsocks-rust"
|
||||||
@@ -34,6 +35,7 @@ if has_singbox then
|
|||||||
table.insert(vmess_type, s)
|
table.insert(vmess_type, s)
|
||||||
table.insert(vless_type, s)
|
table.insert(vless_type, s)
|
||||||
table.insert(hysteria2_type, s)
|
table.insert(hysteria2_type, s)
|
||||||
|
table.insert(other_type, s)
|
||||||
end
|
end
|
||||||
if has_xray then
|
if has_xray then
|
||||||
local s = "xray"
|
local s = "xray"
|
||||||
@@ -41,6 +43,7 @@ if has_xray then
|
|||||||
table.insert(ss_type, s)
|
table.insert(ss_type, s)
|
||||||
table.insert(vmess_type, s)
|
table.insert(vmess_type, s)
|
||||||
table.insert(vless_type, s)
|
table.insert(vless_type, s)
|
||||||
|
table.insert(other_type, s)
|
||||||
if api.compare_versions(xray_version, ">=", "26.1.13") then
|
if api.compare_versions(xray_version, ">=", "26.1.13") then
|
||||||
table.insert(hysteria2_type, s)
|
table.insert(hysteria2_type, s)
|
||||||
end
|
end
|
||||||
@@ -215,7 +218,18 @@ if #hysteria2_type > 0 then
|
|||||||
for key, value in pairs(hysteria2_type) do
|
for key, value in pairs(hysteria2_type) do
|
||||||
o:value(value)
|
o:value(value)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #other_type > 0 then
|
||||||
|
o = s:option(ListValue, "other_type", translatef("%s Node Use Type", translate("Other")))
|
||||||
|
o.default = "global"
|
||||||
|
o:value("global", translate("Use global config"))
|
||||||
|
for key, value in pairs(other_type) do
|
||||||
|
o:value(value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #hysteria2_type > 0 then
|
||||||
o = s:option(Value, "hysteria_up_mbps", "Hy/Hy2 " .. translate("Max upload Mbps"))
|
o = s:option(Value, "hysteria_up_mbps", "Hy/Hy2 " .. translate("Max upload Mbps"))
|
||||||
o.datatype = "uinteger"
|
o.datatype = "uinteger"
|
||||||
|
|
||||||
|
|||||||
@@ -768,6 +768,10 @@ if singbox_tags:find("with_wireguard") then
|
|||||||
|
|
||||||
o = s:option(Value, "wireguard_reserved", translate("Reserved"), translate("Decimal numbers separated by \",\" or Base64-encoded strings."))
|
o = s:option(Value, "wireguard_reserved", translate("Reserved"), translate("Decimal numbers separated by \",\" or Base64-encoded strings."))
|
||||||
o:depends({ protocol = "wireguard" })
|
o:depends({ protocol = "wireguard" })
|
||||||
|
|
||||||
|
o = s:option(Value, "wireguard_keepAlive", translate("Keep Alive"))
|
||||||
|
o.default = "0"
|
||||||
|
o:depends({ protocol = "wireguard" })
|
||||||
end
|
end
|
||||||
|
|
||||||
-- [[ TCP ]]--
|
-- [[ TCP ]]--
|
||||||
|
|||||||
@@ -523,6 +523,7 @@ function gen_outbound(flag, node, tag, proxy_table)
|
|||||||
pre_shared_key = node.wireguard_preSharedKey,
|
pre_shared_key = node.wireguard_preSharedKey,
|
||||||
reserved = node.wireguard_reserved,
|
reserved = node.wireguard_reserved,
|
||||||
mtu = tonumber(node.wireguard_mtu),
|
mtu = tonumber(node.wireguard_mtu),
|
||||||
|
persistent_keepalive_interval = tonumber(node.wireguard_keepAlive),
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -2399,7 +2400,8 @@ function gen_config(var)
|
|||||||
port = value.server_port,
|
port = value.server_port,
|
||||||
public_key = value.peer_public_key,
|
public_key = value.peer_public_key,
|
||||||
pre_shared_key = value.pre_shared_key,
|
pre_shared_key = value.pre_shared_key,
|
||||||
allowed_ips = {"0.0.0.0/0"},
|
allowed_ips = {"0.0.0.0/0", "::/0"},
|
||||||
|
persistent_keepalive_interval = value.persistent_keepalive_interval,
|
||||||
reserved = value.reserved
|
reserved = value.reserved
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -445,7 +445,7 @@ function gen_outbound(flag, node, tag, proxy_table)
|
|||||||
peers = (node.protocol == "wireguard") and {
|
peers = (node.protocol == "wireguard") and {
|
||||||
{
|
{
|
||||||
publicKey = node.wireguard_public_key,
|
publicKey = node.wireguard_public_key,
|
||||||
endpoint = node.address .. ":" .. node.port,
|
endpoint = (api.is_ipv6(node.address) and ("[" .. node.address .. "]") or node.address) .. ":" .. node.port,
|
||||||
preSharedKey = node.wireguard_preSharedKey,
|
preSharedKey = node.wireguard_preSharedKey,
|
||||||
keepAlive = node.wireguard_keepAlive and tonumber(node.wireguard_keepAlive) or nil
|
keepAlive = node.wireguard_keepAlive and tonumber(node.wireguard_keepAlive) or nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ local trojan_type = api.get_core("trojan_type", {{has_singbox,"sing-box"},{has_x
|
|||||||
local vmess_type = api.get_core("vmess_type", {{has_xray,"xray"},{has_singbox,"sing-box"}})
|
local vmess_type = api.get_core("vmess_type", {{has_xray,"xray"},{has_singbox,"sing-box"}})
|
||||||
local vless_type = api.get_core("vless_type", {{has_xray,"xray"},{has_singbox,"sing-box"}})
|
local vless_type = api.get_core("vless_type", {{has_xray,"xray"},{has_singbox,"sing-box"}})
|
||||||
local hysteria2_type = api.get_core("hysteria2_type", {{has_singbox,"sing-box"}, {has_xray,"xray"}})
|
local hysteria2_type = api.get_core("hysteria2_type", {{has_singbox,"sing-box"}, {has_xray,"xray"}})
|
||||||
|
local other_type = api.get_core("other_type", {{has_singbox,"sing-box"},{has_xray,"xray"}})
|
||||||
|
|
||||||
local current_node = map:get(section)
|
local current_node = map:get(section)
|
||||||
-%>
|
-%>
|
||||||
@@ -25,6 +26,7 @@ local current_node = map:get(section)
|
|||||||
let vmess_type = "<%=vmess_type%>"
|
let vmess_type = "<%=vmess_type%>"
|
||||||
let vless_type = "<%=vless_type%>"
|
let vless_type = "<%=vless_type%>"
|
||||||
let hysteria2_type = "<%=hysteria2_type%>"
|
let hysteria2_type = "<%=hysteria2_type%>"
|
||||||
|
let other_type = "<%=other_type%>"
|
||||||
|
|
||||||
function padright(str, cnt, pad) {
|
function padright(str, cnt, pad) {
|
||||||
return str + Array(cnt + 1).join(pad);
|
return str + Array(cnt + 1).join(pad);
|
||||||
@@ -713,6 +715,74 @@ local current_node = map:get(section)
|
|||||||
params = params.substring(1);
|
params = params.substring(1);
|
||||||
}
|
}
|
||||||
url += params;
|
url += params;
|
||||||
|
} else if ((v_type === "Xray" || v_type === "sing-box") && opt.get(dom_prefix + "protocol").value === "wireguard") {
|
||||||
|
protocol = "wireguard";
|
||||||
|
var v_password = opt.get(dom_prefix + "wireguard_secret_key");
|
||||||
|
var v_port = opt.get(dom_prefix + "port");
|
||||||
|
url = encodeURIComponent(v_password.value) +
|
||||||
|
"@" + _address +
|
||||||
|
":" + v_port.value + "?";
|
||||||
|
|
||||||
|
var params = "";
|
||||||
|
params += opt.query("publickey", dom_prefix + "wireguard_public_key");
|
||||||
|
params += opt.query("presharedkey", dom_prefix + "wireguard_preSharedKey");
|
||||||
|
var addresses = opt.getlist(dom_prefix + "wireguard_local_address").map(value => value.trim()).filter(Boolean);
|
||||||
|
params += "&address=" + encodeURIComponent(addresses.join(","));
|
||||||
|
params += opt.query("reserved", dom_prefix + "wireguard_reserved");
|
||||||
|
params += opt.query("mtu", dom_prefix + "wireguard_mtu");
|
||||||
|
params += opt.query("persistent-keepalive", dom_prefix + "wireguard_keepAlive");
|
||||||
|
if (v_type === "Xray" && opt.get(dom_prefix + "use_finalmask")?.checked) {
|
||||||
|
params += opt.query("fm", dom_prefix + "finalmask");
|
||||||
|
}
|
||||||
|
params += "#" + encodeURIComponent(v_alias.value);
|
||||||
|
if (params[0] == "&") {
|
||||||
|
params = params.substring(1);
|
||||||
|
}
|
||||||
|
url += params;
|
||||||
|
} else if ((v_type === "sing-box" || v_type === "Xray") && opt.get(dom_prefix + "protocol").value === "socks") {
|
||||||
|
protocol = "socks";
|
||||||
|
var v_username = opt.get(dom_prefix + "username");
|
||||||
|
var v_password = opt.get(dom_prefix + "password");
|
||||||
|
var v_port = opt.get(dom_prefix + "port");
|
||||||
|
url = _address + ":" + v_port.value;
|
||||||
|
if (v_username.value || v_password.value) {
|
||||||
|
url = b64encutf8safe(v_username.value + ":" + v_password.value) + "@" + url;
|
||||||
|
}
|
||||||
|
var params = "";
|
||||||
|
params += opt.query("uot", dom_prefix + "uot");
|
||||||
|
params += opt.query("tfo", dom_prefix + "tcp_fast_open");
|
||||||
|
if (params) {
|
||||||
|
url += "?" + params.substring(1);
|
||||||
|
}
|
||||||
|
url += "#" + encodeURIComponent(v_alias.value);
|
||||||
|
} else if ((v_type === "sing-box" || v_type === "Xray") && opt.get(dom_prefix + "protocol").value === "http") {
|
||||||
|
protocol = opt.get(dom_prefix + "tls").checked ? "https" : "http";
|
||||||
|
var v_username = opt.get(dom_prefix + "username");
|
||||||
|
var v_password = opt.get(dom_prefix + "password");
|
||||||
|
var v_port = opt.get(dom_prefix + "port");
|
||||||
|
url = _address + ":" + v_port.value;
|
||||||
|
if (v_username.value || v_password.value) {
|
||||||
|
url = encodeURIComponent(v_username.value) + ":" + encodeURIComponent(v_password.value) + "@" + url;
|
||||||
|
}
|
||||||
|
var params = "";
|
||||||
|
if (protocol === "https") {
|
||||||
|
params += opt.query("sni", dom_prefix + "tls_serverName");
|
||||||
|
params += opt.query("alpn", dom_prefix + "alpn");
|
||||||
|
params += opt.query("insecure", dom_prefix + "tls_allowInsecure");
|
||||||
|
params += opt.query("pcs", dom_prefix + "tls_pinSHA256");
|
||||||
|
params += opt.query("vcn", dom_prefix + "tls_CertByName");
|
||||||
|
if (opt.get(dom_prefix + "utls")?.checked) {
|
||||||
|
params += opt.query("fp", dom_prefix + "fingerprint");
|
||||||
|
}
|
||||||
|
if (opt.get(dom_prefix + "ech")?.checked) {
|
||||||
|
params += opt.query("ech", dom_prefix + "ech_config");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
params += opt.query("tfo", dom_prefix + "tcp_fast_open");
|
||||||
|
if (params) {
|
||||||
|
url += "?" + params.substring(1);
|
||||||
|
}
|
||||||
|
url += "#" + encodeURIComponent(v_alias.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url) {
|
if (url) {
|
||||||
@@ -1786,6 +1856,126 @@ local current_node = map:get(section)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (link_type === "wireguard") {
|
||||||
|
if (other_type == "sing-box" && has_singbox) {
|
||||||
|
dom_prefix = "singbox_";
|
||||||
|
opt.set('type', "sing-box");
|
||||||
|
} else if (other_type == "xray" && has_xray) {
|
||||||
|
dom_prefix = "xray_";
|
||||||
|
opt.set('type', "Xray");
|
||||||
|
}
|
||||||
|
opt.set(dom_prefix + 'protocol', "wireguard");
|
||||||
|
var info = link_split[1].split('@');
|
||||||
|
var m = parseNodeUrl("wireguard://" + info[1]);
|
||||||
|
var host_port = info[1].split(/[/?#]/)[0];
|
||||||
|
opt.set(dom_prefix + 'address', unbracketIP(m.hostname));
|
||||||
|
opt.set(dom_prefix + 'port', host_port.match(/:(\d+)$/)?.[1] || '51820');
|
||||||
|
opt.set(dom_prefix + 'wireguard_secret_key', decodeURIComponent(info[0]));
|
||||||
|
var queryParam = {};
|
||||||
|
if (m.search.length > 1) {
|
||||||
|
var queryArray = m.search.substr(1).split('&');
|
||||||
|
for (var i = 0; i < queryArray.length; i++) {
|
||||||
|
var index = queryArray[i].indexOf('=');
|
||||||
|
if (index > 0) {
|
||||||
|
queryParam[decodeURIComponent(queryArray[i].substr(0, index)).toLowerCase()] = decodeURIComponent(queryArray[i].substr(index + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opt.set(dom_prefix + 'wireguard_public_key', queryParam.publickey);
|
||||||
|
opt.set(dom_prefix + 'wireguard_preSharedKey', queryParam.presharedkey || '');
|
||||||
|
var addresses = ((queryParam.address || queryParam.ip || '') + ',' + (queryParam.ipv6 || '')).split(/[,\s]+/).filter(Boolean);
|
||||||
|
var fullName = opt.base + '.' + dom_prefix + 'wireguard_local_address';
|
||||||
|
var lists = document.querySelectorAll('.cbi-dynlist');
|
||||||
|
for (var i = 0; i < lists.length; i++) {
|
||||||
|
var label = lists[i].closest('.cbi-value')?.querySelector('label.cbi-value-title');
|
||||||
|
if (label?.getAttribute('for') === fullName) {
|
||||||
|
L.dom.callClassMethod(lists[i], 'setValue', addresses);
|
||||||
|
lists[i].dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opt.set(dom_prefix + 'wireguard_reserved', queryParam.reserved || '');
|
||||||
|
opt.set(dom_prefix + 'wireguard_mtu', queryParam.mtu || '1420');
|
||||||
|
opt.set(dom_prefix + 'wireguard_keepAlive', queryParam['persistent-keepalive'] || queryParam.keepalive || '0');
|
||||||
|
if (dom_prefix === "xray_") {
|
||||||
|
opt.set(dom_prefix + 'use_finalmask', !!queryParam.fm);
|
||||||
|
opt.set(dom_prefix + 'finalmask', queryParam.fm || '');
|
||||||
|
}
|
||||||
|
if (m.hash) {
|
||||||
|
opt.set('remarks', decodeURIComponent(m.hash.substr(1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (link_type === "socks" || link_type === "socks5" || link_type === "socks5h" || link_type === "http" || link_type === "https") {
|
||||||
|
if (other_type == "sing-box" && has_singbox) {
|
||||||
|
dom_prefix = "singbox_";
|
||||||
|
opt.set('type', "sing-box");
|
||||||
|
} else if (other_type == "xray" && has_xray) {
|
||||||
|
dom_prefix = "xray_";
|
||||||
|
opt.set('type', "Xray");
|
||||||
|
}
|
||||||
|
var protocol = (link_type === "http" || link_type === "https") ? "http" : "socks";
|
||||||
|
opt.set(dom_prefix + 'protocol', protocol);
|
||||||
|
var content = link_split[1];
|
||||||
|
var index = content.search(/[?#]/);
|
||||||
|
var suffix = index >= 0 ? content.substr(index) : '';
|
||||||
|
content = index >= 0 ? content.substr(0, index) : content;
|
||||||
|
var base64_auth = false;
|
||||||
|
if (protocol === "socks" && content.indexOf('@') === -1 && content.indexOf(':') === -1) {
|
||||||
|
content = b64decutf8safe(decodeURIComponent(content));
|
||||||
|
base64_auth = true;
|
||||||
|
}
|
||||||
|
var auth = '';
|
||||||
|
var index = content.lastIndexOf('@');
|
||||||
|
if (index >= 0) {
|
||||||
|
auth = content.substr(0, index);
|
||||||
|
content = content.substr(index + 1);
|
||||||
|
}
|
||||||
|
if (protocol === "socks" && auth.indexOf(':') === -1) {
|
||||||
|
auth = b64decutf8safe(decodeURIComponent(auth));
|
||||||
|
base64_auth = true;
|
||||||
|
}
|
||||||
|
var index = auth.indexOf(':');
|
||||||
|
var username = index >= 0 ? auth.substr(0, index) : auth;
|
||||||
|
var password = index >= 0 ? auth.substr(index + 1) : '';
|
||||||
|
if (!base64_auth) {
|
||||||
|
username = decodeURIComponent(username);
|
||||||
|
password = decodeURIComponent(password);
|
||||||
|
}
|
||||||
|
var m = new URL(link_type + "://" + content + suffix);
|
||||||
|
opt.set(dom_prefix + 'address', unbracketIP(m.hostname));
|
||||||
|
opt.set(dom_prefix + 'port', m.port || (protocol === "socks" ? "1080" : (link_type === "https" ? "443" : "80")));
|
||||||
|
opt.set(dom_prefix + 'username', username);
|
||||||
|
opt.set(dom_prefix + 'password', password);
|
||||||
|
opt.set(dom_prefix + 'transport', dom_prefix === "xray_" ? "raw" : "tcp");
|
||||||
|
var queryParam = {};
|
||||||
|
if (m.search.length > 1) {
|
||||||
|
var queryArray = m.search.substr(1).split('&');
|
||||||
|
for (var i = 0; i < queryArray.length; i++) {
|
||||||
|
var index = queryArray[i].indexOf('=');
|
||||||
|
if (index > 0) {
|
||||||
|
queryParam[decodeURIComponent(queryArray[i].substr(0, index)).toLowerCase()] = decodeURIComponent(queryArray[i].substr(index + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opt.set(dom_prefix + 'uot', queryParam.uot === '1');
|
||||||
|
opt.set(dom_prefix + 'tcp_fast_open', queryParam.tfo === '1');
|
||||||
|
opt.set(dom_prefix + 'tls', link_type === "https");
|
||||||
|
opt.set(dom_prefix + 'reality', false);
|
||||||
|
opt.set(dom_prefix + 'tls_serverName', queryParam.sni || '');
|
||||||
|
opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default');
|
||||||
|
opt.set(dom_prefix + 'tls_allowInsecure', (queryParam.allowinsecure || queryParam.insecure || queryParam.allow_insecure || '0') === '1');
|
||||||
|
opt.set(dom_prefix + 'tls_pinSHA256', queryParam.pcs || '');
|
||||||
|
opt.set(dom_prefix + 'tls_CertByName', queryParam.vcn || '');
|
||||||
|
opt.set(dom_prefix + 'utls', !!queryParam.fp);
|
||||||
|
opt.set(dom_prefix + 'fingerprint', queryParam.fp || 'chrome');
|
||||||
|
opt.set(dom_prefix + 'ech', !!queryParam.ech);
|
||||||
|
opt.set(dom_prefix + 'ech_config', queryParam.ech || '');
|
||||||
|
if (m.hash) {
|
||||||
|
opt.set('remarks', decodeURIComponent(m.hash.substr(1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (switch_type) {
|
if (switch_type) {
|
||||||
sessionStorage.setItem("fromUrl", JSON.stringify({
|
sessionStorage.setItem("fromUrl", JSON.stringify({
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
|
|||||||
@@ -604,6 +604,9 @@ msgstr "پنجره دریافت اتصال QUIC"
|
|||||||
msgid "QUIC concurrent bidirectional streams"
|
msgid "QUIC concurrent bidirectional streams"
|
||||||
msgstr "جریانهای دوطرفه همزمان QUIC"
|
msgstr "جریانهای دوطرفه همزمان QUIC"
|
||||||
|
|
||||||
|
msgid "Keep Alive"
|
||||||
|
msgstr "زنده نگه داشتن اتصال"
|
||||||
|
|
||||||
msgid "QUIC KeepAlive interval"
|
msgid "QUIC KeepAlive interval"
|
||||||
msgstr "فاصله QUIC KeepAlive"
|
msgstr "فاصله QUIC KeepAlive"
|
||||||
|
|
||||||
@@ -1369,6 +1372,9 @@ msgstr "زمانی استفاده میشود که لینک گره شامل ا
|
|||||||
msgid "%s Node Use Type"
|
msgid "%s Node Use Type"
|
||||||
msgstr "نوع استفاده گره %s"
|
msgstr "نوع استفاده گره %s"
|
||||||
|
|
||||||
|
msgid "Other"
|
||||||
|
msgstr "سایر"
|
||||||
|
|
||||||
msgid "UDP relay mode"
|
msgid "UDP relay mode"
|
||||||
msgstr "حالت بازپخش UDP"
|
msgstr "حالت بازپخش UDP"
|
||||||
|
|
||||||
|
|||||||
@@ -605,6 +605,9 @@ msgstr "Размер окна приёма соединения QUIC"
|
|||||||
msgid "QUIC concurrent bidirectional streams"
|
msgid "QUIC concurrent bidirectional streams"
|
||||||
msgstr "Макс. количество одновременных двунаправленных потоков QUIC"
|
msgstr "Макс. количество одновременных двунаправленных потоков QUIC"
|
||||||
|
|
||||||
|
msgid "Keep Alive"
|
||||||
|
msgstr "Поддержание соединения"
|
||||||
|
|
||||||
msgid "QUIC KeepAlive interval"
|
msgid "QUIC KeepAlive interval"
|
||||||
msgstr "Интервал QUIC KeepAlive"
|
msgstr "Интервал QUIC KeepAlive"
|
||||||
|
|
||||||
@@ -1370,6 +1373,9 @@ msgstr "Используется, если в ссылке на узел это
|
|||||||
msgid "%s Node Use Type"
|
msgid "%s Node Use Type"
|
||||||
msgstr "Тип использования сервера %s"
|
msgstr "Тип использования сервера %s"
|
||||||
|
|
||||||
|
msgid "Other"
|
||||||
|
msgstr "Другие"
|
||||||
|
|
||||||
msgid "UDP relay mode"
|
msgid "UDP relay mode"
|
||||||
msgstr "Режим ретрансляции UDP"
|
msgstr "Режим ретрансляции UDP"
|
||||||
|
|
||||||
|
|||||||
@@ -596,6 +596,9 @@ msgstr "QUIC 连接接收窗口"
|
|||||||
msgid "QUIC concurrent bidirectional streams"
|
msgid "QUIC concurrent bidirectional streams"
|
||||||
msgstr "QUIC 并发双向流的最大数量"
|
msgstr "QUIC 并发双向流的最大数量"
|
||||||
|
|
||||||
|
msgid "Keep Alive"
|
||||||
|
msgstr "保持连接"
|
||||||
|
|
||||||
msgid "QUIC KeepAlive interval"
|
msgid "QUIC KeepAlive interval"
|
||||||
msgstr "QUIC KeepAlive 间隔"
|
msgstr "QUIC KeepAlive 间隔"
|
||||||
|
|
||||||
@@ -1361,6 +1364,9 @@ msgstr "当节点链接未包含该参数时,将使用此设置。"
|
|||||||
msgid "%s Node Use Type"
|
msgid "%s Node Use Type"
|
||||||
msgstr "%s 节点使用类型"
|
msgstr "%s 节点使用类型"
|
||||||
|
|
||||||
|
msgid "Other"
|
||||||
|
msgstr "其他"
|
||||||
|
|
||||||
msgid "UDP relay mode"
|
msgid "UDP relay mode"
|
||||||
msgstr "UDP中继模式"
|
msgstr "UDP中继模式"
|
||||||
|
|
||||||
|
|||||||
@@ -596,6 +596,9 @@ msgstr "QUIC 連接接收窗口"
|
|||||||
msgid "QUIC concurrent bidirectional streams"
|
msgid "QUIC concurrent bidirectional streams"
|
||||||
msgstr "QUIC 並發双向流的最大數量"
|
msgstr "QUIC 並發双向流的最大數量"
|
||||||
|
|
||||||
|
msgid "Keep Alive"
|
||||||
|
msgstr "保持連線"
|
||||||
|
|
||||||
msgid "QUIC KeepAlive interval"
|
msgid "QUIC KeepAlive interval"
|
||||||
msgstr "QUIC KeepAlive 間隔"
|
msgstr "QUIC KeepAlive 間隔"
|
||||||
|
|
||||||
@@ -1361,6 +1364,9 @@ msgstr "當節點連結未包含該參數時,將使用此設定。"
|
|||||||
msgid "%s Node Use Type"
|
msgid "%s Node Use Type"
|
||||||
msgstr "%s 節點使用類型"
|
msgstr "%s 節點使用類型"
|
||||||
|
|
||||||
|
msgid "Other"
|
||||||
|
msgstr "其他"
|
||||||
|
|
||||||
msgid "UDP relay mode"
|
msgid "UDP relay mode"
|
||||||
msgstr "UDP中继模式"
|
msgstr "UDP中继模式"
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ local DEFAULT_TROJAN_TYPE = api.get_core("trojan_type", {{has_singbox,"sing-box"
|
|||||||
local DEFAULT_VMESS_TYPE = api.get_core("vmess_type", {{has_xray,"xray"},{has_singbox,"sing-box"}})
|
local DEFAULT_VMESS_TYPE = api.get_core("vmess_type", {{has_xray,"xray"},{has_singbox,"sing-box"}})
|
||||||
local DEFAULT_VLESS_TYPE = api.get_core("vless_type", {{has_xray,"xray"},{has_singbox,"sing-box"}})
|
local DEFAULT_VLESS_TYPE = api.get_core("vless_type", {{has_xray,"xray"},{has_singbox,"sing-box"}})
|
||||||
local DEFAULT_HYSTERIA2_TYPE = api.get_core("hysteria2_type", {{has_singbox,"sing-box"},{has_xray,"xray"}})
|
local DEFAULT_HYSTERIA2_TYPE = api.get_core("hysteria2_type", {{has_singbox,"sing-box"},{has_xray,"xray"}})
|
||||||
|
local DEFAULT_OTHER_TYPE = api.get_core("other_type", {{has_singbox,"sing-box"},{has_xray,"xray"}})
|
||||||
local core_has = {
|
local core_has = {
|
||||||
["xray"] = has_xray,
|
["xray"] = has_xray,
|
||||||
["sing-box"] = has_singbox,
|
["sing-box"] = has_singbox,
|
||||||
@@ -536,6 +537,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
|||||||
local sub_vmess_type = DEFAULT_VMESS_TYPE
|
local sub_vmess_type = DEFAULT_VMESS_TYPE
|
||||||
local sub_vless_type = DEFAULT_VLESS_TYPE
|
local sub_vless_type = DEFAULT_VLESS_TYPE
|
||||||
local sub_hysteria2_type = DEFAULT_HYSTERIA2_TYPE
|
local sub_hysteria2_type = DEFAULT_HYSTERIA2_TYPE
|
||||||
|
local sub_other_type = DEFAULT_OTHER_TYPE
|
||||||
local sub_hy_up_mbps, sub_hy_down_mbps
|
local sub_hy_up_mbps, sub_hy_down_mbps
|
||||||
if sub_cfg then
|
if sub_cfg then
|
||||||
if sub_cfg.allowInsecure and sub_cfg.allowInsecure ~= "1" then
|
if sub_cfg.allowInsecure and sub_cfg.allowInsecure ~= "1" then
|
||||||
@@ -561,6 +563,10 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
|||||||
if hysteria2_type ~= "global" and core_has[hysteria2_type] then
|
if hysteria2_type ~= "global" and core_has[hysteria2_type] then
|
||||||
sub_hysteria2_type = hysteria2_type
|
sub_hysteria2_type = hysteria2_type
|
||||||
end
|
end
|
||||||
|
local other_type = sub_cfg.other_type or "global"
|
||||||
|
if other_type ~= "global" and core_has[other_type] then
|
||||||
|
sub_other_type = other_type
|
||||||
|
end
|
||||||
sub_hy_up_mbps = sub_cfg.hysteria_up_mbps
|
sub_hy_up_mbps = sub_cfg.hysteria_up_mbps
|
||||||
sub_hy_down_mbps = sub_cfg.hysteria_down_mbps
|
sub_hy_down_mbps = sub_cfg.hysteria_down_mbps
|
||||||
end
|
end
|
||||||
@@ -1622,6 +1628,115 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
|||||||
result.snell_mode = params.mode or "default"
|
result.snell_mode = params.mode or "default"
|
||||||
end
|
end
|
||||||
result.snell_reuse = (params.reuse == "1") and "1" or "0"
|
result.snell_reuse = (params.reuse == "1") and "1" or "0"
|
||||||
|
elseif szType == "wireguard" then
|
||||||
|
if sub_other_type == "sing-box" and has_singbox then
|
||||||
|
result.type = "sing-box"
|
||||||
|
elseif sub_other_type == "xray" and has_xray then
|
||||||
|
result.type = "Xray"
|
||||||
|
else
|
||||||
|
log(2, i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", "WireGuard", "Xray / sing-box"))
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
result.protocol = "wireguard"
|
||||||
|
|
||||||
|
local alias = ""
|
||||||
|
if content:find("#") then
|
||||||
|
local idx_sp = content:find("#")
|
||||||
|
alias = content:sub(idx_sp + 1, -1)
|
||||||
|
content = content:sub(0, idx_sp - 1)
|
||||||
|
end
|
||||||
|
result.remarks = UrlDecode(alias)
|
||||||
|
local contents = split(content, "@")
|
||||||
|
result.wireguard_secret_key = UrlDecode(contents[1]:gsub("%+", "%%2B"))
|
||||||
|
local query = split(contents[2], "%?")
|
||||||
|
local host_port = query[1]
|
||||||
|
local params = {}
|
||||||
|
for _, v in pairs(split(query[2], '&')) do
|
||||||
|
local s = v:find("=", 1, true)
|
||||||
|
if s and s > 1 then
|
||||||
|
params[UrlDecode(v:sub(1, s - 1)):lower()] = UrlDecode(v:sub(s + 1):gsub("%+", "%%2B"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
result.address, result.port = parse_host_port(host_port, "51820")
|
||||||
|
result.wireguard_public_key = params.publickey
|
||||||
|
result.wireguard_preSharedKey = params.presharedkey
|
||||||
|
result.wireguard_local_address = {}
|
||||||
|
for value in ((params.address or params.ip or "") .. "," .. (params.ipv6 or "")):gmatch("[^,%s]+") do
|
||||||
|
result.wireguard_local_address[#result.wireguard_local_address + 1] = value
|
||||||
|
end
|
||||||
|
result.wireguard_reserved = params.reserved
|
||||||
|
result.wireguard_mtu = params.mtu or "1420"
|
||||||
|
result.wireguard_keepAlive = params["persistent-keepalive"] or params.keepalive or "0"
|
||||||
|
result.use_finalmask = (params.fm and params.fm ~= "") and "1" or nil
|
||||||
|
result.finalmask = (params.fm and params.fm ~= "") and api.base64Encode(params.fm) or nil
|
||||||
|
elseif szType == "socks" or szType == "socks5" or szType == "socks5h" or szType == "http" or szType == "https" then
|
||||||
|
if sub_other_type == "sing-box" and has_singbox then
|
||||||
|
result.type = "sing-box"
|
||||||
|
elseif sub_other_type == "xray" and has_xray then
|
||||||
|
result.type = "Xray"
|
||||||
|
else
|
||||||
|
log(2, i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", szType, "Xray / sing-box"))
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
result.protocol = (szType == "http" or szType == "https") and "http" or "socks"
|
||||||
|
|
||||||
|
local alias = ""
|
||||||
|
if content:find("#") then
|
||||||
|
local idx_sp = content:find("#")
|
||||||
|
alias = content:sub(idx_sp + 1, -1)
|
||||||
|
content = content:sub(0, idx_sp - 1)
|
||||||
|
end
|
||||||
|
result.remarks = UrlDecode(alias)
|
||||||
|
local query = split(content, "%?")
|
||||||
|
local Info = query[1]
|
||||||
|
local base64_auth = false
|
||||||
|
if result.protocol == "socks" and not Info:find("@", 1, true) and not Info:find(":", 1, true) then
|
||||||
|
Info = base64Decode(UrlDecode(Info:gsub("%+", "%%2B")))
|
||||||
|
base64_auth = true
|
||||||
|
end
|
||||||
|
local auth, host_port = Info:match("^(.*)@(.*)$")
|
||||||
|
host_port = host_port or Info
|
||||||
|
auth = auth or ""
|
||||||
|
if result.protocol == "socks" and not auth:find(":", 1, true) then
|
||||||
|
auth = base64Decode(UrlDecode(auth:gsub("%+", "%%2B")))
|
||||||
|
base64_auth = true
|
||||||
|
end
|
||||||
|
local idx = auth:find(":", 1, true)
|
||||||
|
result.username = idx and auth:sub(1, idx - 1) or auth
|
||||||
|
result.password = idx and auth:sub(idx + 1) or ""
|
||||||
|
if not base64_auth then
|
||||||
|
result.username = UrlDecode(result.username:gsub("%+", "%%2B"))
|
||||||
|
result.password = UrlDecode(result.password:gsub("%+", "%%2B"))
|
||||||
|
end
|
||||||
|
local params = {}
|
||||||
|
for _, v in pairs(split(query[2], '&')) do
|
||||||
|
local s = v:find("=", 1, true)
|
||||||
|
if s and s > 1 then
|
||||||
|
params[UrlDecode(v:sub(1, s - 1)):lower()] = UrlDecode(v:sub(s + 1):gsub("%+", "%%2B"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local port = result.protocol == "socks" and "1080" or (szType == "https" and "443" or "80")
|
||||||
|
result.address, result.port = parse_host_port(host_port, port)
|
||||||
|
result.transport = result.type == "Xray" and "raw" or "tcp"
|
||||||
|
result.uot = params.uot
|
||||||
|
result.tcp_fast_open = params.tfo
|
||||||
|
result.tls = szType == "https" and "1" or "0"
|
||||||
|
if result.tls == "1" then
|
||||||
|
result.tls_serverName = params.sni
|
||||||
|
result.alpn = params.alpn
|
||||||
|
local insecure = params.allowinsecure or params.insecure or params.allow_insecure
|
||||||
|
result.tls_allowInsecure = (insecure == "1" or insecure == "0") and insecure or (sub_allowinsecure and "1" or "0")
|
||||||
|
result.tls_pinSHA256 = params.pcs
|
||||||
|
result.tls_CertByName = params.vcn
|
||||||
|
if params.fp and params.fp ~= "" then
|
||||||
|
result.utls = "1"
|
||||||
|
result.fingerprint = params.fp
|
||||||
|
end
|
||||||
|
if params.ech and params.ech ~= "" then
|
||||||
|
result.ech = "1"
|
||||||
|
result.ech_config = params.ech
|
||||||
|
end
|
||||||
|
end
|
||||||
else
|
else
|
||||||
log(2, i18n.translatef("%s type node subscriptions are not currently supported, skip this node.", szType))
|
log(2, i18n.translatef("%s type node subscriptions are not currently supported, skip this node.", szType))
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=luci-app-trafficctl
|
PKG_NAME:=luci-app-trafficctl
|
||||||
PKG_VERSION:=1.30.0
|
PKG_VERSION:=1.30.1
|
||||||
PKG_RELEASE:=27
|
PKG_RELEASE:=28
|
||||||
|
|
||||||
PKG_LICENSE:=Apache-2.0
|
PKG_LICENSE:=Apache-2.0
|
||||||
PKG_MAINTAINER:=Denis Iusupov <yusdyr@gmail.com>
|
PKG_MAINTAINER:=Denis Iusupov <yusdyr@gmail.com>
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ LUCI_DEPENDS:=+luci-base +v2raya
|
|||||||
LUCI_PKGARCH:=all
|
LUCI_PKGARCH:=all
|
||||||
LUCI_MAINTAINER:=Amr Fateem <amr.fateem@yahoo.com>
|
LUCI_MAINTAINER:=Amr Fateem <amr.fateem@yahoo.com>
|
||||||
|
|
||||||
PKG_VERSION:=2.5.13
|
|
||||||
PKG_RELEASE:=1
|
|
||||||
|
|
||||||
PKG_LICENSE:=Apache-2.0
|
PKG_LICENSE:=Apache-2.0
|
||||||
CONFLICTS:=luci-app-v2raya-nextgen
|
CONFLICTS:=luci-app-v2raya-nextgen
|
||||||
|
|||||||
@@ -13,12 +13,33 @@ import { connect } from 'ubus';
|
|||||||
|
|
||||||
import timezones from 'luci.zoneinfo';
|
import timezones from 'luci.zoneinfo';
|
||||||
|
|
||||||
|
// Cache board identity when rpcd loads this provider, not on every poll.
|
||||||
|
const sr1010_build = trim(readfile('/tmp/sysinfo/board_name') || '') == 'zte,zx279133-sr1010' &&
|
||||||
|
match(readfile('/etc/openwrt_release') || '', /DISTRIB_TARGET=['"]zte\/zx279133['"]/) != null;
|
||||||
|
|
||||||
|
function cpu_usage_result(cpuusage) {
|
||||||
|
let result = { cpuusage };
|
||||||
|
if (sr1010_build) {
|
||||||
|
// One fixed-size kernel summary; never enumerate flows or Conntrack.
|
||||||
|
const summary = readfile('/proc/net/nf_flowtable_summary') || '';
|
||||||
|
const hw = match(summary, /(^|[ \t])hw=([0-9]+)([ \t\r\n]|$)/);
|
||||||
|
const sw = match(summary, /(^|[ \t])sw=([0-9]+)([ \t\r\n]|$)/);
|
||||||
|
result.flows = { hw: hw ? +hw[2] : 0, sw: sw ? +sw[2] : 0 };
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function shellquote(s) {
|
function shellquote(s) {
|
||||||
return `'${replace(s, "'", "'\\''")}'`;
|
return `'${replace(s, "'", "'\\''")}'`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function has_supported_hw_offloading_target(target) {
|
function has_supported_hw_offloading_target(target, board) {
|
||||||
|
if (target == 'zte/zx279133' && board == 'zte,zx279133-sr1010')
|
||||||
|
return access('/sys/module/npu') == true &&
|
||||||
|
access('/sys/module/fnoffload') == true &&
|
||||||
|
length(glob('/sys/bus/platform/drivers/zx279133-eth/*/driver')) > 0;
|
||||||
|
|
||||||
if (target == 'ipq40xx/generic')
|
if (target == 'ipq40xx/generic')
|
||||||
return access('/sys/module/qca_ssdk') == true &&
|
return access('/sys/module/qca_ssdk') == true &&
|
||||||
access('/sys/module/essedma') == true;
|
access('/sys/module/essedma') == true;
|
||||||
@@ -241,7 +262,7 @@ const methods = {
|
|||||||
const has_mt7621_vendor_hnat = sysboard?.release?.target == 'ramips/mt7621' &&
|
const has_mt7621_vendor_hnat = sysboard?.release?.target == 'ramips/mt7621' &&
|
||||||
access('/sys/module/mtkhnat') == true;
|
access('/sys/module/mtkhnat') == true;
|
||||||
const has_hw_offloading = !has_mt7621_vendor_hnat &&
|
const has_hw_offloading = !has_mt7621_vendor_hnat &&
|
||||||
has_supported_hw_offloading_target(sysboard?.release?.target) &&
|
has_supported_hw_offloading_target(sysboard?.release?.target, sysboard?.board_name) &&
|
||||||
((has_fw3 && has_ipt_flowoffload) || (has_fw4 && has_nft_flowoffload));
|
((has_fw3 && has_ipt_flowoffload) || (has_fw4 && has_nft_flowoffload));
|
||||||
|
|
||||||
if (ubus)
|
if (ubus)
|
||||||
@@ -255,6 +276,8 @@ const methods = {
|
|||||||
mii_tool: access('/usr/sbin/mii-tool'),
|
mii_tool: access('/usr/sbin/mii-tool'),
|
||||||
offloading: (has_fw3 && has_ipt_flowoffload) || (has_fw4 && has_nft_flowoffload),
|
offloading: (has_fw3 && has_ipt_flowoffload) || (has_fw4 && has_nft_flowoffload),
|
||||||
offloading_hw: has_hw_offloading,
|
offloading_hw: has_hw_offloading,
|
||||||
|
zte_npu: sysboard?.release?.target == 'zte/zx279133' &&
|
||||||
|
sysboard?.board_name == 'zte,zx279133-sr1010',
|
||||||
fullcone: access('/sys/module/xt_FULLCONENAT/refcnt') == true || access('/sys/module/nft_fullcone/refcnt') == true,
|
fullcone: access('/sys/module/xt_FULLCONENAT/refcnt') == true || access('/sys/module/nft_fullcone/refcnt') == true,
|
||||||
br2684ctl: access('/usr/sbin/br2684ctl') == true,
|
br2684ctl: access('/usr/sbin/br2684ctl') == true,
|
||||||
swconfig: access('/sbin/swconfig') == true,
|
swconfig: access('/sbin/swconfig') == true,
|
||||||
@@ -774,18 +797,18 @@ const methods = {
|
|||||||
fd.close();
|
fd.close();
|
||||||
|
|
||||||
if (length(cpuusage))
|
if (length(cpuusage))
|
||||||
return { cpuusage };
|
return cpu_usage_result(cpuusage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fd = popen('top -n1 | awk \'/^CPU/ {printf("%d%", 100 - $8)}\'');
|
const fd = popen('top -n1 | awk \'/^CPU/ {printf("%d%", 100 - $8)}\'');
|
||||||
if (!fd)
|
if (!fd)
|
||||||
return { cpuusage: error() };
|
return cpu_usage_result(error());
|
||||||
|
|
||||||
let cpuusage = trim(fd.read('all')) || '?';
|
let cpuusage = trim(fd.read('all')) || '?';
|
||||||
fd.close();
|
fd.close();
|
||||||
|
|
||||||
return { cpuusage };
|
return cpu_usage_result(cpuusage);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
LUCI_TITLE:=LuCI Status Pages
|
LUCI_TITLE:=LuCI Status Pages
|
||||||
LUCI_DEPENDS:=+luci-base +luci-app-oui +luci-app-vantage +libiwinfo +rpcd-mod-iwinfo +rpcd-mod-rrdns +ucode +ucode-mod-fs +ucode-mod-ubus +ucode-mod-socket
|
LUCI_DEPENDS:=+luci-base +luci-app-oui +luci-app-vantage +libiwinfo +rpcd-mod-iwinfo +rpcd-mod-rrdns +ucode +ucode-mod-fs +ucode-mod-ubus +ucode-mod-socket
|
||||||
|
|
||||||
PKG_RELEASE:=43
|
PKG_RELEASE:=44
|
||||||
PKG_BUILD_DEPENDS:=iwinfo
|
PKG_BUILD_DEPENDS:=iwinfo
|
||||||
PKG_LICENSE:=Apache-2.0
|
PKG_LICENSE:=Apache-2.0
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,11 @@ return baseclass.extend({
|
|||||||
|
|
||||||
luciversion = luciversion.branch;
|
luciversion = luciversion.branch;
|
||||||
|
|
||||||
|
var cpuUsageText = cpuusage.cpuusage;
|
||||||
|
if (L.hasSystemFeature('zte_npu'))
|
||||||
|
cpuUsageText = (cpuUsageText || '?') + ', NPU: ' + (cpuusage.flows?.hw || 0) +
|
||||||
|
', Flow: ' + (cpuusage.flows?.sw || 0);
|
||||||
|
|
||||||
var datestr = null;
|
var datestr = null;
|
||||||
|
|
||||||
if (unixtime) {
|
if (unixtime) {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=luci-theme-vantage
|
PKG_NAME:=luci-theme-vantage
|
||||||
PKG_VERSION:=1.0.17
|
PKG_VERSION:=1.0.17
|
||||||
PKG_RELEASE:=39
|
PKG_RELEASE:=40
|
||||||
PKG_LICENSE:=GPL-3.0-or-later
|
PKG_LICENSE:=GPL-3.0-or-later
|
||||||
|
|
||||||
LUCI_TITLE:=Vantage theme
|
LUCI_TITLE:=Vantage theme
|
||||||
|
|||||||
@@ -2656,7 +2656,7 @@ html:not([data-darkmode="true"]) .vt-login-page::before {
|
|||||||
.vt-login-head { display: flex; align-items: center; gap: var(--sp-3); margin-bottom: var(--sp-6); }
|
.vt-login-head { display: flex; align-items: center; gap: var(--sp-3); margin-bottom: var(--sp-6); }
|
||||||
.vt-login-mark { flex: none; }
|
.vt-login-mark { flex: none; }
|
||||||
.vt-login-title { margin: 0; font-size: var(--fs-lg); font-weight: 600; letter-spacing: -.01em; overflow-wrap: anywhere; }
|
.vt-login-title { margin: 0; font-size: var(--fs-lg); font-weight: 600; letter-spacing: -.01em; overflow-wrap: anywhere; }
|
||||||
.vt-login-error {
|
.vt-login-error, .vt-login-info {
|
||||||
margin: 0 0 var(--sp-4);
|
margin: 0 0 var(--sp-4);
|
||||||
padding: var(--sp-2) var(--sp-3);
|
padding: var(--sp-2) var(--sp-3);
|
||||||
border: 1px solid color-mix(in srgb, var(--err) 45%, transparent);
|
border: 1px solid color-mix(in srgb, var(--err) 45%, transparent);
|
||||||
@@ -2665,6 +2665,11 @@ html:not([data-darkmode="true"]) .vt-login-page::before {
|
|||||||
border-radius: var(--r);
|
border-radius: var(--r);
|
||||||
font-size: var(--fs-sm);
|
font-size: var(--fs-sm);
|
||||||
}
|
}
|
||||||
|
.vt-login-info {
|
||||||
|
border-color: color-mix(in srgb, var(--info) 45%, transparent);
|
||||||
|
border-left-color: var(--info);
|
||||||
|
background: var(--info-soft);
|
||||||
|
}
|
||||||
.vt-login-form { display: flex; flex-direction: column; gap: var(--sp-4); }
|
.vt-login-form { display: flex; flex-direction: column; gap: var(--sp-4); }
|
||||||
.vt-field { display: flex; flex-direction: column; gap: 6px; }
|
.vt-field { display: flex; flex-direction: column; gap: 6px; }
|
||||||
.vt-field-label { color: var(--text-3); font-size: var(--fs-micro); font-weight: 600; letter-spacing: .08em; text-transform: uppercase; }
|
.vt-field-label { color: var(--text-3); font-size: var(--fs-micro); font-weight: 600; letter-spacing: .08em; text-transform: uppercase; }
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ msgctxt "vantage-theme"
|
|||||||
msgid "Appearance"
|
msgid "Appearance"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: luci-theme-vantage/ucode/template/themes/vantage/sysauth.ut:50
|
#: luci-theme-vantage/ucode/template/themes/vantage/sysauth.ut:52
|
||||||
msgid "Authorization Required"
|
msgid "Authorization Required"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -296,7 +296,7 @@ msgctxt "vantage-theme"
|
|||||||
msgid "Input"
|
msgid "Input"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: luci-theme-vantage/ucode/template/themes/vantage/sysauth.ut:54
|
#: luci-theme-vantage/ucode/template/themes/vantage/sysauth.ut:56
|
||||||
msgid "Invalid username and/or password! Please try again."
|
msgid "Invalid username and/or password! Please try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -314,8 +314,8 @@ msgctxt "vantage-theme"
|
|||||||
msgid "Light"
|
msgid "Light"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: luci-theme-vantage/ucode/template/themes/vantage/sysauth.ut:30
|
#: luci-theme-vantage/ucode/template/themes/vantage/sysauth.ut:32
|
||||||
#: luci-theme-vantage/ucode/template/themes/vantage/sysauth.ut:66
|
#: luci-theme-vantage/ucode/template/themes/vantage/sysauth.ut:85
|
||||||
msgid "Log in"
|
msgid "Log in"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -441,7 +441,7 @@ msgctxt "vantage-theme"
|
|||||||
msgid "Passes unmatched"
|
msgid "Passes unmatched"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: luci-theme-vantage/ucode/template/themes/vantage/sysauth.ut:63
|
#: luci-theme-vantage/ucode/template/themes/vantage/sysauth.ut:65
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -590,7 +590,7 @@ msgstr ""
|
|||||||
msgid "Used"
|
msgid "Used"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: luci-theme-vantage/ucode/template/themes/vantage/sysauth.ut:59
|
#: luci-theme-vantage/ucode/template/themes/vantage/sysauth.ut:61
|
||||||
msgid "Username"
|
msgid "Username"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ msgctxt "vantage-theme"
|
|||||||
msgid "Appearance"
|
msgid "Appearance"
|
||||||
msgstr "外观"
|
msgstr "外观"
|
||||||
|
|
||||||
#: luci-theme-vantage/ucode/template/themes/vantage/sysauth.ut:50
|
#: luci-theme-vantage/ucode/template/themes/vantage/sysauth.ut:52
|
||||||
msgid "Authorization Required"
|
msgid "Authorization Required"
|
||||||
msgstr "需要授权"
|
msgstr "需要授权"
|
||||||
|
|
||||||
@@ -307,7 +307,7 @@ msgctxt "vantage-theme"
|
|||||||
msgid "Input"
|
msgid "Input"
|
||||||
msgstr "输入"
|
msgstr "输入"
|
||||||
|
|
||||||
#: luci-theme-vantage/ucode/template/themes/vantage/sysauth.ut:54
|
#: luci-theme-vantage/ucode/template/themes/vantage/sysauth.ut:56
|
||||||
msgid "Invalid username and/or password! Please try again."
|
msgid "Invalid username and/or password! Please try again."
|
||||||
msgstr "用户名或密码错误,请重试。"
|
msgstr "用户名或密码错误,请重试。"
|
||||||
|
|
||||||
@@ -325,8 +325,8 @@ msgctxt "vantage-theme"
|
|||||||
msgid "Light"
|
msgid "Light"
|
||||||
msgstr "浅色"
|
msgstr "浅色"
|
||||||
|
|
||||||
#: luci-theme-vantage/ucode/template/themes/vantage/sysauth.ut:30
|
#: luci-theme-vantage/ucode/template/themes/vantage/sysauth.ut:32
|
||||||
#: luci-theme-vantage/ucode/template/themes/vantage/sysauth.ut:66
|
#: luci-theme-vantage/ucode/template/themes/vantage/sysauth.ut:85
|
||||||
msgid "Log in"
|
msgid "Log in"
|
||||||
msgstr "登录"
|
msgstr "登录"
|
||||||
|
|
||||||
@@ -452,7 +452,7 @@ msgctxt "vantage-theme"
|
|||||||
msgid "Passes unmatched"
|
msgid "Passes unmatched"
|
||||||
msgstr "放行未匹配流量"
|
msgstr "放行未匹配流量"
|
||||||
|
|
||||||
#: luci-theme-vantage/ucode/template/themes/vantage/sysauth.ut:63
|
#: luci-theme-vantage/ucode/template/themes/vantage/sysauth.ut:65
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "密码"
|
msgstr "密码"
|
||||||
|
|
||||||
@@ -600,7 +600,7 @@ msgstr "使用浅色主题"
|
|||||||
msgid "Used"
|
msgid "Used"
|
||||||
msgstr "已用"
|
msgstr "已用"
|
||||||
|
|
||||||
#: luci-theme-vantage/ucode/template/themes/vantage/sysauth.ut:59
|
#: luci-theme-vantage/ucode/template/themes/vantage/sysauth.ut:61
|
||||||
msgid "Username"
|
msgid "Username"
|
||||||
msgstr "用户名"
|
msgstr "用户名"
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
const look_theme = look('theme', [ 'auto', 'light', 'dark' ]);
|
const look_theme = look('theme', [ 'auto', 'light', 'dark' ]);
|
||||||
const look_login = look('login', [ 'grid', 'stars', 'none' ]);
|
const look_login = look('login', [ 'grid', 'stars', 'none' ]);
|
||||||
const host = (() => { const h = uci.get_first('system', 'system', 'hostname'); return (type(h) == 'string' && length(h)) ? substr(h, 0, 63) : null; })();
|
const host = (() => { const h = uci.get_first('system', 'system', 'hostname'); return (type(h) == 'string' && length(h)) ? substr(h, 0, 63) : null; })();
|
||||||
|
// A pending challenge is guidance until the user submits a response.
|
||||||
|
const auth_failed = auth_plugin && length(filter(auth_fields ?? [], field => length(http.formvalue(field.name) ?? '')));
|
||||||
-%}
|
-%}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{{ entityencode(dispatcher.lang ?? 'en', true) }}" data-darkmode="true" data-vt-look-theme="{{ look_theme }}" data-vt-look-login="{{ look_login }}">
|
<html lang="{{ entityencode(dispatcher.lang ?? 'en', true) }}" data-darkmode="true" data-vt-look-theme="{{ look_theme }}" data-vt-look-login="{{ look_login }}">
|
||||||
@@ -57,27 +59,44 @@
|
|||||||
<h1 class="vt-login-title">{{ entityencode(host ?? _('Authorization Required'), true) }}</h1>
|
<h1 class="vt-login-title">{{ entityencode(host ?? _('Authorization Required'), true) }}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if (fuser): %}
|
{% if (fuser && !auth_plugin): %}
|
||||||
<div class="vt-login-error" role="alert">{{ entityencode(_('Invalid username and/or password! Please try again.'), true) }}</div>
|
<div class="vt-login-error" role="alert">{{ entityencode(_('Invalid username and/or password! Please try again.'), true) }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<form method="post" class="vt-login-form" data-https-ports="{{ entityencode(sprintf('%J', tls_ports), true) }}" data-https-probe="{{ entityencode(resource + '/icons/loading.svg', true) }}">
|
<form method="post" class="vt-login-form" data-https-ports="{{ entityencode(sprintf('%J', tls_ports), true) }}" data-https-probe="{{ entityencode(resource + '/icons/loading.svg', true) }}">
|
||||||
<label class="vt-field">
|
<label class="vt-field" for="luci_username">
|
||||||
<span class="vt-field-label">{{ entityencode(_('Username'), true) }}</span>
|
<span class="vt-field-label">{{ entityencode(_('Username'), true) }}</span>
|
||||||
<input class="cbi-input-text" type="text" name="luci_username" autocomplete="username" autocapitalize="off" spellcheck="false" required value="{{ entityencode(duser ?? '', true) }}">
|
<input class="cbi-input-text" id="luci_username" type="text" name="luci_username" autocomplete="username" autocapitalize="off" spellcheck="false" required value="{{ entityencode(duser ?? '', true) }}">
|
||||||
</label>
|
</label>
|
||||||
<label class="vt-field">
|
<label class="vt-field" for="luci_password">
|
||||||
<span class="vt-field-label">{{ entityencode(_('Password'), true) }}</span>
|
<span class="vt-field-label">{{ entityencode(_('Password'), true) }}</span>
|
||||||
<input class="cbi-input-password" type="password" name="luci_password" autocomplete="current-password" autofocus>
|
<input class="cbi-input-password" id="luci_password" type="password" name="luci_password" autocomplete="current-password">
|
||||||
</label>
|
</label>
|
||||||
|
{% if (auth_fields): %}
|
||||||
|
{% for (let field in auth_fields): %}
|
||||||
|
<label class="vt-field" for="{{ entityencode(field.name, true) }}">
|
||||||
|
<span class="vt-field-label">{{ entityencode(_(field.label ?? field.name), true) }}</span>
|
||||||
|
<input class="cbi-input-text" id="{{ entityencode(field.name, true) }}" type="{{ entityencode(field.type ?? 'text', true) }}" name="{{ entityencode(field.name, true) }}"
|
||||||
|
{% if (field.placeholder): %}placeholder="{{ entityencode(field.placeholder, true) }}"{% endif %}
|
||||||
|
{% if (field.inputmode): %}inputmode="{{ entityencode(field.inputmode, true) }}"{% endif %}
|
||||||
|
{% if (field.pattern): %}pattern="{{ entityencode(field.pattern, true) }}"{% endif %}
|
||||||
|
{% if (field.maxlength): %}maxlength="{{ entityencode('' + field.maxlength, true) }}"{% endif %}
|
||||||
|
{% if (field.autocomplete): %}autocomplete="{{ entityencode(field.autocomplete, true) }}"{% endif %}
|
||||||
|
{% if (field.required): %}required{% endif %}>
|
||||||
|
</label>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if (auth_message): %}
|
||||||
|
<div class="{{ auth_failed ? 'vt-login-error' : 'vt-login-info' }}" role="{{ auth_failed ? 'alert' : 'status' }}">{{ entityencode(auth_message, true) }}</div>
|
||||||
|
{% endif %}
|
||||||
<button type="submit" class="btn cbi-button cbi-button-apply vt-login-submit">{{ entityencode(_('Log in'), true) }}</button>
|
<button type="submit" class="btn cbi-button cbi-button-apply vt-login-submit">{{ entityencode(_('Log in'), true) }}</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<script>
|
<script>
|
||||||
(function (form) {
|
(function (form) {
|
||||||
var pw = document.querySelector('input[name="luci_password"]');
|
var input = form && form.querySelector('input[name="luci_password"]');
|
||||||
if (pw) pw.focus();
|
if (input) input.focus();
|
||||||
if (!form || location.protocol === 'https:') return;
|
if (!form || location.protocol === 'https:') return;
|
||||||
var ports = [];
|
var ports = [];
|
||||||
try { ports = JSON.parse(form.getAttribute('data-https-ports') || '[]'); } catch (e) {}
|
try { ports = JSON.parse(form.getAttribute('data-https-ports') || '[]'); } catch (e) {}
|
||||||
|
|||||||
+190
@@ -0,0 +1,190 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
#
|
||||||
|
# Copyright (C) 2021 ImmortalWrt.org
|
||||||
|
# Copyright (C) 2026 Amr Fateem <amr.fateem@yahoo.com>
|
||||||
|
#
|
||||||
|
# v2rayA OpenWrt packaging.
|
||||||
|
#
|
||||||
|
# The structure follows the packaging work by Tianling Shen
|
||||||
|
# <cnsztl@immortalwrt.org> in the v2raya-openwrt feed and the OpenWrt
|
||||||
|
# packages feed, updated for the 2.5.x layout where the service and the
|
||||||
|
# matching v2raya_core binary are separate Go modules.
|
||||||
|
|
||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
|
PKG_NAME:=v2raya
|
||||||
|
PKG_VERSION:=2.5.14
|
||||||
|
PKG_RELEASE:=3
|
||||||
|
|
||||||
|
PKG_SOURCE:=v2rayA-$(PKG_VERSION).tar.gz
|
||||||
|
PKG_SOURCE_URL:=https://codeload.github.com/v2rayA/v2rayA/tar.gz/v$(PKG_VERSION)?
|
||||||
|
PKG_HASH:=skip
|
||||||
|
|
||||||
|
PKG_BUILD_DIR:=$(BUILD_DIR)/v2rayA-$(PKG_VERSION)/service
|
||||||
|
|
||||||
|
PKG_LICENSE:=AGPL-3.0-only
|
||||||
|
PKG_LICENSE_FILES:=../LICENSE
|
||||||
|
PKG_MAINTAINER:=Amr Fateem <amr.fateem@yahoo.com>
|
||||||
|
|
||||||
|
PKG_BUILD_DEPENDS:=golang/host
|
||||||
|
PKG_BUILD_PARALLEL:=1
|
||||||
|
PKG_BUILD_FLAGS:=no-mips16
|
||||||
|
|
||||||
|
# v2rayA 2.5.x needs Go 1.26+ and v2raya_core needs Go 1.27+. The host Go
|
||||||
|
# shipped by older OpenWrt releases is older than that, so let GOTOOLCHAIN
|
||||||
|
# fetch the matching toolchain from proxy.golang.org during the build.
|
||||||
|
# scripts/update-version.sh refreshes this after a major Go bump.
|
||||||
|
V2RAYA_GO_TOOLCHAIN?=go1.27.3+auto
|
||||||
|
|
||||||
|
GO_PKG:=github.com/v2rayA/v2rayA
|
||||||
|
GO_PKG_BUILD_PKG:=github.com/v2rayA/v2rayA
|
||||||
|
GO_PKG_LDFLAGS_X:= \
|
||||||
|
$(GO_PKG)/conf.Version=$(PKG_VERSION) \
|
||||||
|
$(GO_PKG)/kernel/iptables.TproxyNotSkipBr=true
|
||||||
|
|
||||||
|
include $(INCLUDE_DIR)/package.mk
|
||||||
|
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
|
||||||
|
|
||||||
|
# v2rayA's SQLite layer has no pure-Go driver for mips* or loong64. Those
|
||||||
|
# targets must use cgo (and therefore the target C toolchain); all other
|
||||||
|
# targets get fully static, CGO-free binaries so one build runs on every
|
||||||
|
# OpenWrt release with the same CPU architecture.
|
||||||
|
V2RAYA_CGO:=0
|
||||||
|
ifneq ($(filter mips% loong64,$(GO_ARCH)),)
|
||||||
|
V2RAYA_CGO:=1
|
||||||
|
endif
|
||||||
|
|
||||||
|
GO_PKG_TARGET_VARS:=$(filter-out CGO_ENABLED=%,$(GO_PKG_TARGET_VARS)) CGO_ENABLED=$(V2RAYA_CGO)
|
||||||
|
ifeq ($(V2RAYA_CGO),0)
|
||||||
|
GO_PKG_DEFAULT_LDFLAGS:=$(filter-out -linkmode external,$(GO_PKG_DEFAULT_LDFLAGS))
|
||||||
|
endif
|
||||||
|
GO_PKG_BUILD_VARS:=$(filter-out GOTOOLCHAIN=%,$(GO_PKG_BUILD_VARS)) GOTOOLCHAIN=$(V2RAYA_GO_TOOLCHAIN)
|
||||||
|
|
||||||
|
TAR_OPTIONS+= --strip-components 1
|
||||||
|
TAR_CMD=$(HOST_TAR) -C $(1)/.. $(TAR_OPTIONS)
|
||||||
|
|
||||||
|
# geoip.dat and geosite.dat are intentionally not hard dependencies: v2rayA
|
||||||
|
# downloads missing files on first start. Install v2raya-geoip and
|
||||||
|
# v2raya-geosite for offline/package-managed data.
|
||||||
|
define Package/v2raya
|
||||||
|
TITLE:=v2rayA - web client for its own Xray-based core
|
||||||
|
SECTION:=net
|
||||||
|
CATEGORY:=Network
|
||||||
|
SUBMENU:=Web Servers/Proxies
|
||||||
|
DEPENDS:=$(GO_ARCH_DEPENDS) \
|
||||||
|
+v2raya-core \
|
||||||
|
+ca-bundle \
|
||||||
|
+nftables \
|
||||||
|
+kmod-nft-tproxy \
|
||||||
|
+ip-full \
|
||||||
|
+kmod-tun
|
||||||
|
CONFLICTS:=v2raya-nextgen
|
||||||
|
URL:=https://github.com/v2rayA/v2rayA
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/v2raya/description
|
||||||
|
v2rayA is a web client for its own Xray-based core. It imports
|
||||||
|
subscriptions and share links, selects the fastest node, and provides
|
||||||
|
transparent proxy on Linux.
|
||||||
|
|
||||||
|
This package tracks the current upstream 2.5.x release. It installs the
|
||||||
|
service together with the matching v2raya_core binary.
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/v2raya/conffiles
|
||||||
|
/etc/config/v2raya
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/v2raya/postinst
|
||||||
|
#!/bin/sh
|
||||||
|
[ -n "$${IPKG_INSTROOT}" ] || {
|
||||||
|
mkdir -p /etc/v2raya /var/log/v2raya
|
||||||
|
chmod 750 /etc/v2raya
|
||||||
|
chmod 755 /var/log/v2raya
|
||||||
|
/etc/init.d/v2raya enable
|
||||||
|
if [ "$$(uci -q get v2raya.config.enabled)" = "1" ]; then
|
||||||
|
/etc/init.d/v2raya restart
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/v2raya/prerm
|
||||||
|
#!/bin/sh
|
||||||
|
[ -n "$${IPKG_INSTROOT}" ] || {
|
||||||
|
[ "$$1" = "upgrade" ] && exit 0
|
||||||
|
/etc/init.d/v2raya stop
|
||||||
|
/etc/init.d/v2raya disable
|
||||||
|
}
|
||||||
|
endef
|
||||||
|
|
||||||
|
# v2rayA publishes the built web UI as web.tar.gz on its releases. The
|
||||||
|
# GitHub release CDN has been returning 504/404 for it, which makes every
|
||||||
|
# matrix build fail. Keep the 871 KiB asset in the package instead; the
|
||||||
|
# hash of the upstream v2.5.13 asset is
|
||||||
|
# 76774f195ee74604fbc43a48fe74e5f3363e81ec68af02d1c26feeeacc0f63ae.
|
||||||
|
|
||||||
|
define Build/Prepare
|
||||||
|
$(call Build/Prepare/Default)
|
||||||
|
|
||||||
|
# OpenWrt's LAN is a bridge (br-lan). v2rayA 2.5.x defaults to excluding
|
||||||
|
# br-* from transparent proxy, which silently bypasses every LAN client.
|
||||||
|
# The old OpenWrt package removed that exclusion with TproxyNotSkipBr;
|
||||||
|
# 2.5.x moved it into this stored default, so strip it here as well.
|
||||||
|
$(SED) 's/,br-\*"/"/' $(PKG_BUILD_DIR)/db/configure/setting.go
|
||||||
|
|
||||||
|
# v2rayA 2.2.7 (the old OpenWrt package) did not hijack DNS. 2.5.x turns
|
||||||
|
# DNS interception on by default and sends queries through the proxy. With
|
||||||
|
# a vless/ws or other UDP-incapable node this breaks all client DNS, so the
|
||||||
|
# OpenWrt default stays off. Users can still enable it in the web UI.
|
||||||
|
$(SED) 's/DnsHijack: *Yes,/DnsHijack: No,/' \
|
||||||
|
$(PKG_BUILD_DIR)/db/configure/setting.go
|
||||||
|
$(SED) '/if setting.DnsHijack == ""/,/}/ s/setting.DnsHijack = Yes/setting.DnsHijack = No/' \
|
||||||
|
$(PKG_BUILD_DIR)/db/configure/migrate_dns.go
|
||||||
|
|
||||||
|
# Router defaults: warn-level logging reduces flash writes, QUIC
|
||||||
|
# sniffing is pointless when the LAN QUIC block is in place, and the
|
||||||
|
# DNS cache is bounded for the case where a user enables the DNS
|
||||||
|
# module later.
|
||||||
|
$(SED) 's/LogLevel: *"info"/LogLevel: "warn"/' \
|
||||||
|
$(PKG_BUILD_DIR)/db/configure/setting.go
|
||||||
|
$(SED) 's/InboundSniffing: *"http,tls,quic"/InboundSniffing: "http,tls"/' \
|
||||||
|
$(PKG_BUILD_DIR)/db/configure/setting.go
|
||||||
|
$(SED) 's/DnsCacheSize: *4096/DnsCacheSize: 1024/' \
|
||||||
|
$(PKG_BUILD_DIR)/db/configure/setting.go
|
||||||
|
$(SED) 's/DnsPrefetch: *true/DnsPrefetch: false/' \
|
||||||
|
$(PKG_BUILD_DIR)/db/configure/setting.go
|
||||||
|
$(SED) 's/DnsNegativeCache: *true/DnsNegativeCache: false/' \
|
||||||
|
$(PKG_BUILD_DIR)/db/configure/setting.go
|
||||||
|
|
||||||
|
( \
|
||||||
|
mkdir -p $(PKG_BUILD_DIR)/server/router/web ; \
|
||||||
|
gzip -dc $(CURDIR)/files/web.tar.gz | $(HOST_TAR) -C $(PKG_BUILD_DIR)/server/router/web $(TAR_OPTIONS) ; \
|
||||||
|
)
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/v2raya/install
|
||||||
|
$(call GoPackage/Package/Install/Bin,$(PKG_INSTALL_DIR))
|
||||||
|
|
||||||
|
$(INSTALL_DIR) $(1)/usr/bin
|
||||||
|
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/v2rayA $(1)/usr/bin/v2raya
|
||||||
|
|
||||||
|
$(INSTALL_DIR) $(1)/etc/config
|
||||||
|
$(INSTALL_CONF) $(CURDIR)/files/v2raya.config $(1)/etc/config/v2raya
|
||||||
|
|
||||||
|
$(INSTALL_DIR) $(1)/etc/init.d
|
||||||
|
$(INSTALL_BIN) $(CURDIR)/files/v2raya.init $(1)/etc/init.d/v2raya
|
||||||
|
|
||||||
|
$(INSTALL_DIR) $(1)/usr/libexec
|
||||||
|
$(INSTALL_BIN) $(CURDIR)/files/v2raya-detect $(1)/usr/libexec/v2raya-detect
|
||||||
|
|
||||||
|
# Keep the database and the web password across sysupgrade. Listing
|
||||||
|
# /etc/v2raya as an opkg conffile copies the whole directory and has
|
||||||
|
# broken real upgrades.
|
||||||
|
$(INSTALL_DIR) $(1)/lib/upgrade/keep.d
|
||||||
|
$(INSTALL_DATA) $(CURDIR)/files/v2raya.keep $(1)/lib/upgrade/keep.d/v2raya
|
||||||
|
|
||||||
|
$(INSTALL_DIR) $(1)/usr/share/v2raya
|
||||||
|
endef
|
||||||
|
|
||||||
|
$(eval $(call GoBinPackage,v2raya))
|
||||||
|
$(eval $(call BuildPackage,v2raya))
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
#
|
||||||
|
# Copyright (C) 2026 Amr Fateem <amr.fateem@yahoo.com>
|
||||||
|
#
|
||||||
|
# Emit a small key=value report about installed proxy cores and services.
|
||||||
|
# Called by the LuCI overview; values are single-line and contain no newlines.
|
||||||
|
#
|
||||||
|
# This script is read-only. It never installs, removes, starts or stops
|
||||||
|
# anything. It exists so the UI can show that an existing Xray-core or
|
||||||
|
# sing-box installation is present and will not be duplicated or replaced.
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
first_bin() {
|
||||||
|
for path in "$@"; do
|
||||||
|
if [ -x "$path" ]; then
|
||||||
|
printf '%s' "$path"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
version_line() {
|
||||||
|
bin="$1"
|
||||||
|
shift
|
||||||
|
[ -n "$bin" ] || return 0
|
||||||
|
"$bin" "$@" 2>/dev/null | sed -n '1p' | tr -d '\r'
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg_list=""
|
||||||
|
if command -v opkg >/dev/null 2>&1; then
|
||||||
|
pkg_list=$(opkg list-installed 2>/dev/null | awk '{ print $1 }')
|
||||||
|
elif command -v apk >/dev/null 2>&1; then
|
||||||
|
pkg_list=$(apk info 2>/dev/null)
|
||||||
|
fi
|
||||||
|
|
||||||
|
pkg_has() {
|
||||||
|
printf '%s\n' "$pkg_list" | grep -qx "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
core_bin=$(first_bin /usr/bin/v2raya_core /usr/sbin/v2raya_core) || core_bin=""
|
||||||
|
xray_bin=$(first_bin /usr/bin/xray /usr/sbin/xray /usr/bin/xray-core) || xray_bin=""
|
||||||
|
singbox_bin=$(first_bin /usr/bin/sing-box /usr/sbin/sing-box /usr/bin/singbox /usr/sbin/singbox) || singbox_bin=""
|
||||||
|
v2ray_bin=$(first_bin /usr/bin/v2ray /usr/sbin/v2ray /usr/bin/v2ray-core) || v2ray_bin=""
|
||||||
|
|
||||||
|
xray_pkg=0
|
||||||
|
pkg_has xray-core && xray_pkg=1
|
||||||
|
pkg_has xray && xray_pkg=1
|
||||||
|
singbox_pkg=0
|
||||||
|
pkg_has sing-box && singbox_pkg=1
|
||||||
|
v2ray_pkg=0
|
||||||
|
pkg_has v2ray-core && v2ray_pkg=1
|
||||||
|
pkg_has v2ray && v2ray_pkg=1
|
||||||
|
pkg_version() {
|
||||||
|
if command -v opkg >/dev/null 2>&1; then
|
||||||
|
opkg list-installed "$1" 2>/dev/null | awk '{ print $3 }' | sed -n '1p'
|
||||||
|
elif [ -f /lib/apk/db/installed ]; then
|
||||||
|
awk -v p="$1" '$0 == "P:" p { getline; sub(/^V:/, ""); print; exit }' \
|
||||||
|
/lib/apk/db/installed
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
version_lt() {
|
||||||
|
[ -n "$1" ] || return 1
|
||||||
|
awk -v a="$1" -v b="$2" 'BEGIN {
|
||||||
|
n = split(a, x, "."); m = split(b, y, ".");
|
||||||
|
for (i = 1; i <= 3; i++) {
|
||||||
|
xs = (i <= n) ? x[i] : 0;
|
||||||
|
ys = (i <= m) ? y[i] : 0;
|
||||||
|
sub(/-.*/, "", xs); sub(/-.*/, "", ys);
|
||||||
|
if (xs + 0 < ys + 0) exit 0;
|
||||||
|
if (xs + 0 > ys + 0) exit 1;
|
||||||
|
}
|
||||||
|
exit 1;
|
||||||
|
}'
|
||||||
|
}
|
||||||
|
|
||||||
|
old_v2raya_pkg=0
|
||||||
|
pkg_has v2raya-nextgen && old_v2raya_pkg=1
|
||||||
|
pkg_has v2raya-core-nextgen && old_v2raya_pkg=1
|
||||||
|
pkg_has luci-app-v2raya-nextgen && old_v2raya_pkg=1
|
||||||
|
if pkg_has v2raya; then
|
||||||
|
version_lt "$(pkg_version v2raya)" "2.5" && old_v2raya_pkg=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The upstream packages install to /usr/share/v2ray, while this feed uses
|
||||||
|
# /usr/share/v2raya. Check the configured directory first, then both
|
||||||
|
# well-known paths, so the report is correct with either package set.
|
||||||
|
assets_dirs=""
|
||||||
|
configured_assets=$(uci -q get v2raya.config.v2ray_assetsdir 2>/dev/null) || configured_assets=""
|
||||||
|
[ -n "$configured_assets" ] && assets_dirs="$configured_assets "
|
||||||
|
assets_dirs="$assets_dirs/usr/share/v2ray /usr/local/share/v2ray /usr/share/v2raya /usr/local/share/v2raya"
|
||||||
|
|
||||||
|
geoip_present=0
|
||||||
|
geosite_present=0
|
||||||
|
for dir in $assets_dirs; do
|
||||||
|
[ -f "$dir/geoip.dat" ] && geoip_present=1
|
||||||
|
[ -f "$dir/geosite.dat" ] && geosite_present=1
|
||||||
|
done
|
||||||
|
|
||||||
|
present=""
|
||||||
|
enabled=""
|
||||||
|
for service in xray sing-box v2ray passwall passwall2 ssrplus openclash shellcrash mihomo clash byedpi; do
|
||||||
|
[ -x "/etc/init.d/$service" ] || continue
|
||||||
|
|
||||||
|
present="${present}${present:+,}$service"
|
||||||
|
if "/etc/init.d/$service" enabled >/dev/null 2>&1; then
|
||||||
|
enabled="${enabled}${enabled:+,}$service"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
core_path=$core_bin
|
||||||
|
core_version=$(version_line "$core_bin" version)
|
||||||
|
xray_path=$xray_bin
|
||||||
|
xray_version=$(version_line "$xray_bin" version)
|
||||||
|
xray_package=$xray_pkg
|
||||||
|
singbox_path=$singbox_bin
|
||||||
|
singbox_version=$(version_line "$singbox_bin" version)
|
||||||
|
singbox_package=$singbox_pkg
|
||||||
|
v2ray_path=$v2ray_bin
|
||||||
|
v2ray_version=$(version_line "$v2ray_bin" version)
|
||||||
|
v2ray_package=$v2ray_pkg
|
||||||
|
old_v2raya_package=$old_v2raya_pkg
|
||||||
|
geoip_present=$geoip_present
|
||||||
|
geosite_present=$geosite_present
|
||||||
|
services_present=$present
|
||||||
|
services_enabled=$enabled
|
||||||
|
EOF
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
# v2rayA configuration
|
||||||
|
#
|
||||||
|
# This file is read by /etc/init.d/v2raya and translated to V2RAYA_*
|
||||||
|
# environment variables. The names intentionally match the old v2rayA feed
|
||||||
|
# so an existing /etc/config/v2raya keeps working after the upgrade.
|
||||||
|
|
||||||
|
config v2raya 'config'
|
||||||
|
# Start the service at boot. The procd instance is also enabled/disabled
|
||||||
|
# by this flag, not only by /etc/rc.d/S99v2raya.
|
||||||
|
option enabled '0'
|
||||||
|
|
||||||
|
# Web UI listening address. Use 127.0.0.1:2017 if you only access it
|
||||||
|
# through an SSH tunnel or a reverse proxy.
|
||||||
|
option address '0.0.0.0:2017'
|
||||||
|
|
||||||
|
# URL prefix when v2rayA sits behind a reverse proxy. Must start with /.
|
||||||
|
option baseurl '/'
|
||||||
|
|
||||||
|
# auto, on or off. Make sure IPv6 works before forcing it on.
|
||||||
|
option ipv6_support 'auto'
|
||||||
|
|
||||||
|
# auto, on or off. nftables is the default on OpenWrt 22.03+.
|
||||||
|
option nftables_support 'auto'
|
||||||
|
|
||||||
|
# Seconds to wait for v2raya_core to start. Raise this on slow MIPS
|
||||||
|
# devices if the core is reported as failing to start.
|
||||||
|
option core_startup_timeout '15'
|
||||||
|
|
||||||
|
# trace, debug, info, warn or error. Warn keeps the flash writes down
|
||||||
|
# on a router; use info while debugging.
|
||||||
|
option log_level 'warn'
|
||||||
|
|
||||||
|
# Number of days to keep rotated log files.
|
||||||
|
option log_max_days '1'
|
||||||
|
|
||||||
|
# The log file already contains timestamps and no terminal colours.
|
||||||
|
option log_disable_color '1'
|
||||||
|
option log_disable_timestamp '0'
|
||||||
|
|
||||||
|
# Soft Go memory limit for both v2raya and v2raya_core (GOMEMLIMIT).
|
||||||
|
# 0 disables the limit. 64MiB is a safe start on a 256MiB router.
|
||||||
|
option gomemlimit '64MiB'
|
||||||
|
|
||||||
|
# Go scheduler threads (GOMAXPROCS). 0 uses every CPU core; 2 is
|
||||||
|
# enough for a home router and uses fewer scheduler resources.
|
||||||
|
option gomaxprocs '2'
|
||||||
|
|
||||||
|
# Go GC target percentage (GOGC). 0 keeps the Go default; lower values
|
||||||
|
# use less memory at the cost of more CPU.
|
||||||
|
option gogc '0'
|
||||||
|
|
||||||
|
# Maximum open file descriptors. 16384 is plenty for a home router and
|
||||||
|
# stops a connection flood from exhausting memory.
|
||||||
|
option nofile '16384'
|
||||||
|
|
||||||
|
# Directory containing geoip.dat and geosite.dat. Leave empty to let
|
||||||
|
# v2rayA search the standard locations.
|
||||||
|
option v2ray_assetsdir '/usr/share/v2raya'
|
||||||
|
|
||||||
|
# Path to v2raya_core. Leave empty to auto-detect the binary next to
|
||||||
|
# /usr/bin/v2raya or in PATH.
|
||||||
|
option v2ray_bin ''
|
||||||
|
|
||||||
|
# Extra v2ray configuration directory merged into the generated config.
|
||||||
|
option v2ray_confdir ''
|
||||||
|
|
||||||
|
# Hooks run around transparent-proxy and core life-cycle events.
|
||||||
|
# v2rayA passes --stage (pre-start, post-start, pre-stop, post-stop) and,
|
||||||
|
# for the transparent hook, --transparent-type (tproxy, redirect).
|
||||||
|
option transparent_hook ''
|
||||||
|
option core_hook ''
|
||||||
|
option plugin_manager ''
|
||||||
|
|
||||||
|
# [Linux] In redirect mode, let sockets bound to a device bypass the
|
||||||
|
# proxy. Requires kernel 5.14+ and cgroup v2.
|
||||||
|
option redirect_respect_bound_device '0'
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
#!/bin/sh /etc/rc.common
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
#
|
||||||
|
# Copyright (C) 2026 Amr Fateem <amr.fateem@yahoo.com>
|
||||||
|
#
|
||||||
|
# procd init script for v2rayA.
|
||||||
|
#
|
||||||
|
# All settings are kept in UCI (/etc/config/v2raya) and passed to v2rayA as
|
||||||
|
# V2RAYA_* environment variables, which is the interface gonfig documents
|
||||||
|
# upstream. Keeping the command line short also means paths with spaces or
|
||||||
|
# shell metacharacters cannot break the service.
|
||||||
|
#
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
# shellcheck disable=SC3043
|
||||||
|
|
||||||
|
USE_PROCD=1
|
||||||
|
START=99
|
||||||
|
STOP=10
|
||||||
|
|
||||||
|
CONF="v2raya"
|
||||||
|
PROG="/usr/bin/v2raya"
|
||||||
|
CONF_DIR="/etc/v2raya"
|
||||||
|
LOG_DIR="/var/log/v2raya"
|
||||||
|
DEFAULT_ASSETS="/usr/share/v2raya"
|
||||||
|
|
||||||
|
is_enabled() {
|
||||||
|
local enabled
|
||||||
|
config_get_bool enabled "$1" "$2" "${3:-0}"
|
||||||
|
[ "$enabled" -eq 1 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
append_env() {
|
||||||
|
procd_append_param env "V2RAYA_$(echo "$1" | tr 'a-z' 'A-Z')=$2"
|
||||||
|
}
|
||||||
|
|
||||||
|
append_env_arg() {
|
||||||
|
local value
|
||||||
|
config_get value "$1" "$2" "$3"
|
||||||
|
[ -n "$value" ] && append_env "$2" "$value"
|
||||||
|
}
|
||||||
|
|
||||||
|
append_env_bool() {
|
||||||
|
is_enabled "$1" "$2" && append_env "$2" "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
start_service() {
|
||||||
|
config_load "$CONF"
|
||||||
|
|
||||||
|
is_enabled "config" "enabled" || return 0
|
||||||
|
|
||||||
|
local assets_dir gomemlimit gomaxprocs gogc nofile
|
||||||
|
config_get assets_dir "config" "v2ray_assetsdir" "$DEFAULT_ASSETS"
|
||||||
|
config_get gomemlimit "config" "gomemlimit" "64MiB"
|
||||||
|
config_get gomaxprocs "config" "gomaxprocs" "2"
|
||||||
|
config_get gogc "config" "gogc" "0"
|
||||||
|
config_get nofile "config" "nofile" "16384"
|
||||||
|
|
||||||
|
mkdir -p "$CONF_DIR" "$LOG_DIR"
|
||||||
|
[ -n "$assets_dir" ] && mkdir -p "$assets_dir"
|
||||||
|
chmod 750 "$CONF_DIR"
|
||||||
|
chmod 755 "$LOG_DIR"
|
||||||
|
|
||||||
|
procd_open_instance "$CONF"
|
||||||
|
procd_set_param command "$PROG"
|
||||||
|
procd_set_param env "V2RAYA_CONFIG=$CONF_DIR" "V2RAYA_LOG_FILE=$LOG_DIR/v2raya.log"
|
||||||
|
|
||||||
|
# The core inherits this environment, so one limit covers both processes.
|
||||||
|
[ -n "$gomemlimit" ] && [ "$gomemlimit" != "0" ] && \
|
||||||
|
procd_append_param env "GOMEMLIMIT=$gomemlimit"
|
||||||
|
[ -n "$gomaxprocs" ] && [ "$gomaxprocs" -gt 0 ] 2>/dev/null && \
|
||||||
|
procd_append_param env "GOMAXPROCS=$gomaxprocs"
|
||||||
|
[ -n "$gogc" ] && [ "$gogc" -gt 0 ] 2>/dev/null && \
|
||||||
|
procd_append_param env "GOGC=$gogc"
|
||||||
|
|
||||||
|
append_env_arg "config" "v2ray_assetsdir" "$DEFAULT_ASSETS"
|
||||||
|
append_env_arg "config" "address" "0.0.0.0:2017"
|
||||||
|
append_env_arg "config" "baseurl" "/"
|
||||||
|
append_env_arg "config" "ipv6_support" "auto"
|
||||||
|
append_env_arg "config" "nftables_support" "auto"
|
||||||
|
append_env_arg "config" "core_startup_timeout" "15"
|
||||||
|
append_env_arg "config" "log_level" "warn"
|
||||||
|
append_env_arg "config" "log_max_days" "1"
|
||||||
|
append_env_arg "config" "v2ray_bin"
|
||||||
|
append_env_arg "config" "v2ray_confdir"
|
||||||
|
append_env_arg "config" "transparent_hook"
|
||||||
|
append_env_arg "config" "core_hook"
|
||||||
|
append_env_arg "config" "plugin_manager"
|
||||||
|
|
||||||
|
append_env_bool "config" "log_disable_color"
|
||||||
|
append_env_bool "config" "log_disable_timestamp"
|
||||||
|
append_env_bool "config" "redirect_respect_bound_device"
|
||||||
|
|
||||||
|
# No core dumps on a router: they fill the overlay and can trip a
|
||||||
|
# marginal NAND. A 16384 descriptor ceiling is plenty for the LAN and
|
||||||
|
# keeps a connection flood from allocating unbounded socket buffers.
|
||||||
|
[ -n "$nofile" ] || nofile=16384
|
||||||
|
procd_set_param limits core="0 0"
|
||||||
|
procd_set_param limits nofile="$nofile $nofile"
|
||||||
|
procd_set_param respawn
|
||||||
|
procd_set_param term_timeout 30
|
||||||
|
procd_set_param stdout 1
|
||||||
|
procd_set_param stderr 1
|
||||||
|
|
||||||
|
procd_close_instance
|
||||||
|
}
|
||||||
|
|
||||||
|
reload_service() {
|
||||||
|
# v2rayA does not reload its configuration on SIGHUP; a restart is the
|
||||||
|
# only safe way to apply new UCI values.
|
||||||
|
restart
|
||||||
|
}
|
||||||
|
|
||||||
|
service_triggers() {
|
||||||
|
procd_add_reload_trigger "$CONF"
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/etc/v2raya
|
||||||
Binary file not shown.
@@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
#
|
||||||
|
# Copyright (C) 2026 Amr Fateem <amr.fateem@yahoo.com>
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
v2raya)
|
||||||
|
/usr/bin/v2raya --version | grep -F "$2"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Reference in new issue
Block a user