mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-13 20:08:05 +08:00
🌴 Sync 2026-10-12 12:02:15
This commit is contained in:
1 parent
1b64546588
commit
887e5ac8e3
261 files changed
+52646
-35012
No files matched your search
@@ -81,6 +81,35 @@ module YAML
|
||||
end
|
||||
end
|
||||
|
||||
# The pipeline loads the same large file repeatedly, cache it by mtime/size.
|
||||
def self.load_file_cached(filename, cache_file, *args, **kwargs)
|
||||
if File.exist?(cache_file)
|
||||
begin
|
||||
entry = Marshal.load(File.binread(cache_file))
|
||||
if entry.is_a?(Array) && entry.length == 3 &&
|
||||
entry[0] == File.mtime(filename).to_i && entry[1] == File.size(filename)
|
||||
return entry[2]
|
||||
end
|
||||
rescue ::Exception
|
||||
# ignore broken cache, parse the file again
|
||||
end
|
||||
end
|
||||
|
||||
value = load_file(filename, *args, **kwargs)
|
||||
cache_write(filename, value, cache_file)
|
||||
value
|
||||
end
|
||||
|
||||
def self.cache_write(filename, value, cache_file)
|
||||
begin
|
||||
File.open(cache_file, 'wb') do |file|
|
||||
Marshal.dump([File.mtime(filename).to_i, File.size(filename), value], file)
|
||||
end
|
||||
rescue ::Exception
|
||||
File.delete(cache_file) rescue nil
|
||||
end
|
||||
end
|
||||
|
||||
def self.dump(obj, io = nil, **options)
|
||||
if obj.nil? || obj == false
|
||||
target = ""
|
||||
@@ -148,22 +177,35 @@ module YAML
|
||||
end
|
||||
end
|
||||
|
||||
if io.nil?
|
||||
yaml_content = original_dump(obj, **options)
|
||||
needs_fix ? fix_short_id_quotes(yaml_content) : yaml_content
|
||||
elsif io.respond_to?(:write)
|
||||
if needs_fix
|
||||
stream = options.empty? && StreamDump.supported?(obj)
|
||||
|
||||
if io.respond_to?(:write)
|
||||
if stream
|
||||
StreamDump.new(io).dump(obj, short_id: needs_fix)
|
||||
elsif needs_fix
|
||||
fix_short_id_text(original_dump(obj, **options), io)
|
||||
else
|
||||
original_dump(obj, io, **options)
|
||||
end
|
||||
io
|
||||
elsif stream
|
||||
stream_dump_to_string(obj, needs_fix)
|
||||
else
|
||||
yaml_content = original_dump(obj, **options)
|
||||
needs_fix ? fix_short_id_quotes(yaml_content) : yaml_content
|
||||
dump_string(obj, options, needs_fix)
|
||||
end
|
||||
end
|
||||
|
||||
def self.stream_dump_to_string(obj, needs_fix)
|
||||
sink = StringSink.new
|
||||
StreamDump.new(sink).dump(obj, short_id: needs_fix)
|
||||
sink.string
|
||||
end
|
||||
|
||||
def self.dump_string(obj, options, needs_fix)
|
||||
yaml_content = original_dump(obj, **options)
|
||||
needs_fix ? fix_short_id_quotes(yaml_content) : yaml_content
|
||||
end
|
||||
|
||||
def self.dump_to_path(obj, path, **options)
|
||||
real = File.symlink?(path) ? File.realpath(path) : path
|
||||
dir = File.dirname(real)
|
||||
@@ -173,7 +215,7 @@ module YAML
|
||||
File.open(tmp, 'w') { |f| dump_to_io(obj, f, **options.merge(filename: real)) }
|
||||
File.chmod(mode, tmp) if mode
|
||||
File.rename(tmp, real)
|
||||
rescue Exception
|
||||
rescue ::Exception
|
||||
begin
|
||||
File.unlink(tmp) if File.exist?(tmp)
|
||||
rescue
|
||||
@@ -221,22 +263,49 @@ module YAML
|
||||
input
|
||||
end
|
||||
|
||||
AGE_KEYS_UCI_CONFIG = "/etc/config/openclash"
|
||||
AGE_KEYS_SECTION = "config_age_secret".freeze
|
||||
|
||||
# The file itself says whether a lookup is needed: the common case has no age key and must
|
||||
# not pay for a fork and a uci parse.
|
||||
def self.age_keys_enabled?
|
||||
File.read(AGE_KEYS_UCI_CONFIG).include?(AGE_KEYS_SECTION)
|
||||
rescue StandardError
|
||||
true
|
||||
end
|
||||
|
||||
# One "uci show" dump resolves every name and both key types
|
||||
def self.age_keys
|
||||
@age_keys ||= begin
|
||||
keys = {}
|
||||
if age_keys_enabled?
|
||||
cmd = ["/bin/sh", "-c", ". /usr/share/openclash/uci.sh; uci_age_keys_dump"]
|
||||
IO.popen(cmd, "r") do |io|
|
||||
io.each_line do |line|
|
||||
tag, name, value = line.strip.split("\t", 3)
|
||||
next if name.nil? || value.nil? || value.strip == ""
|
||||
keys[name] ||= { publics: [], secrets: [] }
|
||||
(tag == "P" ? keys[name][:publics] : keys[name][:secrets]) << value.strip
|
||||
end
|
||||
end
|
||||
end
|
||||
keys
|
||||
rescue Errno::ENOENT
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
def self.find_age_keys_for_filename(filename)
|
||||
basename = File.basename(filename)
|
||||
basename_no_ext = File.basename(filename, File.extname(filename))
|
||||
keys = age_keys
|
||||
publics = []
|
||||
secrets = []
|
||||
|
||||
[basename, basename_no_ext].uniq.each do |n|
|
||||
cmd_public = ["/bin/sh", "-c", ". /usr/share/openclash/uci.sh; uci_get_age_public_keys \"$1\"", "sh", n]
|
||||
IO.popen(cmd_public, "r") do |io|
|
||||
io.each_line { |l| publics << l.strip unless l.nil? || l.strip == "" }
|
||||
end
|
||||
[File.basename(filename), File.basename(filename, File.extname(filename))].uniq.each do |n|
|
||||
entry = keys[n]
|
||||
next if entry.nil?
|
||||
|
||||
cmd_secret = ["/bin/sh", "-c", ". /usr/share/openclash/uci.sh; uci_get_age_secret_keys \"$1\"", "sh", n]
|
||||
IO.popen(cmd_secret, "r") do |io|
|
||||
io.each_line { |l| secrets << l.strip unless l.nil? || l.strip == "" }
|
||||
end
|
||||
publics.concat(entry[:publics])
|
||||
secrets.concat(entry[:secrets])
|
||||
end
|
||||
{ publics: publics, secrets: secrets }
|
||||
end
|
||||
@@ -451,6 +520,218 @@ module YAML
|
||||
end
|
||||
end
|
||||
|
||||
# StreamDump writes the data tree directly into libyaml, Psych.dump builds a full AST first
|
||||
# and nearly doubles the peak memory. supported? skips trees with anchors or tags,
|
||||
# and the scalar style rules below reproduce Psych::Visitors::YAMLTree#visit_String.
|
||||
class StringSink
|
||||
attr_reader :string
|
||||
|
||||
def initialize
|
||||
@string = String.new
|
||||
end
|
||||
|
||||
def write(data)
|
||||
@string << data
|
||||
data.to_s.bytesize
|
||||
end
|
||||
end
|
||||
|
||||
class StreamDump
|
||||
ANY = Psych::Nodes::Scalar::ANY
|
||||
PLAIN = Psych::Nodes::Scalar::PLAIN
|
||||
SINGLE_QUOTED = Psych::Nodes::Scalar::SINGLE_QUOTED
|
||||
DOUBLE_QUOTED = Psych::Nodes::Scalar::DOUBLE_QUOTED
|
||||
LITERAL = Psych::Nodes::Scalar::LITERAL
|
||||
MAP_BLOCK = Psych::Nodes::Mapping::BLOCK
|
||||
SEQ_BLOCK = Psych::Nodes::Sequence::BLOCK
|
||||
NULL_TAG = 'tag:yaml.org,2002:null'
|
||||
STR_TAG = 'tag:yaml.org,2002:str'
|
||||
|
||||
def self.available?
|
||||
defined?(Psych::Emitter) && defined?(Psych::ScalarScanner) && defined?(Psych::ClassLoader)
|
||||
end
|
||||
|
||||
def self.supported?(obj)
|
||||
return false unless available?
|
||||
|
||||
seen = {}.compare_by_identity
|
||||
stack = [obj]
|
||||
until stack.empty?
|
||||
item = stack.pop
|
||||
case item
|
||||
when Hash
|
||||
return false unless item.class == ::Hash
|
||||
return false if seen.key?(item)
|
||||
seen[item] = true
|
||||
item.each do |key, value|
|
||||
stack << key
|
||||
stack << value
|
||||
end
|
||||
when Array
|
||||
return false unless item.class == ::Array
|
||||
return false if seen.key?(item)
|
||||
seen[item] = true
|
||||
stack.concat(item)
|
||||
when String
|
||||
return false unless item.class == ::String
|
||||
return false if item.instance_variables.any?
|
||||
return false if item.encoding == Encoding::ASCII_8BIT && !item.ascii_only?
|
||||
when Integer, Float, TrueClass, FalseClass, NilClass
|
||||
# dumped through to_s, exactly like Psych does
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def initialize(io)
|
||||
@io = io
|
||||
@scanner = Psych::ScalarScanner.new(Psych::ClassLoader.new)
|
||||
@short_id = false
|
||||
end
|
||||
|
||||
def dump(obj, short_id: false)
|
||||
@short_id = short_id
|
||||
emitter = Psych::Emitter.new(@io)
|
||||
@emitter = emitter
|
||||
emitter.start_stream(Psych::Parser::UTF8)
|
||||
emitter.start_document([], [], false)
|
||||
emit(obj)
|
||||
emitter.end_document(true)
|
||||
emitter.end_stream
|
||||
@emitter = nil
|
||||
@io
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def emit(obj)
|
||||
case obj
|
||||
when Hash
|
||||
@emitter.start_mapping(nil, nil, true, MAP_BLOCK)
|
||||
obj.each do |key, value|
|
||||
emit(key)
|
||||
emit_value(value, key)
|
||||
end
|
||||
@emitter.end_mapping
|
||||
when Array
|
||||
@emitter.start_sequence(nil, nil, true, SEQ_BLOCK)
|
||||
obj.each { |value| emit(value) }
|
||||
@emitter.end_sequence
|
||||
when String
|
||||
emit_string(obj)
|
||||
when Integer, TrueClass, FalseClass
|
||||
@emitter.scalar(obj.to_s, nil, nil, true, false, ANY)
|
||||
when Float
|
||||
emit_float(obj)
|
||||
when NilClass
|
||||
@emitter.scalar('', nil, NULL_TAG, true, false, ANY)
|
||||
end
|
||||
end
|
||||
|
||||
def emit_value(value, key)
|
||||
if @short_id && (key == 'short-id' || key == :'short-id')
|
||||
emit_short_id(value)
|
||||
else
|
||||
emit(value)
|
||||
end
|
||||
end
|
||||
|
||||
def emit_float(value)
|
||||
if value.nan?
|
||||
@emitter.scalar('.nan', nil, nil, true, false, ANY)
|
||||
elsif value.infinite?
|
||||
@emitter.scalar(value.infinite? > 0 ? '.inf' : '-.inf', nil, nil, true, false, ANY)
|
||||
else
|
||||
@emitter.scalar(value.to_s, nil, nil, true, false, ANY)
|
||||
end
|
||||
end
|
||||
|
||||
# short-id values are always double quoted: same result as
|
||||
# fix_short_id_text, without building the intermediate document string
|
||||
def emit_short_id(value)
|
||||
if value.is_a?(Array)
|
||||
@emitter.start_sequence(nil, nil, true, SEQ_BLOCK)
|
||||
value.each { |item| emit_quoted(item) }
|
||||
@emitter.end_sequence
|
||||
else
|
||||
emit_quoted(value)
|
||||
end
|
||||
end
|
||||
|
||||
def emit_quoted(value)
|
||||
case value
|
||||
when String, Integer, Float
|
||||
@emitter.scalar(value.to_s, nil, nil, false, true, DOUBLE_QUOTED)
|
||||
when nil
|
||||
@emitter.scalar('', nil, nil, false, true, DOUBLE_QUOTED)
|
||||
else
|
||||
emit(value)
|
||||
end
|
||||
end
|
||||
|
||||
def emit_string(value)
|
||||
plain = true
|
||||
quote = true
|
||||
style = PLAIN
|
||||
tag = nil
|
||||
|
||||
if value.match?(/\n(?!\Z)/)
|
||||
style = LITERAL
|
||||
elsif value == '<<'
|
||||
style = SINGLE_QUOTED
|
||||
tag = STR_TAG
|
||||
plain = false
|
||||
quote = false
|
||||
elsif value == 'y' || value == 'Y' || value == 'n' || value == 'N'
|
||||
style = DOUBLE_QUOTED
|
||||
elsif value.match?(/^[^[:word:]][^"]*$/)
|
||||
style = DOUBLE_QUOTED
|
||||
elsif !(String === @scanner.tokenize(value)) || /\A0[0-7]*[89]/.match?(value)
|
||||
style = SINGLE_QUOTED
|
||||
end
|
||||
|
||||
@emitter.scalar(value, nil, tag, plain, quote, style)
|
||||
end
|
||||
end
|
||||
|
||||
# Inline replaces the "one Thread per field" pattern: a finished Thread keeps its stack
|
||||
# until it is joined, which costs hundreds of MB for thousands of entries.
|
||||
class Inline
|
||||
def initialize(*args, &block)
|
||||
@error = nil
|
||||
begin
|
||||
block.call(*args)
|
||||
rescue ::Exception => e
|
||||
@error = e
|
||||
end
|
||||
end
|
||||
|
||||
def join
|
||||
raise @error if @error
|
||||
self
|
||||
end
|
||||
|
||||
def value
|
||||
raise @error if @error
|
||||
self
|
||||
end
|
||||
|
||||
def alive?
|
||||
false
|
||||
end
|
||||
|
||||
def status
|
||||
@error ? nil : false
|
||||
end
|
||||
|
||||
def kill
|
||||
self
|
||||
end
|
||||
alias_method :terminate, :kill
|
||||
end
|
||||
|
||||
def self.overwrite(base, override)
|
||||
return override if base.nil?
|
||||
return base if override.nil?
|
||||
@@ -770,4 +1051,405 @@ module YAML
|
||||
raise "[batch update] update_spec: [#{update_spec}], error: [#{e.message}]"
|
||||
end
|
||||
end
|
||||
|
||||
# The [Overwrite] modules and the custom overwrite script are both executed by this section,
|
||||
# they record their helper calls and let one process own one Value and one dump; the argument
|
||||
# counts below must stay in sync with ruby_record() in ruby.sh
|
||||
|
||||
OVERWRITE_HELPERS = {
|
||||
'ruby_arr_add_file' => 5,
|
||||
'ruby_arr_edit' => 6,
|
||||
'ruby_arr_head_add_file' => 4,
|
||||
'ruby_arr_insert' => 4,
|
||||
'ruby_arr_insert_arr' => 4,
|
||||
'ruby_arr_insert_hash' => 4,
|
||||
'ruby_cover' => 4,
|
||||
'ruby_delete' => 3,
|
||||
'ruby_edit' => 3,
|
||||
'ruby_map_edit' => 5,
|
||||
'ruby_merge' => 4,
|
||||
'ruby_merge_hash' => 3,
|
||||
'ruby_uniq' => 2
|
||||
}.freeze
|
||||
|
||||
OVERWRITE_REQUIRED = {
|
||||
'ruby_arr_add_file' => [1, 2, 3, 4, 5],
|
||||
'ruby_arr_edit' => [1, 2],
|
||||
'ruby_arr_head_add_file' => [1, 2, 3, 4],
|
||||
'ruby_arr_insert' => [1, 2, 3, 4],
|
||||
'ruby_arr_insert_arr' => [1, 2, 3, 4],
|
||||
'ruby_arr_insert_hash' => [1, 2, 3, 4],
|
||||
'ruby_cover' => [1, 2],
|
||||
'ruby_delete' => [1, 3],
|
||||
'ruby_edit' => [1, 2],
|
||||
'ruby_map_edit' => [1, 2, 3, 4],
|
||||
'ruby_merge' => [1, 2, 3],
|
||||
'ruby_merge_hash' => [1, 2, 3],
|
||||
'ruby_uniq' => [1, 2]
|
||||
}.freeze
|
||||
|
||||
# The code arguments are checked against these lists before eval: node types give the allowed
|
||||
# syntax, the method, operator and constant lists the allowed callable surface, everything
|
||||
# not listed is rejected; the inspected arguments never take part in the check
|
||||
OVERWRITE_NODES = %i[
|
||||
AND ARGS ATTRASGN BEGIN BLOCK BLOCK_PASS BREAK CALL CASE CASE3 CONST DASGN DOT2 DOT3
|
||||
DREGX DSTR DVAR EVSTR FALSE FCALL HASH IF IN ITER LAMBDA LASGN LIST LIT LVAR MASGN
|
||||
NEXT NIL NOT OP_ASGN1 OP_ASGN2 OP_ASGN_AND OP_ASGN_OR OPCALL OR RESBODY RESCUE SCOPE
|
||||
STR SYM TRUE UNLESS WHEN ZLIST
|
||||
].freeze
|
||||
|
||||
OVERWRITE_METHODS = %w(
|
||||
[] []= LOG LOG_ERROR LOG_TIP LOG_WARN abs all? any? call capitalize ceil chars chomp
|
||||
chomp! chop clone compact compact! concat count delete delete_at delete_if detect dig
|
||||
downcase drop drop_while dup each each_char each_key each_line each_pair each_slice
|
||||
each_value each_with_index empty? end_with? fetch filter filter! find find_index first
|
||||
flat_map flatten flatten! floor frozen? group_by gsub gsub! has_key? has_value?
|
||||
include? index insert inspect instance_of? is_a? itself join keep_if key? keys kind_of?
|
||||
lambda last length lstrip map map! match match? max max_by member? merge merge! min
|
||||
min_by nil? none? partition pop proc push reject reject! replace reverse reverse!
|
||||
rindex round rstrip select select! shift size slice slice! sort sort! sort_by sort_by!
|
||||
split start_with? store strip sub sub! sum swapcase take take_while tally tap then to_a
|
||||
to_f to_h to_i to_s to_sym transform_keys transform_keys! transform_values
|
||||
transform_values! uniq uniq! unshift upcase update values values_at yield_self
|
||||
).freeze
|
||||
|
||||
OVERWRITE_OPERATORS = %w[! != !~ % & * ** + - / < << <= <=> == =~ > >= >> ^ | ~].freeze
|
||||
|
||||
OVERWRITE_CONSTANTS = %w[Array FalseClass Float Hash Integer NilClass Numeric String Symbol TrueClass Value YAML].freeze
|
||||
|
||||
# Derived lookups keep the node walk cheap, the tables above stay readable
|
||||
OVERWRITE_NODE_LOOKUP = OVERWRITE_NODES.to_h { |type| [type, true] }.freeze
|
||||
OVERWRITE_CALL_LOOKUP = (OVERWRITE_METHODS + OVERWRITE_OPERATORS).to_h { |name| [name.to_sym, true] }.freeze
|
||||
OVERWRITE_CONST_LOOKUP = OVERWRITE_CONSTANTS.to_h { |name| [name.to_sym, true] }.freeze
|
||||
|
||||
# Argument numbers follow the helper call order of ruby.sh, 1 is the config file; the check
|
||||
# only covers the arguments interpolated as ruby source
|
||||
OVERWRITE_CODE = {
|
||||
'ruby_arr_add_file' => [2, 3, 5],
|
||||
'ruby_arr_edit' => [2, 3, 5],
|
||||
'ruby_arr_head_add_file' => [2, 4],
|
||||
'ruby_arr_insert' => [2, 3],
|
||||
'ruby_arr_insert_arr' => [2, 3, 4],
|
||||
'ruby_arr_insert_hash' => [2, 3, 4],
|
||||
'ruby_cover' => [2],
|
||||
'ruby_delete' => [2],
|
||||
'ruby_edit' => [2, 3],
|
||||
'ruby_map_edit' => [2, 4],
|
||||
'ruby_merge' => [2, 4],
|
||||
'ruby_merge_hash' => [2, 3],
|
||||
'ruby_uniq' => [2]
|
||||
}.freeze
|
||||
|
||||
# Manifest records: 'M <module>' opens a module block, 'Y <file>' is its [YAML] override block
|
||||
# and every 'L <line>' is one [Overwrite] line of it
|
||||
def self.overwrite_run(config_file, manifest_file)
|
||||
return if config_file.nil? || config_file.empty? || !File.exist?(manifest_file)
|
||||
|
||||
# module lines expand $CONFIG_FILE from the environment, keep the legacy contract
|
||||
ENV['CONFIG_FILE'] = config_file
|
||||
|
||||
value = load_file(config_file)
|
||||
blocks = []
|
||||
File.foreach(manifest_file) do |raw|
|
||||
line = raw.chomp
|
||||
if line.start_with?('M ')
|
||||
blocks << [line[2..-1], nil, []]
|
||||
elsif blocks.any?
|
||||
if line.start_with?('Y ')
|
||||
blocks.last[1] = line[2..-1]
|
||||
elsif line.start_with?('L ')
|
||||
blocks.last[2] << line[2..-1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
blocks.each do |name, yaml_file, lines|
|
||||
value = overwrite_apply_yaml(value, yaml_file) if yaml_file
|
||||
lines.each { |line| overwrite_apply_line(value, name, line) }
|
||||
end
|
||||
|
||||
dump(value, config_file)
|
||||
rescue ::Exception => e
|
||||
LOG_ERROR("Set Custom Overwrite Script Failed,【#{e.message}】")
|
||||
end
|
||||
|
||||
# Calls recorded by ruby_record() in ruby.sh: NUL separated fields and a fixed argument count
|
||||
# per helper, the records may target several config files
|
||||
def self.overwrite_run_custom(calls_file)
|
||||
return if calls_file.nil? || calls_file.empty? || !File.exist?(calls_file)
|
||||
|
||||
fields = File.read(calls_file).split("\0", -1)
|
||||
values = {}
|
||||
position = 0
|
||||
while position < fields.length
|
||||
fn = fields[position]
|
||||
arity = OVERWRITE_HELPERS[fn]
|
||||
if fn.nil? || fn.empty?
|
||||
position += 1
|
||||
next
|
||||
elsif arity.nil?
|
||||
LOG_WARN("skip unsafe Overwrite command【Ruby Script => #{fn}】")
|
||||
position += 1
|
||||
next
|
||||
end
|
||||
|
||||
args = fields[position + 1, arity] || []
|
||||
args += [''] * (arity - args.length)
|
||||
position += arity + 1
|
||||
|
||||
if overwrite_unsafe?(fn, args)
|
||||
LOG_WARN("skip unsafe Overwrite command【Ruby Script => #{fn}(#{args.map(&:inspect).join(', ')})】")
|
||||
next
|
||||
end
|
||||
|
||||
file = args[0]
|
||||
next if file.nil? || file.empty? || !File.exist?(file)
|
||||
|
||||
values[file] = load_file(file) unless values.key?(file)
|
||||
begin
|
||||
overwrite_apply(values[file], fn, args)
|
||||
rescue ::Exception => e
|
||||
LOG_ERROR("Set Custom Overwrite Script Failed,【#{e.message}】")
|
||||
end
|
||||
end
|
||||
|
||||
values.each { |file, value| dump(value, file) }
|
||||
rescue ::Exception => e
|
||||
LOG_ERROR("Set Custom Overwrite Script Failed,【#{e.message}】")
|
||||
end
|
||||
|
||||
def self.overwrite_apply_yaml(value, yaml_file)
|
||||
return value if yaml_file.nil? || yaml_file.empty? || !File.exist?(yaml_file)
|
||||
|
||||
begin
|
||||
yaml_data = load(File.read(yaml_file))
|
||||
if yaml_data.is_a?(Hash)
|
||||
value = overwrite(value, yaml_data)
|
||||
else
|
||||
LOG_WARN('Invalid YAML Override format, skipped...')
|
||||
end
|
||||
rescue ::Exception => e
|
||||
LOG_ERROR("Parse YAML Override failed:【#{e.message}】")
|
||||
end
|
||||
value
|
||||
end
|
||||
|
||||
def self.overwrite_apply_line(value, name, line)
|
||||
parsed = overwrite_parse_line(line)
|
||||
if parsed.nil?
|
||||
LOG_WARN("skip invalid Overwrite command【Ruby Script => #{name}: #{line}】")
|
||||
return
|
||||
end
|
||||
|
||||
fn, args = parsed
|
||||
if overwrite_unsafe?(fn, args)
|
||||
LOG_WARN("skip invalid Overwrite command【Ruby Script => #{name}: #{line}】")
|
||||
return
|
||||
end
|
||||
|
||||
LOG_TIP("Load Overwrite Script【Ruby Script => #{line}】")
|
||||
overwrite_apply(value, fn, args)
|
||||
rescue ::Exception => e
|
||||
LOG_ERROR("Set Custom Overwrite Script Failed,【#{e.message}】")
|
||||
end
|
||||
|
||||
def self.overwrite_unsafe?(fn, args)
|
||||
OVERWRITE_CODE[fn].any? do |index|
|
||||
text = args[index - 1]
|
||||
!text.nil? && !text.empty? && overwrite_code_unsafe?(text)
|
||||
end
|
||||
end
|
||||
|
||||
# The hash body of ruby_merge_hash is only valid inside braces, so a => fragment needs the
|
||||
# braced source checked too, an unparsable argument is unsafe
|
||||
def self.overwrite_code_unsafe?(text)
|
||||
tree = overwrite_parse_source(text)
|
||||
if tree.nil?
|
||||
tree = overwrite_parse_source("{#{text}}")
|
||||
return true if tree.nil?
|
||||
elsif text.include?('=>')
|
||||
braced = overwrite_parse_source("{#{text}}")
|
||||
return true if braced && overwrite_ast_unsafe?(braced)
|
||||
end
|
||||
overwrite_ast_unsafe?(tree)
|
||||
end
|
||||
|
||||
def self.overwrite_parse_source(source)
|
||||
RubyVM::AbstractSyntaxTree.parse(source)
|
||||
rescue ::Exception
|
||||
nil
|
||||
end
|
||||
|
||||
# An explicit stack keeps the walk free of per-node recursion and block calls
|
||||
def self.overwrite_ast_unsafe?(node)
|
||||
stack = [node]
|
||||
until stack.empty?
|
||||
current = stack.pop
|
||||
next unless current.is_a?(RubyVM::AbstractSyntaxTree::Node)
|
||||
return true unless OVERWRITE_NODE_LOOKUP.key?(current.type)
|
||||
|
||||
case current.type
|
||||
when :CALL, :FCALL, :VCALL, :ATTRASGN
|
||||
return true unless overwrite_method_allowed?(overwrite_call_name(current))
|
||||
when :OPCALL
|
||||
return true unless overwrite_method_allowed?(current.children[1])
|
||||
when :CONST
|
||||
names = overwrite_leaf_names(current)
|
||||
return true unless names.length == 1 && OVERWRITE_CONST_LOOKUP.key?(names[0].to_sym)
|
||||
when :BLOCK_PASS
|
||||
names = overwrite_leaf_names(current)
|
||||
return true if names.empty? || names.any? { |name| !overwrite_method_allowed?(name) }
|
||||
end
|
||||
|
||||
stack.concat(current.children)
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
def self.overwrite_method_allowed?(name)
|
||||
return OVERWRITE_CALL_LOOKUP.key?(name) if name.is_a?(Symbol)
|
||||
|
||||
OVERWRITE_CALL_LOOKUP.key?(name.to_s.to_sym)
|
||||
end
|
||||
|
||||
def self.overwrite_call_name(node)
|
||||
(node.type == :CALL || node.type == :ATTRASGN) ? node.children[1] : node.children[0]
|
||||
end
|
||||
|
||||
# Symbols and strings of a node subtree, the called name of &:<name> block passes and the
|
||||
# name of constants are carried this way
|
||||
def self.overwrite_leaf_names(node, names = [])
|
||||
node.children.each do |child|
|
||||
if child.is_a?(Symbol) || child.is_a?(String)
|
||||
names << child
|
||||
elsif child.is_a?(RubyVM::AbstractSyntaxTree::Node)
|
||||
overwrite_leaf_names(child, names)
|
||||
end
|
||||
end
|
||||
names
|
||||
end
|
||||
|
||||
# Only a single call to an allowed helper with quoted arguments is accepted; in double quoted
|
||||
# arguments $NAME and ${NAME} expand from the environment and every other character stays
|
||||
# literal, in single quoted arguments nothing is expanded
|
||||
def self.overwrite_parse_line(line)
|
||||
length = line.length
|
||||
position = 0
|
||||
position += 1 while position < length && (line[position] == ' ' || line[position] == "\t")
|
||||
|
||||
head = position
|
||||
position += 1 while position < length && line[position] != ' ' && line[position] != "\t"
|
||||
fn = line[head...position]
|
||||
return nil unless OVERWRITE_HELPERS.key?(fn)
|
||||
|
||||
args = []
|
||||
while position < length
|
||||
position += 1 while position < length && (line[position] == ' ' || line[position] == "\t")
|
||||
break if position >= length
|
||||
|
||||
quote = line[position]
|
||||
return nil unless quote == '"' || quote == "'"
|
||||
|
||||
position += 1
|
||||
closing = line.index(quote, position)
|
||||
return nil if closing.nil?
|
||||
|
||||
arg = line[position...closing]
|
||||
args << (quote == '"' && arg.include?('$') ? overwrite_expand_env(arg) : arg)
|
||||
position = closing + 1
|
||||
end
|
||||
return nil if args.empty?
|
||||
|
||||
[fn, args]
|
||||
end
|
||||
|
||||
def self.overwrite_expand_env(text)
|
||||
text.gsub(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/) do
|
||||
ENV[Regexp.last_match(1) || Regexp.last_match(2)].to_s
|
||||
end
|
||||
end
|
||||
|
||||
def self.overwrite_apply(value, fn, args)
|
||||
arity = OVERWRITE_HELPERS[fn]
|
||||
args = args[0, arity] || []
|
||||
args += [''] * (arity - args.length)
|
||||
return if OVERWRITE_REQUIRED[fn].any? { |index| args[index - 1].empty? }
|
||||
|
||||
statement = overwrite_statement(fn, args)
|
||||
return if statement.nil?
|
||||
|
||||
overwrite_eval(value, statement)
|
||||
end
|
||||
|
||||
def self.overwrite_statement(fn, args)
|
||||
path = args[1]
|
||||
a3 = args[2]
|
||||
a4 = args[3]
|
||||
a5 = args[4]
|
||||
a6 = args[5]
|
||||
case fn
|
||||
when 'ruby_edit'
|
||||
"Value#{path} = #{a3}"
|
||||
when 'ruby_cover'
|
||||
"if File.exist?(#{a3.inspect}) then value_1 = YAML.load_file(#{a3.inspect}); if not #{a4.inspect}.empty? then Value#{path} = value_1[#{a4.inspect}] else Value#{path} = value_1 end else if not #{a4.inspect}.empty? then Value.delete(#{a4.inspect}); end; end"
|
||||
when 'ruby_merge'
|
||||
"if File.exist?(#{a3.inspect}) then value_1 = YAML.load_file(#{a3.inspect}); if not Value#{path} then Value#{path} = {}; end; if value_1#{a4} && value_1#{a4}.is_a?(Hash) then Value#{path}.merge!(value_1#{a4}) end end"
|
||||
when 'ruby_uniq'
|
||||
"if Value#{path} then Value#{path} = Value#{path}.uniq; end"
|
||||
when 'ruby_merge_hash'
|
||||
"if not Value#{path} then Value#{path} = {}; end; Value#{path}.merge!({#{a3}})"
|
||||
when 'ruby_arr_add_file'
|
||||
"if File.exist?(#{a4.inspect}) then value_1 = YAML.load_file(#{a4.inspect}); if not Value#{path} or Value#{path}.nil? then Value#{path} = []; end; if value_1#{a5} && value_1#{a5}.is_a?(Array) then idx = [#{a3}.to_i, 0].max; idx = [idx, Value#{path}.length].min; value_1#{a5}.reverse.each{|x| Value#{path}.insert(idx,x); idx += 1}; Value#{path} = Value#{path}.uniq end end"
|
||||
when 'ruby_arr_head_add_file'
|
||||
"if File.exist?(#{a3.inspect}) then value_1 = YAML.load_file(#{a3.inspect}); if not Value#{path} or Value#{path}.nil? then Value#{path} = []; end; if value_1#{a4} && value_1#{a4}.is_a?(Array) then Value#{path} = (value_1#{a4} + Value#{path}).uniq else Value#{path} = Value#{path}.uniq end end"
|
||||
when 'ruby_arr_insert'
|
||||
"if not Value#{path} or Value#{path}.nil? then Value#{path} = []; end; idx = [#{a3}.to_i, 0].max; idx = [idx, Value#{path}.length].min; Value#{path} = Value#{path}.insert(idx, #{a4.inspect}).uniq"
|
||||
when 'ruby_arr_insert_hash'
|
||||
"if not Value#{path} or Value#{path}.nil? then Value#{path} = []; end; idx = [#{a3}.to_i, 0].max; idx = [idx, Value#{path}.length].min; Value#{path} = Value#{path}.insert(idx, #{a4}).uniq"
|
||||
when 'ruby_arr_insert_arr'
|
||||
"if not Value#{path} or Value#{path}.nil? then Value#{path} = []; end; if (#{a4}).is_a?(Array) then idx = [#{a3}.to_i, 0].max; idx = [idx, Value#{path}.length].min; (#{a4}).reverse.each{|x| Value#{path} = Value#{path}.insert(idx,x); idx += 1}; Value#{path} = Value#{path}.uniq end"
|
||||
when 'ruby_delete'
|
||||
if path.empty?
|
||||
"Value.delete(#{a3.inspect})"
|
||||
else
|
||||
"if Value#{path} then if Value#{path}.is_a?(Hash) then Value#{path}.delete(#{a3.inspect}) elsif Value#{path}.is_a?(Array) then Value#{path}.delete(#{a3.inspect}) end end"
|
||||
end
|
||||
when 'ruby_map_edit'
|
||||
"if Value#{path} && Value#{path}.is_a?(Hash) then if Value#{path}[#{a3.inspect}] && Value#{path}[#{a3.inspect}].is_a?(Hash) then Value#{path}[#{a3.inspect}]#{a4} = #{a5.inspect} end end"
|
||||
when 'ruby_arr_edit'
|
||||
if !a3.empty? && !a4.empty? && !a5.empty? && !a6.empty?
|
||||
"if Value#{path} && Value#{path}.is_a?(Array) then Value#{path}.map!{|x| if x.is_a?(Hash) && x#{a3} == #{a4.inspect} then x#{a5} = #{a6.inspect} end; x}; Value#{path}.uniq! end"
|
||||
elsif a3.empty? && !a4.empty? && a5.empty? && !a6.empty?
|
||||
"if Value#{path} && Value#{path}.is_a?(Array) then Value#{path}.map!{|x| if x == #{a4.inspect} then #{a6.inspect} else x end}; Value#{path}.uniq! end"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.overwrite_eval(value, statement)
|
||||
remove_const(:Value) if const_defined?(:Value, false)
|
||||
const_set(:Value, value)
|
||||
eval(statement, binding, '(overwrite)', 1)
|
||||
end
|
||||
|
||||
# Value readers for scripts, the path keeps the ruby source form the old helpers accepted,
|
||||
# e.g. "['dns']['fake-ip-range']" or ".select { |k, _| k != 'proxies' }.to_yaml"
|
||||
def self.overwrite_read(file, path)
|
||||
value = load_file(file)
|
||||
result = overwrite_eval(value, "Value#{path}")
|
||||
puts result if result
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
|
||||
def self.overwrite_read_hash(code, path)
|
||||
overwrite_eval(nil, "v = #{code}; if v#{path} then puts v#{path} end")
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
|
||||
def self.overwrite_read_hash_arr(file, path, item_path)
|
||||
overwrite_eval(nil, "v = YAML.load_file(#{file.inspect}); if v#{path} then v#{path}.each do |i| if i#{item_path} then puts i#{item_path} end; end; end")
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
end
|
||||
@@ -2,49 +2,49 @@
|
||||
|
||||
START_LOG="/tmp/openclash_start.log"
|
||||
LOG_FILE="/tmp/openclash.log"
|
||||
|
||||
|
||||
LOG_OUT()
|
||||
{
|
||||
if [ -n "${1}" ]; then
|
||||
echo -e "$(date "+%Y-%m-%d %H:%M:%S") [Info] ${1}" >> $START_LOG
|
||||
echo -e "$(date "+%Y-%m-%d %H:%M:%S") [Info] ${1}" >> $LOG_FILE
|
||||
fi
|
||||
if [ -n "${1}" ]; then
|
||||
echo -e "$(date "+%Y-%m-%d %H:%M:%S") [Info] ${1}" >> $START_LOG
|
||||
echo -e "$(date "+%Y-%m-%d %H:%M:%S") [Info] ${1}" >> $LOG_FILE
|
||||
fi
|
||||
}
|
||||
|
||||
LOG_TIP()
|
||||
{
|
||||
if [ -n "${1}" ]; then
|
||||
echo -e "$(date "+%Y-%m-%d %H:%M:%S") [Tip] ${1}" >> $START_LOG
|
||||
echo -e "$(date "+%Y-%m-%d %H:%M:%S") [Tip] ${1}" >> $LOG_FILE
|
||||
fi
|
||||
if [ -n "${1}" ]; then
|
||||
echo -e "$(date "+%Y-%m-%d %H:%M:%S") [Tip] ${1}" >> $START_LOG
|
||||
echo -e "$(date "+%Y-%m-%d %H:%M:%S") [Tip] ${1}" >> $LOG_FILE
|
||||
fi
|
||||
}
|
||||
|
||||
LOG_WARN()
|
||||
{
|
||||
if [ -n "${1}" ]; then
|
||||
echo -e "$(date "+%Y-%m-%d %H:%M:%S") [Warning] ${1}" >> $START_LOG
|
||||
echo -e "$(date "+%Y-%m-%d %H:%M:%S") [Warning] ${1}" >> $LOG_FILE
|
||||
fi
|
||||
if [ -n "${1}" ]; then
|
||||
echo -e "$(date "+%Y-%m-%d %H:%M:%S") [Warning] ${1}" >> $START_LOG
|
||||
echo -e "$(date "+%Y-%m-%d %H:%M:%S") [Warning] ${1}" >> $LOG_FILE
|
||||
fi
|
||||
}
|
||||
|
||||
LOG_ERROR()
|
||||
{
|
||||
if [ -n "${1}" ]; then
|
||||
echo -e "$(date "+%Y-%m-%d %H:%M:%S") [Error] ${1}" >> $START_LOG
|
||||
echo -e "$(date "+%Y-%m-%d %H:%M:%S") [Error] ${1}" >> $LOG_FILE
|
||||
fi
|
||||
if [ -n "${1}" ]; then
|
||||
echo -e "$(date "+%Y-%m-%d %H:%M:%S") [Error] ${1}" >> $START_LOG
|
||||
echo -e "$(date "+%Y-%m-%d %H:%M:%S") [Error] ${1}" >> $LOG_FILE
|
||||
fi
|
||||
}
|
||||
|
||||
LOG_INFO()
|
||||
{
|
||||
if [ -n "${1}" ]; then
|
||||
echo -e "$(date "+%Y-%m-%d %H:%M:%S") [Info] ${1}" >> $LOG_FILE
|
||||
fi
|
||||
if [ -n "${1}" ]; then
|
||||
echo -e "$(date "+%Y-%m-%d %H:%M:%S") [Info] ${1}" >> $LOG_FILE
|
||||
fi
|
||||
}
|
||||
|
||||
LOG_WATCHDOG()
|
||||
{
|
||||
if [ -n "${1}" ]; then
|
||||
echo -e "$(date "+%Y-%m-%d %H:%M:%S") [Watchdog] ${1}" >> $LOG_FILE
|
||||
fi
|
||||
if [ -n "${1}" ]; then
|
||||
echo -e "$(date "+%Y-%m-%d %H:%M:%S") [Watchdog] ${1}" >> $LOG_FILE
|
||||
fi
|
||||
}
|
||||
@@ -29,6 +29,9 @@ CLASH="/etc/openclash/clash"
|
||||
CLASH_CONFIG="/etc/openclash"
|
||||
restart=0
|
||||
only_download=0
|
||||
norestart=0
|
||||
exit_code=0
|
||||
[ "$2" = "norestart" ] && norestart=1
|
||||
|
||||
inc_job_counter
|
||||
|
||||
@@ -106,18 +109,18 @@ DOWNLOAD_RESULT=$?
|
||||
|
||||
config_cus_up()
|
||||
{
|
||||
if [ -z "$subscribe_url_param" ]; then
|
||||
if [ -n "$key_match_param" ] || [ -n "$key_ex_match_param" ]; then
|
||||
LOG_OUT "Config File【$name】Start Picking Nodes..."
|
||||
ruby -ryaml -rYAML -I "/usr/share/openclash" -E UTF-8 -e "
|
||||
begin
|
||||
if [ -z "$subscribe_url_param" ]; then
|
||||
if [ -n "$key_match_param" ] || [ -n "$key_ex_match_param" ]; then
|
||||
LOG_OUT "Config File【$name】Start Picking Nodes..."
|
||||
ruby -ryaml -rYAML -I "/usr/share/openclash" -E UTF-8 -e "
|
||||
begin
|
||||
threads = [];
|
||||
Value = YAML.load_file('$CFG_FILE');
|
||||
if Value.has_key?('proxies') and not Value['proxies'].to_a.empty? then
|
||||
Value['proxies'].reverse.each{
|
||||
|x|
|
||||
Value = YAML.load_file('$CFG_FILE');
|
||||
if Value.has_key?('proxies') and not Value['proxies'].to_a.empty? then
|
||||
Value['proxies'].reverse.each{
|
||||
|x|
|
||||
if not '$key_match_param'.empty? then
|
||||
threads << Thread.new {
|
||||
threads << YAML::Inline.new {
|
||||
if not /$key_match_param/i =~ x['name'] then
|
||||
Value['proxies'].delete(x)
|
||||
Value['proxy-groups'].each{
|
||||
@@ -133,7 +136,7 @@ config_cus_up()
|
||||
};
|
||||
end;
|
||||
if not '$key_ex_match_param'.empty? then
|
||||
threads << Thread.new {
|
||||
threads << YAML::Inline.new {
|
||||
if /$key_ex_match_param/i =~ x['name'] then
|
||||
if Value['proxies'].include?(x) then
|
||||
Value['proxies'].delete(x)
|
||||
@@ -150,12 +153,12 @@ config_cus_up()
|
||||
end;
|
||||
};
|
||||
end;
|
||||
};
|
||||
end;
|
||||
};
|
||||
end;
|
||||
if Value.key?('proxy-providers') and not Value['proxy-providers'].nil? then
|
||||
Value['proxy-providers'].values.each do
|
||||
|i|
|
||||
threads << Thread.new {
|
||||
threads << YAML::Inline.new {
|
||||
if not '$key_match_param'.empty? then
|
||||
i['filter'] = '(?i)$key_match_param';
|
||||
end;
|
||||
@@ -166,16 +169,16 @@ config_cus_up()
|
||||
end;
|
||||
end;
|
||||
threads.each(&:join);
|
||||
rescue Exception => e
|
||||
YAML.LOG_ERROR('Filter Proxies Failed,【' + e.message + '】');
|
||||
ensure
|
||||
begin
|
||||
YAML.dump(Value, '$CFG_FILE');
|
||||
rescue Exception => e
|
||||
YAML.LOG_ERROR('Write file failed:【%s】' % [e.message])
|
||||
end
|
||||
end" 2>/dev/null >> $LOG_FILE
|
||||
fi
|
||||
rescue Exception => e
|
||||
YAML.LOG_ERROR('Filter Proxies Failed,【' + e.message + '】');
|
||||
ensure
|
||||
begin
|
||||
YAML.dump(Value, '$CFG_FILE');
|
||||
rescue Exception => e
|
||||
YAML.LOG_ERROR('Write file failed:【%s】' % [e.message])
|
||||
end
|
||||
end" 2>/dev/null >> $LOG_FILE
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -208,8 +211,8 @@ config_su_check()
|
||||
if [ -z "$CONFIG_PATH" ]; then
|
||||
uci -q set openclash.config.config_path="$CONFIG_FILE"
|
||||
uci commit openclash
|
||||
fi
|
||||
if [ "$CONFIG_FILE" == "$CONFIG_PATH" ]; then
|
||||
fi
|
||||
if [ "$CONFIG_FILE" == "$CONFIG_PATH" ] && [ "$norestart" -eq 0 ]; then
|
||||
restart=1
|
||||
fi
|
||||
}
|
||||
@@ -218,13 +221,14 @@ config_error()
|
||||
{
|
||||
LOG_ERROR "【$name】Update Error, Please Try Again Later..."
|
||||
rm -rf "$CFG_FILE" 2>/dev/null
|
||||
exit_code=1
|
||||
}
|
||||
|
||||
change_dns()
|
||||
{
|
||||
if pidof clash >/dev/null; then
|
||||
/etc/init.d/openclash reload "restore" >/dev/null 2>&1
|
||||
procd_send_signal "openclash" "openclash-watchdog" CONT
|
||||
procd_send_signal "openclash-watchdog" "" CONT
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -232,7 +236,7 @@ config_download_direct()
|
||||
{
|
||||
if pidof clash >/dev/null && [ "$router_self_proxy" = 1 ]; then
|
||||
kill_streaming_unlock
|
||||
procd_send_signal "openclash" "openclash-watchdog" STOP
|
||||
procd_send_signal "openclash-watchdog" "" STOP
|
||||
/etc/init.d/openclash reload "revert" >/dev/null 2>&1
|
||||
sleep 3
|
||||
|
||||
@@ -279,12 +283,12 @@ config_download_direct()
|
||||
|
||||
server_key_match()
|
||||
{
|
||||
local key_match key_word
|
||||
|
||||
local key_match key_word
|
||||
|
||||
if [ -n "$(echo "$1" |grep "^ \{0,\}$")" ] || [ -n "$(echo "$1" |grep "^\t\{0,\}$")" ]; then
|
||||
return
|
||||
return
|
||||
fi
|
||||
|
||||
|
||||
if [ -n "$(echo "$1" |grep "&")" ]; then
|
||||
key_word=$(echo "$1" |sed 's/&/ /g')
|
||||
for k in $key_word
|
||||
@@ -309,7 +313,7 @@ server_key_match()
|
||||
key_match_param="$key_match_param|$key_match"
|
||||
fi
|
||||
elif [ "$2" = "ex_keyword" ]; then
|
||||
if [ -z "$key_ex_match_param" ]; then
|
||||
if [ -z "$key_ex_match_param" ]; then
|
||||
key_ex_match_param="$key_match"
|
||||
else
|
||||
key_ex_match_param="$key_ex_match_param|$key_match"
|
||||
@@ -349,6 +353,7 @@ sub_info_get()
|
||||
config_get "address" "$section" "address" ""
|
||||
config_get "keyword" "$section" "keyword" ""
|
||||
config_get "ex_keyword" "$section" "ex_keyword" ""
|
||||
config_get_bool "keyword_option" "$section" "keyword_option" "1"
|
||||
config_get "emoji" "$section" "emoji" ""
|
||||
config_get "udp" "$section" "udp" ""
|
||||
config_get "skip_cert_verify" "$section" "skip_cert_verify" ""
|
||||
@@ -357,6 +362,8 @@ sub_info_get()
|
||||
config_get "template" "$section" "template" ""
|
||||
config_get "node_type" "$section" "node_type" ""
|
||||
config_get "rule_provider" "$section" "rule_provider" ""
|
||||
config_get "tfo" "$section" "tfo" ""
|
||||
config_get "tls13" "$section" "tls13" ""
|
||||
config_get "custom_template_url" "$section" "custom_template_url" ""
|
||||
config_get "de_ex_keyword" "$section" "de_ex_keyword" ""
|
||||
config_get "sub_ua" "$section" "sub_ua" "clash-verge/v2.4.5"
|
||||
@@ -392,24 +399,38 @@ sub_info_get()
|
||||
rule_provider=""
|
||||
fi
|
||||
|
||||
if [ "$tfo" == "true" ]; then
|
||||
tfo="&tfo=true"
|
||||
else
|
||||
tfo=""
|
||||
fi
|
||||
|
||||
if [ "$tls13" == "true" ]; then
|
||||
tls13="&tls13=true"
|
||||
else
|
||||
tls13=""
|
||||
fi
|
||||
|
||||
if [ -n "$2" ] && [ "$2" != "$CONFIG_FILE" ] && [ "$2" != "$name" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if [ ! -z "$keyword" ] || [ ! -z "$ex_keyword" ]; then
|
||||
config_list_foreach "$section" "keyword" server_key_match "keyword"
|
||||
config_list_foreach "$section" "ex_keyword" server_key_match "ex_keyword"
|
||||
fi
|
||||
if [ "$keyword_option" -eq 1 ]; then
|
||||
if [ ! -z "$keyword" ] || [ ! -z "$ex_keyword" ]; then
|
||||
config_list_foreach "$section" "keyword" server_key_match "keyword"
|
||||
config_list_foreach "$section" "ex_keyword" server_key_match "ex_keyword"
|
||||
fi
|
||||
|
||||
if [ -n "$de_ex_keyword" ]; then
|
||||
for i in $de_ex_keyword;
|
||||
do
|
||||
if [ -z "$key_ex_match_param" ]; then
|
||||
key_ex_match_param="($i)"
|
||||
else
|
||||
key_ex_match_param="$key_ex_match_param|($i)"
|
||||
fi
|
||||
done
|
||||
if [ -n "$de_ex_keyword" ]; then
|
||||
for i in $de_ex_keyword;
|
||||
do
|
||||
if [ -z "$key_ex_match_param" ]; then
|
||||
key_ex_match_param="($i)"
|
||||
else
|
||||
key_ex_match_param="$key_ex_match_param|($i)"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$sub_convert" -eq 0 ]; then
|
||||
@@ -429,7 +450,7 @@ sub_info_get()
|
||||
template_path_encode=$(urlencode "$template_path")
|
||||
[ -n "$key_match_param" ] && key_match_param="$(urlencode "(?i)$key_match_param")"
|
||||
[ -n "$key_ex_match_param" ] && key_ex_match_param="$(urlencode "(?i)$key_ex_match_param")"
|
||||
subscribe_url_param="?target=clash&new_name=true&url=$subscribe_url&config=$template_path_encode&include=$key_match_param&exclude=$key_ex_match_param&emoji=$emoji&list=false&sort=$sort$udp&scv=$skip_cert_verify&append_type=$node_type&fdn=true$rule_provider$append_custom_params"
|
||||
subscribe_url_param="?target=clash&new_name=true&url=$subscribe_url&config=$template_path_encode&include=$key_match_param&exclude=$key_ex_match_param&emoji=$emoji&list=false&sort=$sort$udp&scv=$skip_cert_verify&append_type=$node_type&fdn=true$rule_provider$tfo$tls13$append_custom_params"
|
||||
c_address="$convert_address"
|
||||
else
|
||||
subscribe_url=$address
|
||||
@@ -476,4 +497,7 @@ sub_info_get()
|
||||
config_load "openclash"
|
||||
config_foreach sub_info_get "config_subscribe" "$1"
|
||||
dec_job_counter_and_restart "$restart"
|
||||
del_lock
|
||||
del_lock
|
||||
|
||||
# the caller (luci) judges the update result by this exit code
|
||||
exit "$exit_code"
|
||||
@@ -2,6 +2,8 @@
|
||||
. /usr/share/openclash/log.sh
|
||||
. /usr/share/openclash/openclash_etag.sh
|
||||
|
||||
DEFAULT_UA="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36"
|
||||
|
||||
DOWNLOAD_FAILURE_OUTPUT() {
|
||||
failure_exit_code="$1"
|
||||
failure_http_code="$2"
|
||||
@@ -26,7 +28,7 @@ DOWNLOAD_FILE_CURL() {
|
||||
DOWNLOAD_UA=$4
|
||||
SECRET_KEY=$5
|
||||
CUSTOM_HEADERS=$6
|
||||
[ -z "$DOWNLOAD_UA" ] && DOWNLOAD_UA="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
|
||||
[ -z "$DOWNLOAD_UA" ] && DOWNLOAD_UA="$DEFAULT_UA"
|
||||
HEADER_TMP="/tmp/openclash_curl_header_$$"
|
||||
DOWNLOAD_TMP="${DOWNLOAD_PATH}.download.$$"
|
||||
CACHED_ETAG=$(GET_ETAG_BY_PATH "$FILE_PATH")
|
||||
@@ -191,6 +193,14 @@ EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
resolve_checksum_from_url "$DOWNLOAD_URL"
|
||||
|
||||
if ! verify_sha256_checksum "$DOWNLOAD_TMP" "$CHECKSUM_FILENAME" "$CHECKSUM_URL" "$DOWNLOAD_UA"; then
|
||||
LOG_OUT "【${DOWNLOAD_PATH}】Checksum Verification Failed"
|
||||
rm -f "$HEADER_TMP" "$DOWNLOAD_TMP"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! mv -f "$DOWNLOAD_TMP" "$DOWNLOAD_PATH"; then
|
||||
LOG_OUT "【${DOWNLOAD_PATH}】Download Failed:【Unable to save download file】"
|
||||
rm -f "$HEADER_TMP" "$DOWNLOAD_TMP"
|
||||
@@ -206,3 +216,83 @@ EOF
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
CHECKSUM_RAW_PREFIX="https://raw.githubusercontent.com/vernesong/OpenClash"
|
||||
CHECKSUM_OIX_URL="https://github.com/vernesong/mihomo-oix/releases/download/Pre-Alpha/checksums.txt"
|
||||
|
||||
resolve_checksum_from_url() {
|
||||
local url="$1"
|
||||
local base name path
|
||||
|
||||
CHECKSUM_URL=""
|
||||
CHECKSUM_FILENAME=""
|
||||
|
||||
[ -z "$url" ] && return 1
|
||||
|
||||
base="${url%%\?*}"
|
||||
base="${base%%#*}"
|
||||
name="${base##*/}"
|
||||
[ -n "$name" ] || return 1
|
||||
|
||||
case "$name" in
|
||||
clash-*.tar.gz|luci-app-openclash_*.ipk|luci-app-openclash-*.apk|mihomo-*.gz) ;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$base" in
|
||||
*"jsdelivr.net/gh/vernesong/OpenClash@"*)
|
||||
path="${base#*OpenClash@}"
|
||||
[ "${path%/*}" = "$path" ] && return 1
|
||||
CHECKSUM_URL="${CHECKSUM_RAW_PREFIX}/${path%/*}/checksums.txt"
|
||||
;;
|
||||
*"raw.githubusercontent.com/vernesong/OpenClash/"*)
|
||||
path="${base#*OpenClash/}"
|
||||
[ "${path%/*}" = "$path" ] && return 1
|
||||
CHECKSUM_URL="${CHECKSUM_RAW_PREFIX}/${path%/*}/checksums.txt"
|
||||
;;
|
||||
*"mihomo-oix/"*)
|
||||
CHECKSUM_URL="$CHECKSUM_OIX_URL"
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
CHECKSUM_FILENAME="$name"
|
||||
return 0
|
||||
}
|
||||
|
||||
verify_sha256_checksum() {
|
||||
local file="$1"
|
||||
local expected_name="$2"
|
||||
local checksum_url="$3"
|
||||
local ua="${4:-$DEFAULT_UA}"
|
||||
|
||||
[ -z "$file" ] || [ -z "$checksum_url" ] && return 0
|
||||
[ -s "$file" ] || return 0
|
||||
|
||||
local expected_hash
|
||||
expected_hash=$(curl -sL -m 15 --connect-timeout 5 -H "User-Agent: $ua" "$checksum_url" 2>/dev/null \
|
||||
| awk -v n="$expected_name" '$2 ~ n {print $1; exit} $0 ~ n {print $1; exit}')
|
||||
|
||||
if [ -z "$expected_hash" ]; then
|
||||
LOG_WARN "Checksum file unavailable or entry not found, skip verification for【$expected_name】"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local actual_hash
|
||||
actual_hash=$(sha256sum "$file" 2>/dev/null | awk '{print $1}')
|
||||
if [ -z "$actual_hash" ]; then
|
||||
LOG_WARN "Unable to compute checksum, skip verification for【$expected_name】"
|
||||
return 0
|
||||
fi
|
||||
if [ "$actual_hash" = "$expected_hash" ]; then
|
||||
LOG_OUT "Checksum Verification Successful for【$expected_name】"
|
||||
return 0
|
||||
fi
|
||||
|
||||
LOG_ERROR "Checksum mismatch for【$expected_name】(expected【$expected_hash】,got【$actual_hash】)"
|
||||
return 1
|
||||
}
|
||||
@@ -30,8 +30,8 @@ if [ "$(uci_get_config "enable_custom_domain_dns_server")" = "1" ] && [ "$(uci_g
|
||||
|
||||
custom_domain_dns_server=$(uci_get_config "custom_domain_dns_server")
|
||||
[ -z "$custom_domain_dns_server" ] && {
|
||||
custom_domain_dns_server="114.114.114.114"
|
||||
}
|
||||
custom_domain_dns_server="114.114.114.114"
|
||||
}
|
||||
|
||||
if [ -s "/etc/openclash/custom/openclash_custom_domain_dns.list" ]; then
|
||||
mkdir -p ${DNSMASQ_CONF_DIR}
|
||||
|
||||
@@ -28,7 +28,7 @@ DEBUG_LOG="/tmp/openclash_debug.log"
|
||||
LOGTIME=$(echo $(date "+%Y-%m-%d %H:%M:%S"))
|
||||
log_level=$(uci_get_config "log_level")
|
||||
enable_custom_dns=$(uci_get_config "enable_custom_dns")
|
||||
enable_custom_clash_rules=$(uci_get_config "enable_custom_clash_rules")
|
||||
enable_custom_clash_rules=$(uci_get_config "enable_custom_clash_rules")
|
||||
ipv6_enable=$(uci_get_config "ipv6_enable")
|
||||
ipv6_dns=$(uci_get_config "ipv6_dns")
|
||||
enable_redirect_dns=$(uci_get_config "enable_redirect_dns")
|
||||
@@ -49,6 +49,11 @@ fi
|
||||
core_meta_version=$(/etc/openclash/core/clash_meta -v 2>/dev/null |awk -F ' ' '{print $3}' |head -1 2>/dev/null)
|
||||
op_version=$(ipk_v "luci-app-openclash")
|
||||
china_ip_route=$(uci_get_config "china_ip_route")
|
||||
china_ip_route_domain_source=$(uci_get_config "china_ip_route_domain_source" || echo "mrs")
|
||||
case "$china_ip_route_domain_source" in
|
||||
mrs|geosite) ;;
|
||||
*) china_ip_route_domain_source="mrs" ;;
|
||||
esac
|
||||
common_ports=$(uci_get_config "common_ports")
|
||||
router_self_proxy=$(uci_get_config "router_self_proxy")
|
||||
core_type=$(uci_get_config "core_type" || echo "Dev")
|
||||
@@ -89,28 +94,28 @@ fi
|
||||
|
||||
ts_cf()
|
||||
{
|
||||
if [ "$1" = "0" ] || [ -z "$1" ]; then
|
||||
echo "停用"
|
||||
else
|
||||
echo "启用"
|
||||
if [ "$1" = "0" ] || [ -z "$1" ]; then
|
||||
echo "停用"
|
||||
else
|
||||
echo "启用"
|
||||
fi
|
||||
}
|
||||
|
||||
ts_re()
|
||||
{
|
||||
if [ -z "$1" ]; then
|
||||
echo "未安装"
|
||||
else
|
||||
echo "已安装 ($1)"
|
||||
if [ -z "$1" ]; then
|
||||
echo "未安装"
|
||||
else
|
||||
echo "已安装 ($1)"
|
||||
fi
|
||||
}
|
||||
|
||||
dns_re()
|
||||
{
|
||||
if [ "$1" = "1" ]; then
|
||||
echo "Dnsmasq 转发"
|
||||
echo "Dnsmasq 转发"
|
||||
elif [ "$1" = "2" ]; then
|
||||
echo "Firewall 转发"
|
||||
echo "Firewall 转发"
|
||||
else
|
||||
echo "停用"
|
||||
fi
|
||||
@@ -146,6 +151,7 @@ cat > "$DEBUG_LOG" <<-EOF
|
||||
| 系统运行时间 | $(uptime 2>/dev/null) |
|
||||
| IPV6-DHCP | $(uci -q get dhcp.lan.dhcpv6 || echo "未配置") |
|
||||
| DNS劫持 | $(dns_re "$enable_redirect_dns") |
|
||||
| TFO 黑洞超时(秒) | $(cat /proc/sys/net/ipv4/tcp_fastopen_blackhole_timeout_sec 2>/dev/null || echo "不支持") |
|
||||
|
||||
### 磁盘与内存
|
||||
|
||||
@@ -311,8 +317,9 @@ cat >> "$DEBUG_LOG" <<-EOF
|
||||
| 仅代理命中规则流量 | $(ts_cf "$enable_rule_proxy") |
|
||||
| 仅允许常用端口流量 | $(ts_cf "$common_ports") |
|
||||
| 绕过中国大陆IP | $(ts_cf "$china_ip_route") |
|
||||
| 中国大陆域名数据源 | $china_ip_route_domain_source |
|
||||
| 路由本机代理 | $(ts_cf "$router_self_proxy") |
|
||||
| TUN堆栈类型 | ${stack_type:-system} |
|
||||
| TUN堆栈类型 | ${stack_type:-mips} |
|
||||
| 启动延迟 | ${delay_start:-0}秒 |
|
||||
| 日志大小 | ${log_size:-1024}KB |
|
||||
| 旁路由兼容 | $(ts_cf "$bypass_gateway_compatible") |
|
||||
@@ -800,4 +807,4 @@ sed -i -E 's/([0-9a-fA-F]{1,4})(:[0-9a-fA-F]{1,4}){0,5}::([0-9a-fA-F]{1,4}(:[0-9
|
||||
|
||||
sed -i 's/Downloading URL【[^】]*】/Downloading URL【*】/g' "$DEBUG_LOG" 2>/dev/null
|
||||
|
||||
del_lock
|
||||
del_lock
|
||||
@@ -13,7 +13,7 @@ local resolve = arg[2]
|
||||
|
||||
local function debug_dns()
|
||||
local info, ip, host
|
||||
ip = fs.lanip()
|
||||
ip = fs.lanip(true)
|
||||
local port = fs.uci_get_config("config", "cn_port")
|
||||
local passwd = fs.uci_get_config("config", "dashboard_password") or ""
|
||||
|
||||
|
||||
@@ -9,60 +9,104 @@ local json = require "luci.jsonc"
|
||||
local datatype = require "luci.cbi.datatypes"
|
||||
local addr = arg[1]
|
||||
|
||||
if addr and not (datatype.hostname(addr) or datatype.ipaddr(addr)) then
|
||||
os.exit(0)
|
||||
end
|
||||
|
||||
local function fetch_connections(ip, port, passwd)
|
||||
local raw = luci.sys.exec(string.format('curl -sL -m 3 -H "Content-Type: application/json" -H "Authorization: Bearer %s" -XGET http://"%s":"%s"/connections', passwd, ip, port))
|
||||
if not raw or raw == "" then
|
||||
return nil
|
||||
end
|
||||
return json.parse(raw)
|
||||
end
|
||||
|
||||
local function print_connection(c)
|
||||
print("id: "..(c.id))
|
||||
print("start: "..(c.start))
|
||||
print("download: "..fs.filesize(c.download))
|
||||
print("upload: "..fs.filesize(c.upload))
|
||||
print("rule: "..(c.rule))
|
||||
print("rulePayload: "..(c.rulePayload))
|
||||
print("chains: ")
|
||||
for o = 1, #(c.chains) do
|
||||
print(" "..o..": "..(c.chains[o]))
|
||||
end
|
||||
print("metadata: ")
|
||||
print(" sourceIP: "..(c.metadata.sourceIP))
|
||||
print(" sourcePort: "..(c.metadata.sourcePort))
|
||||
print(" host: "..(c.metadata.host == "" and "Empty" or c.metadata.host))
|
||||
print(" destinationIP: "..(c.metadata.destinationIP))
|
||||
print(" destinationPort: "..(c.metadata.destinationPort))
|
||||
print(" network: "..(c.metadata.network))
|
||||
print(" type: "..(c.metadata.type))
|
||||
print("")
|
||||
end
|
||||
|
||||
local function scan_connections(ip, port, passwd)
|
||||
local info = fetch_connections(ip, port, passwd)
|
||||
if not info then
|
||||
return false
|
||||
end
|
||||
local found = false
|
||||
for i = 1, #(info.connections) do
|
||||
local c = info.connections[i]
|
||||
local host = (c.metadata.host == "" and "Empty" or c.metadata.host)
|
||||
if (datatype.hostname(addr) and string.lower(addr) == host) or (datatype.ipaddr(addr) and addr == c.metadata.destinationIP) then
|
||||
found = true
|
||||
print_connection(c)
|
||||
end
|
||||
end
|
||||
return found
|
||||
end
|
||||
|
||||
local function dump_connections(info)
|
||||
local conn_lines = {}
|
||||
for i = 1, #(info.connections) do
|
||||
local c = info.connections[i]
|
||||
local host = (c.metadata.host == "" and "Empty" or c.metadata.host)
|
||||
conn_lines[#conn_lines + 1] = string.format("%d. SourceIP:【%s】 - Host:【%s】 - DestinationIP:【%s】 - Network:【%s】 - RulePayload:【%s】 - Lastchain:【%s】\n",
|
||||
i,
|
||||
tostring(c.metadata.sourceIP),
|
||||
tostring(host),
|
||||
tostring(c.metadata.destinationIP),
|
||||
tostring(c.metadata.network),
|
||||
tostring(c.rulePayload),
|
||||
tostring(c.chains and c.chains[1]))
|
||||
end
|
||||
if #conn_lines > 0 then
|
||||
local existing = fs.readfile("/tmp/openclash_debug.log") or ""
|
||||
fs.writefile("/tmp/openclash_debug.log", existing .. table.concat(conn_lines))
|
||||
end
|
||||
end
|
||||
|
||||
local function debug_getcon()
|
||||
local info, ip, host, diag_info
|
||||
ip = fs.lanip()
|
||||
local ip = fs.lanip(true)
|
||||
local port = fs.uci_get_config("config", "cn_port")
|
||||
local passwd = fs.uci_get_config("config", "dashboard_password") or ""
|
||||
if ip and port then
|
||||
info = luci.sys.exec(string.format('curl -sL -m 3 -H "Content-Type: application/json" -H "Authorization: Bearer %s" -XGET http://"%s":"%s"/connections', passwd, ip, port))
|
||||
if not ip or not port then
|
||||
os.exit(0)
|
||||
end
|
||||
|
||||
if not addr then
|
||||
local info = fetch_connections(ip, port, passwd)
|
||||
if info then
|
||||
info = json.parse(info)
|
||||
dump_connections(info)
|
||||
end
|
||||
if info then
|
||||
local conn_lines = {}
|
||||
for i = 1, #(info.connections) do
|
||||
if info.connections[i].metadata.host == "" then
|
||||
host = "Empty"
|
||||
else
|
||||
host = info.connections[i].metadata.host
|
||||
end
|
||||
if not addr then
|
||||
conn_lines[#conn_lines + 1] = string.format("%d. SourceIP:【%s】 - Host:【%s】 - DestinationIP:【%s】 - Network:【%s】 - RulePayload:【%s】 - Lastchain:【%s】\n",
|
||||
i,
|
||||
tostring(info.connections[i].metadata.sourceIP),
|
||||
tostring(host),
|
||||
tostring(info.connections[i].metadata.destinationIP),
|
||||
tostring(info.connections[i].metadata.network),
|
||||
tostring(info.connections[i].rulePayload),
|
||||
tostring(info.connections[i].chains and info.connections[i].chains[1]))
|
||||
else
|
||||
if datatype.hostname(addr) and string.lower(addr) == host or datatype.ipaddr(addr) and addr == (info.connections[i].metadata.destinationIP) then
|
||||
print("id: "..(info.connections[i].id))
|
||||
print("start: "..(info.connections[i].start))
|
||||
print("download: "..fs.filesize(info.connections[i].download))
|
||||
print("upload: "..fs.filesize(info.connections[i].upload))
|
||||
print("rule: "..(info.connections[i].rule))
|
||||
print("rulePayload: "..(info.connections[i].rulePayload))
|
||||
print("chains: ")
|
||||
for o = 1, #(info.connections[i].chains) do
|
||||
print(" "..o..": "..(info.connections[i].chains[o]))
|
||||
end
|
||||
print("metadata: ")
|
||||
print(" sourceIP: "..(info.connections[i].metadata.sourceIP))
|
||||
print(" sourcePort: "..(info.connections[i].metadata.sourcePort))
|
||||
print(" host: "..host)
|
||||
print(" destinationIP: "..(info.connections[i].metadata.destinationIP))
|
||||
print(" destinationPort: "..(info.connections[i].metadata.destinationPort))
|
||||
print(" network: "..(info.connections[i].metadata.network))
|
||||
print(" type: "..(info.connections[i].metadata.type))
|
||||
print("")
|
||||
end
|
||||
end
|
||||
end
|
||||
if not addr and #conn_lines > 0 then
|
||||
local existing = fs.readfile("/tmp/openclash_debug.log") or ""
|
||||
fs.writefile("/tmp/openclash_debug.log", existing .. table.concat(conn_lines))
|
||||
os.exit(0)
|
||||
end
|
||||
|
||||
local found = scan_connections(ip, port, passwd)
|
||||
if not found then
|
||||
luci.sys.call(string.format('( (sleep 5) | nc %s 443 ) >/dev/null 2>&1 &', addr))
|
||||
luci.sys.call(string.format('( (sleep 5) | openssl s_client -connect %s:443 -servername %s -quiet -no_ign_eof ) >/dev/null 2>&1 &', addr, addr))
|
||||
local deadline = os.time() + 5
|
||||
local polls = 0
|
||||
while os.time() < deadline and not found and polls < 5 do
|
||||
polls = polls + 1
|
||||
found = scan_connections(ip, port, passwd)
|
||||
if not found and polls < 5 then
|
||||
luci.sys.call("sleep 1")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -72,20 +72,20 @@ EOF
|
||||
github_address_mod=$(uci_get_config "github_address_mod" || echo 0)
|
||||
if [ "$DASH_NAME" == "Dashboard" ]; then
|
||||
UNPACK_FILE_DIR="/usr/share/openclash/ui/dashboard/"
|
||||
if [ "$DASH_TYPE" == "Official" ]; then
|
||||
DOWNLOAD_PATH="https://codeload.github.com/ayanamist/clash-dashboard/zip/refs/heads/gh-pages"
|
||||
if [ "$DASH_TYPE" == "Official" ]; then
|
||||
DOWNLOAD_PATH="https://codeload.github.com/ayanamist/clash-dashboard/zip/refs/heads/gh-pages"
|
||||
FILE_PATH_INCLUDE="clash-dashboard-gh-pages"
|
||||
else
|
||||
DOWNLOAD_PATH="https://codeload.github.com/MetaCubeX/Razord-meta/zip/refs/heads/gh-pages"
|
||||
DOWNLOAD_PATH="https://codeload.github.com/MetaCubeX/Razord-meta/zip/refs/heads/gh-pages"
|
||||
FILE_PATH_INCLUDE="Razord-meta-gh-pages"
|
||||
fi
|
||||
elif [ "$DASH_NAME" == "Yacd" ]; then
|
||||
elif [ "$DASH_NAME" == "Yacd" ]; then
|
||||
UNPACK_FILE_DIR="/usr/share/openclash/ui/yacd/"
|
||||
if [ "$DASH_TYPE" == "Official" ]; then
|
||||
DOWNLOAD_PATH="https://codeload.github.com/haishanh/yacd/zip/refs/heads/gh-pages"
|
||||
if [ "$DASH_TYPE" == "Official" ]; then
|
||||
DOWNLOAD_PATH="https://codeload.github.com/haishanh/yacd/zip/refs/heads/gh-pages"
|
||||
FILE_PATH_INCLUDE="yacd-gh-pages"
|
||||
else
|
||||
DOWNLOAD_PATH="https://codeload.github.com/MetaCubeX/Yacd-meta/zip/refs/heads/gh-pages"
|
||||
DOWNLOAD_PATH="https://codeload.github.com/MetaCubeX/Yacd-meta/zip/refs/heads/gh-pages"
|
||||
FILE_PATH_INCLUDE="Yacd-meta-gh-pages"
|
||||
fi
|
||||
elif [ "$DASH_NAME" == "Zashboard" ]; then
|
||||
@@ -94,9 +94,9 @@ EOF
|
||||
FILE_PATH_INCLUDE="zashboard-gh-pages-cdn-fonts"
|
||||
else
|
||||
UNPACK_FILE_DIR="/usr/share/openclash/ui/metacubexd/"
|
||||
DOWNLOAD_PATH="https://codeload.github.com/MetaCubeX/metacubexd/zip/refs/heads/gh-pages"
|
||||
DOWNLOAD_PATH="https://codeload.github.com/MetaCubeX/metacubexd/zip/refs/heads/gh-pages"
|
||||
FILE_PATH_INCLUDE="metacubexd-gh-pages"
|
||||
fi
|
||||
fi
|
||||
TARGET_FILE_DIR="${UNPACK_FILE_DIR%/}"
|
||||
TARGET_PARENT_DIR="$(dirname "$TARGET_FILE_DIR")"
|
||||
NEW_FILE_DIR="${TARGET_PARENT_DIR}/.openclash_dashboard_new.$$"
|
||||
|
||||
@@ -9,7 +9,7 @@ GET_ETAG_TIMESTAMP_BY_PATH() {
|
||||
[ ! -f "$ETAG_CACHE" ] && return 1
|
||||
|
||||
local path_hash=$(echo -n "$path" | md5sum | cut -d' ' -f1)
|
||||
|
||||
|
||||
awk -v hash="$path_hash" '
|
||||
$0 ~ "^\\[" hash "\\]" { found=1; next }
|
||||
/^\[/ { found=0 }
|
||||
@@ -23,7 +23,7 @@ GET_ETAG_BY_PATH() {
|
||||
[ ! -f "$ETAG_CACHE" ] && return 1
|
||||
|
||||
local path_hash=$(echo -n "$path" | md5sum | cut -d' ' -f1)
|
||||
|
||||
|
||||
awk -v hash="$path_hash" '
|
||||
$0 ~ "^\\[" hash "\\]" { found=1; next }
|
||||
/^\[/ { found=0 }
|
||||
@@ -33,16 +33,16 @@ GET_ETAG_BY_PATH() {
|
||||
|
||||
# 保存或更新 ETag
|
||||
SAVE_ETAG_TO_CACHE() {
|
||||
local url="\"$1\""
|
||||
local etag="\"$2\""
|
||||
local path="\"$3\""
|
||||
local url="\"$1\""
|
||||
local etag="\"$2\""
|
||||
local path="\"$3\""
|
||||
local time="\"$(date '+%Y-%m-%d %H:%M:%S')\""
|
||||
local path_hash=$(echo -n "$3" | md5sum | cut -d' ' -f1)
|
||||
|
||||
|
||||
mkdir -p "$(dirname "$ETAG_CACHE")"
|
||||
|
||||
|
||||
[ ! -f "$ETAG_CACHE" ] && echo "# ETag Cache File" > "$ETAG_CACHE"
|
||||
|
||||
|
||||
if grep -q "^\[$path_hash\]" "$ETAG_CACHE"; then
|
||||
local temp_file="${ETAG_CACHE}.tmp"
|
||||
awk -v hash="$path_hash" \
|
||||
@@ -50,27 +50,27 @@ SAVE_ETAG_TO_CACHE() {
|
||||
-v new_etag="$etag" \
|
||||
-v new_path="$path" \
|
||||
-v new_time="$time" '
|
||||
$0 ~ "^\\[" hash "\\]" {
|
||||
print;
|
||||
found=1;
|
||||
next
|
||||
$0 ~ "^\\[" hash "\\]" {
|
||||
print;
|
||||
found=1;
|
||||
next
|
||||
}
|
||||
/^\[/ { found=0 }
|
||||
found && /^url=/ {
|
||||
print "url=" new_url;
|
||||
next
|
||||
found && /^url=/ {
|
||||
print "url=" new_url;
|
||||
next
|
||||
}
|
||||
found && /^path=/ {
|
||||
print "path=" new_path;
|
||||
next
|
||||
found && /^path=/ {
|
||||
print "path=" new_path;
|
||||
next
|
||||
}
|
||||
found && /^etag=/ {
|
||||
print "etag=" new_etag;
|
||||
next
|
||||
found && /^etag=/ {
|
||||
print "etag=" new_etag;
|
||||
next
|
||||
}
|
||||
found && /^time=/ {
|
||||
print "time=" new_time;
|
||||
next
|
||||
found && /^time=/ {
|
||||
print "time=" new_time;
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
' "$ETAG_CACHE" > "$temp_file" && mv "$temp_file" "$ETAG_CACHE"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/lua
|
||||
|
||||
require "nixio"
|
||||
require "luci.util"
|
||||
require "luci.sys"
|
||||
local util = require "luci.util"
|
||||
local ntm = require "luci.model.network".init()
|
||||
local cidr = require "luci.ip"
|
||||
local fs = require "luci.openclash"
|
||||
@@ -12,18 +12,62 @@ local wan, wan6
|
||||
|
||||
if not type then os.exit(0) end
|
||||
|
||||
if pcall(function() local x = ntm:get_all_wan_networks(); local y = ntm:get_all_wan6_networks(); end) then
|
||||
wan = ntm:get_all_wan_networks()
|
||||
wan6 = ntm:get_all_wan6_networks()
|
||||
elseif pcall(function() local x = ntm:get_wan_networks(); local y = ntm:get_wan6_networks(); end) then
|
||||
wan = ntm:get_wan_networks()
|
||||
wan6 = ntm:get_wan6_networks()
|
||||
elseif pcall(function() local x = ntm:get_wannet(); local y = ntm:get_wan6net(); end) then
|
||||
wan = {}
|
||||
wan6 = {}
|
||||
wan[1] = ntm:get_wannet()
|
||||
wan6[1] = ntm:get_wan6net()
|
||||
else
|
||||
local function get_all_wan_networks()
|
||||
local nets = { }
|
||||
local _, object
|
||||
for _, object in ipairs(util.ubus()) do
|
||||
local name = object:match("^network%.interface%.(.+)")
|
||||
if name then
|
||||
local stat = util.ubus(object, "status", { })
|
||||
if stat and stat.route then
|
||||
local rt
|
||||
for _, rt in ipairs(stat.route) do
|
||||
if not rt.table and rt.target == "0.0.0.0" and rt.mask == 0 then
|
||||
local net = ntm:get_network(name)
|
||||
if net then
|
||||
nets[#nets+1] = net
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return nets
|
||||
end
|
||||
|
||||
local function get_all_wan6_networks()
|
||||
local nets = { }
|
||||
local _, object
|
||||
for _, object in ipairs(util.ubus()) do
|
||||
local name = object:match("^network%.interface%.(.+)")
|
||||
if name then
|
||||
local stat = util.ubus(object, "status", { })
|
||||
if stat and stat.route then
|
||||
local rt
|
||||
for _, rt in ipairs(stat.route) do
|
||||
if not rt.table and rt.target == "::" and rt.mask == 0 then
|
||||
local net = ntm:get_network(name)
|
||||
if net then
|
||||
nets[#nets+1] = net
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return nets
|
||||
end
|
||||
|
||||
local ok = pcall(function() wan = get_all_wan_networks(); wan6 = get_all_wan6_networks(); end)
|
||||
if not ok then
|
||||
ok = pcall(function() wan = ntm:get_wan_networks(); wan6 = ntm:get_wan6_networks(); end)
|
||||
end
|
||||
if not ok then
|
||||
ok = pcall(function() wan = { ntm:get_wannet() }; wan6 = { ntm:get_wan6net() }; end)
|
||||
end
|
||||
if not ok then
|
||||
os.exit(0)
|
||||
end
|
||||
|
||||
@@ -63,7 +107,7 @@ if type == "dns" then
|
||||
if wan then
|
||||
for o = 1, #(rv.wan) do
|
||||
for i = 1, #(rv.wan[o].dns) do
|
||||
if rv.wan[o].dns[i] ~= rv.wan[o].gwaddr and rv.wan[o].dns[i] ~= rv.wan[o].ipaddr then
|
||||
if rv.wan[o].dns[i] ~= rv.wan[o].ipaddr then
|
||||
print(rv.wan[o].dns[i])
|
||||
end
|
||||
end
|
||||
@@ -75,7 +119,7 @@ if type == "dns6" then
|
||||
if wan6 then
|
||||
for o = 1, #(rv.wan6) do
|
||||
for i = 1, #(rv.wan6[o].dns) do
|
||||
if rv.wan6[o].dns[i] ~= rv.wan6[o].gw6addr and rv.wan6[o].ip6addr then
|
||||
if rv.wan6[o].dns[i] ~= rv.wan6[o].ip6addr then
|
||||
print(rv.wan6[o].dns[i])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/bin/sh
|
||||
# Same-origin reverse proxy for the mihomo control API (https://<luci host>/oc-api/, see
|
||||
# ocGetDashboardApiOrigin in common.js), an https page cannot open ws://. -n skips the
|
||||
# reload for callers that reload nginx themselves.
|
||||
|
||||
[ -d /etc/nginx/conf.d ] || exit 0
|
||||
|
||||
PORT=$(uci -q get openclash.config.cn_port)
|
||||
[ -n "$PORT" ] || PORT=9090
|
||||
FILE=/etc/nginx/conf.d/openclash.locations
|
||||
|
||||
NEW=$(cat <<EOF
|
||||
location /oc-api/ {
|
||||
proxy_pass http://127.0.0.1:${PORT}/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade \$http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
proxy_buffering off;
|
||||
}
|
||||
EOF
|
||||
)
|
||||
nginx -V 2>&1 | grep -q 'without-http_proxy_module' && NEW=""
|
||||
|
||||
if [ -f "$FILE" ] && [ "$(cat "$FILE")" = "$NEW" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -n "$NEW" ]; then
|
||||
printf '%s\n' "$NEW" > "$FILE"
|
||||
else
|
||||
rm -f "$FILE"
|
||||
fi
|
||||
|
||||
[ "$1" = "-n" ] || /etc/init.d/nginx reload >/dev/null 2>&1
|
||||
exit 0
|
||||
@@ -4,35 +4,35 @@
|
||||
JOB_COUNTER_FILE="/tmp/openclash_jobs"
|
||||
|
||||
unify_ps_status() {
|
||||
if [ "$(ps --version 2>&1 |grep -c procps-ng)" -eq 1 ]; then
|
||||
echo "$(ps -efw |grep -v grep |grep -c "$1")"
|
||||
else
|
||||
echo "$(ps -w |grep -v grep |grep -c "$1")"
|
||||
fi
|
||||
if [ "$(ps --version 2>&1 |grep -c procps-ng)" -eq 1 ]; then
|
||||
echo "$(ps -efw |grep -v grep |grep -c "$1")"
|
||||
else
|
||||
echo "$(ps -w |grep -v grep |grep -c "$1")"
|
||||
fi
|
||||
}
|
||||
|
||||
unify_ps_pids() {
|
||||
if [ "$(ps --version 2>&1 |grep -c procps-ng)" -eq 1 ]; then
|
||||
echo "$(ps -efw |grep "$1" |grep -v grep |awk '{print $2}' 2>/dev/null)"
|
||||
else
|
||||
echo "$(ps -w |grep "$1" |grep -v grep |awk '{print $1}' 2>/dev/null)"
|
||||
fi
|
||||
if [ "$(ps --version 2>&1 |grep -c procps-ng)" -eq 1 ]; then
|
||||
echo "$(ps -efw |grep "$1" |grep -v grep |awk '{print $2}' 2>/dev/null)"
|
||||
else
|
||||
echo "$(ps -w |grep "$1" |grep -v grep |awk '{print $1}' 2>/dev/null)"
|
||||
fi
|
||||
}
|
||||
|
||||
unify_ps_prevent() {
|
||||
if [ "$(ps --version 2>&1 |grep -c procps-ng)" -eq 1 ]; then
|
||||
echo "$(ps -efw |grep -v grep |grep -c "/etc/init.d/openclash")"
|
||||
else
|
||||
echo "$(ps -w |grep -v grep |grep -c "/etc/init.d/openclash")"
|
||||
fi
|
||||
if [ "$(ps --version 2>&1 |grep -c procps-ng)" -eq 1 ]; then
|
||||
echo "$(ps -efw |grep -v grep |grep -c "/etc/init.d/openclash")"
|
||||
else
|
||||
echo "$(ps -w |grep -v grep |grep -c "/etc/init.d/openclash")"
|
||||
fi
|
||||
}
|
||||
|
||||
unify_ps_cfgname() {
|
||||
if [ "$(ps --version 2>&1 |grep -c procps-ng)" -eq 1 ]; then
|
||||
echo "$(ps -efw |grep /etc/openclash/clash 2>/dev/null |grep -v grep |awk -F '-f ' '{print $2}' 2>/dev/null)"
|
||||
else
|
||||
echo "$(ps -w |grep /etc/openclash/clash 2>/dev/null |grep -v grep |awk -F '-f ' '{print $2}' 2>/dev/null)"
|
||||
fi
|
||||
if [ "$(ps --version 2>&1 |grep -c procps-ng)" -eq 1 ]; then
|
||||
echo "$(ps -efw |grep /etc/openclash/clash 2>/dev/null |grep -v grep |awk -F '-f ' '{print $2}' 2>/dev/null)"
|
||||
else
|
||||
echo "$(ps -w |grep /etc/openclash/clash 2>/dev/null |grep -v grep |awk -F '-f ' '{print $2}' 2>/dev/null)"
|
||||
fi
|
||||
}
|
||||
|
||||
inc_job_counter() {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,219 +1,198 @@
|
||||
#!/usr/bin/lua
|
||||
-- Parse subscription files (plain or base64) and extract server addresses.
|
||||
-- Prints one server address per node of a subscription file, the fallback of the watchdog
|
||||
-- for provider files that are not YAML.
|
||||
--
|
||||
-- The share links are the ones mihomo accepts (common/convert/converter.go), they follow
|
||||
-- the schemes of the clients themselves:
|
||||
-- ss:// SIP002, with the plugin query, or the legacy fully encoded form
|
||||
-- ssr:// base64 of host:port:protocol:method:obfs:password/?params
|
||||
-- vmess:// V2RayN base64 json, or the Xray VMessAEAD uri (XTLS/Xray-core#716)
|
||||
-- vless:// Xray uri, the host of the uri may be base64 encoded
|
||||
-- trojan:// password as userinfo
|
||||
-- hysteria:// github.com/apernet/hysteria (docs/developers/URI-Scheme)
|
||||
-- hysteria2:// and hy2://, also with the +realm suffix and with port hopping
|
||||
-- tuic:// temporary unofficial standard, uuid:password or the v5 token as userinfo
|
||||
-- anytls:// github.com/anytls/anytls-go (docs/uri_scheme.md)
|
||||
-- mierus:// one uri per profile, the port and protocol query keys are repeated
|
||||
-- socks5://, socks5h://, http://, https://
|
||||
--
|
||||
-- Only the server address is needed, every other part of the uri is ignored.
|
||||
|
||||
local nixio = require "nixio"
|
||||
local jsonc = require "luci.jsonc"
|
||||
local util = require "luci.util"
|
||||
local fs = require "luci.openclash"
|
||||
local jsonc = require "luci.jsonc"
|
||||
|
||||
if not nixio.fs.access("/usr/bin/base64") and not nixio.fs.access("/bin/base64") then
|
||||
os.exit(1)
|
||||
local BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
local BASE64_PAD = 61
|
||||
|
||||
-- Decodes base64 in every form a subscription may use: standard or url safe, padded or not.
|
||||
-- A string that is not base64 at all is not a link payload, the caller gets nil.
|
||||
local function decode64(text)
|
||||
if not text then
|
||||
return nil
|
||||
end
|
||||
text = text:gsub("[%s]", ""):gsub("%-", "+"):gsub("_", "/"):gsub("=+$", "")
|
||||
if text == "" or #text % 4 == 1 or not text:match("^[A-Za-z0-9+/]+$") then
|
||||
return nil
|
||||
end
|
||||
text = text .. ("===="):sub(1, (4 - #text % 4) % 4)
|
||||
local result = {}
|
||||
for group in text:gmatch("....") do
|
||||
local c3, c4 = group:byte(3, 4)
|
||||
local i1 = BASE64:find(group:sub(1, 1), 1, true)
|
||||
local i2 = BASE64:find(group:sub(2, 2), 1, true)
|
||||
local i3 = c3 == BASE64_PAD and 0 or BASE64:find(group:sub(3, 3), 1, true)
|
||||
local i4 = c4 == BASE64_PAD and 0 or BASE64:find(group:sub(4, 4), 1, true)
|
||||
if not (i1 and i2 and i3 and i4) then
|
||||
return nil
|
||||
end
|
||||
i1, i2, i3, i4 = i1 - 1, i2 - 1, i3 - 1, i4 - 1
|
||||
result[#result + 1] = string.char(i1 * 4 + math.floor(i2 / 16))
|
||||
if c3 ~= BASE64_PAD then
|
||||
result[#result + 1] = string.char((i2 % 16) * 16 + math.floor(i3 / 4))
|
||||
end
|
||||
if c4 ~= BASE64_PAD then
|
||||
result[#result + 1] = string.char((i3 % 4) * 64 + i4)
|
||||
end
|
||||
end
|
||||
return table.concat(result)
|
||||
end
|
||||
|
||||
local file_path = arg[1]
|
||||
if not file_path or not nixio.fs.access(file_path) then
|
||||
os.exit(1)
|
||||
-- scheme, userinfo, host, port, query and fragment of a uri. A host without a port and an
|
||||
-- IPv6 host in brackets are accepted, the port of a hop link is a list of ports.
|
||||
local function parse_uri(uri)
|
||||
local scheme, body = uri:match("^([%w%+%-%.]+)://(.*)$")
|
||||
if not scheme then
|
||||
return nil
|
||||
end
|
||||
local authority = body:match("^([^/%?#]*)") or ""
|
||||
local remainder = body:sub(#authority + 1)
|
||||
local userinfo, hostport = authority:match("^(.*)@([^@]+)$")
|
||||
if not hostport then
|
||||
userinfo, hostport = nil, authority
|
||||
end
|
||||
local host, port
|
||||
if hostport:match("^%[") then
|
||||
host, port = hostport:match("^%[([^%]]+)%]:?([%d,%-]*)$")
|
||||
else
|
||||
host, port = hostport:match("^([^:]*):([%d,%-]*)$")
|
||||
end
|
||||
return {
|
||||
scheme = scheme:lower(),
|
||||
userinfo = userinfo,
|
||||
host = host or hostport,
|
||||
port = port,
|
||||
path = remainder:match("^[^%?#]*"),
|
||||
query = remainder:match("%?([^#]*)"),
|
||||
fragment = remainder:match("#(.*)$")
|
||||
}
|
||||
end
|
||||
|
||||
local function base64_decode_subscription(str)
|
||||
local result = fs.decode64(str)
|
||||
if result and result:find("://") then
|
||||
return result
|
||||
end
|
||||
return nil
|
||||
-- mihomo decodes the host of a vless uri when it is base64 encoded, the watchdog can only
|
||||
-- use it when the result looks like an address
|
||||
local function decoded_host(host)
|
||||
local decoded = decode64(host)
|
||||
if decoded and decoded:match("^[%w%.%-%_]+$") and decoded:find("%.") then
|
||||
return decoded
|
||||
end
|
||||
return host
|
||||
end
|
||||
|
||||
local function url_decode(str)
|
||||
return str:gsub("%%(%x%x)", function(h) return string.char(tonumber(h, 16)) end)
|
||||
local function without_brackets(host)
|
||||
if not host then
|
||||
return nil
|
||||
end
|
||||
return host:match("^%[(.-)%]$") or host
|
||||
end
|
||||
|
||||
local function parse_query_params(query_string)
|
||||
local params = {}
|
||||
if not query_string then return params end
|
||||
-- The server address of one share link, nil when the line is not a link the plugin knows
|
||||
local function server_of(uri)
|
||||
local parsed = parse_uri(uri)
|
||||
if not parsed then
|
||||
return nil
|
||||
end
|
||||
local scheme = parsed.scheme
|
||||
local host = parsed.host
|
||||
|
||||
for param in query_string:gmatch("([^&]+)") do
|
||||
local key, value = param:match("([^=]*)=?(.*)")
|
||||
if key then
|
||||
params[key] = url_decode(value or "")
|
||||
end
|
||||
end
|
||||
return params
|
||||
if scheme == "vmess" then
|
||||
-- V2RayN: the whole link body is the base64 of a json document
|
||||
local decoded = decode64((uri:match("^%w+://([^#]+)") or ""))
|
||||
if decoded and decoded:sub(1, 1) == "{" then
|
||||
local ok, node = pcall(jsonc.parse, decoded)
|
||||
if ok and type(node) == "table" and type(node.add) == "string" and node.add ~= "" then
|
||||
return without_brackets(node.add)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
-- Xray VMessAEAD: uuid@host:port
|
||||
return host
|
||||
end
|
||||
|
||||
if scheme == "ss" then
|
||||
if parsed.port and parsed.port ~= "" then
|
||||
return host
|
||||
end
|
||||
-- the legacy link is the base64 of method:password@host:port
|
||||
local decoded = decode64(host)
|
||||
if decoded then
|
||||
return decoded:match("@%[([^%]]+)%]") or decoded:match("@([^:/]+)")
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
if scheme == "ssr" then
|
||||
local decoded = decode64((uri:match("^%w+://([^#]+)") or ""))
|
||||
if not decoded then
|
||||
return nil
|
||||
end
|
||||
return decoded:match("^([^:]+):")
|
||||
end
|
||||
|
||||
if scheme == "hysteria2" or scheme == "hy2" or scheme == "hysteria2+realm" or scheme == "hy2+realm" then
|
||||
return without_brackets(host)
|
||||
end
|
||||
|
||||
if scheme == "vless" then
|
||||
return decoded_host(host)
|
||||
end
|
||||
|
||||
if scheme == "hysteria" or scheme == "tuic" or scheme == "trojan" or scheme == "mierus"
|
||||
or scheme == "anytls" or scheme == "socks" or scheme == "socks5" or scheme == "socks5h"
|
||||
or scheme == "http" or scheme == "https" then
|
||||
return without_brackets(host)
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
local function parse_url(url_str)
|
||||
local scheme, rest = url_str:match("^([%w%-]+)://(.+)")
|
||||
if not scheme then return nil end
|
||||
|
||||
local userinfo, host_part = rest:match("^([^@]+)@(.+)")
|
||||
if not userinfo then
|
||||
userinfo = ""
|
||||
host_part = rest
|
||||
end
|
||||
|
||||
local path_query_fragment = host_part:match("^[^/]*(.*)") or ""
|
||||
local host_port = host_part:match("^([^/]*)")
|
||||
|
||||
local host, port = host_port:match("^%[([^%]]+)%]:(%d+)") -- IPv6
|
||||
if not host then
|
||||
host, port = host_port:match("^([^:]+):(%d+)")
|
||||
end
|
||||
if not host then
|
||||
host = host_port
|
||||
port = nil
|
||||
end
|
||||
|
||||
local path, query_fragment = path_query_fragment:match("^([^?]*)(.*)")
|
||||
path = path or ""
|
||||
|
||||
local query, fragment
|
||||
if query_fragment then
|
||||
if query_fragment:sub(1,1) == "?" then
|
||||
query, fragment = query_fragment:sub(2):match("^([^#]*)(.*)")
|
||||
if fragment and fragment:sub(1,1) == "#" then
|
||||
fragment = fragment:sub(2)
|
||||
end
|
||||
elseif query_fragment:sub(1,1) == "#" then
|
||||
fragment = query_fragment:sub(2)
|
||||
end
|
||||
end
|
||||
|
||||
local username, password = userinfo:match("^([^:]*):?(.*)")
|
||||
|
||||
return {
|
||||
scheme = scheme:lower(),
|
||||
username = username or "",
|
||||
password = password or "",
|
||||
host = host,
|
||||
port = port,
|
||||
path = path,
|
||||
query = query,
|
||||
fragment = fragment
|
||||
}
|
||||
-- A provider file that is a broken yaml still names the address of its nodes
|
||||
local function server_of_yaml(line)
|
||||
local value = line:match("^%s*server%s*:%s*([^%s#%]]+)")
|
||||
if not value then
|
||||
return nil
|
||||
end
|
||||
return value:gsub("^[\"']", ""):gsub("[\"']$", "")
|
||||
end
|
||||
|
||||
local function get_server_from_url(line)
|
||||
line = util.trim(line)
|
||||
local scheme, original_body = line:match("^([%w%-]+)://(.+)")
|
||||
if not scheme or not original_body then return nil end
|
||||
local path = arg[1]
|
||||
local file = path and io.open(path, "r")
|
||||
if not file then
|
||||
os.exit(1)
|
||||
end
|
||||
local content = file:read("*a")
|
||||
file:close()
|
||||
|
||||
local server = nil
|
||||
scheme = scheme:lower()
|
||||
|
||||
if scheme == "vmess" then
|
||||
-- fragment
|
||||
original_body = original_body:match("([^#]+)") or original_body
|
||||
|
||||
-- base64
|
||||
local decoded = fs.decode64(original_body)
|
||||
if decoded then
|
||||
-- V2RayN JSON
|
||||
local ok, data = pcall(jsonc.parse, decoded)
|
||||
if ok and type(data) == "table" and data.add and data.add ~= "" then
|
||||
server = data.add
|
||||
end
|
||||
else
|
||||
-- Xray VMessAEAD
|
||||
local url_parts = parse_url(line)
|
||||
if url_parts and url_parts.host then
|
||||
server = url_parts.host
|
||||
end
|
||||
end
|
||||
|
||||
elseif scheme == "vless" then
|
||||
local url_parts = parse_url(line)
|
||||
if url_parts and url_parts.host then
|
||||
server = url_parts.host
|
||||
end
|
||||
|
||||
elseif scheme == "trojan" then
|
||||
local url_parts = parse_url(line)
|
||||
if url_parts and url_parts.host then
|
||||
server = url_parts.host
|
||||
end
|
||||
|
||||
elseif scheme == "ss" then
|
||||
local url_parts = parse_url(line)
|
||||
if url_parts and url_parts.host then
|
||||
server = url_parts.host
|
||||
else
|
||||
-- ss://base64
|
||||
original_body = original_body:match("([^#]+)") or original_body
|
||||
local decoded = fs.decode64(original_body)
|
||||
if decoded then
|
||||
server = decoded:match("@([^:/]+)")
|
||||
else
|
||||
server = original_body:match("[^@]+@([^:/]+)")
|
||||
end
|
||||
end
|
||||
|
||||
elseif scheme == "ssr" then
|
||||
original_body = original_body:match("([^#]+)") or original_body
|
||||
local decoded = fs.decode64(original_body)
|
||||
if decoded then
|
||||
-- ssr://host:port:protocol:method:obfs:urlsafebase64pass/?params
|
||||
local before_query = decoded:match("^([^/?]+)")
|
||||
if before_query then
|
||||
local parts = {}
|
||||
for part in before_query:gmatch("([^:]+)") do
|
||||
table.insert(parts, part)
|
||||
end
|
||||
-- host:port:protocol:method:obfs:password
|
||||
if #parts == 6 then
|
||||
server = parts[1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
elseif scheme == "hysteria" or scheme == "hysteria2" or scheme == "hy2" then
|
||||
local url_parts = parse_url(line)
|
||||
if url_parts and url_parts.host then
|
||||
server = url_parts.host
|
||||
end
|
||||
|
||||
elseif scheme == "tuic" then
|
||||
local url_parts = parse_url(line)
|
||||
if url_parts and url_parts.host then
|
||||
server = url_parts.host
|
||||
end
|
||||
|
||||
elseif scheme == "socks" or scheme == "socks5" or scheme == "socks5h" or
|
||||
scheme == "http" or scheme == "https" then
|
||||
local url_parts = parse_url(line)
|
||||
if url_parts and url_parts.host then
|
||||
server = url_parts.host
|
||||
end
|
||||
|
||||
elseif scheme == "anytls" then
|
||||
local url_parts = parse_url(line)
|
||||
if url_parts and url_parts.host then
|
||||
server = url_parts.host
|
||||
end
|
||||
end
|
||||
|
||||
-- fallback
|
||||
if not server then
|
||||
local body_to_parse = fs.decode64(original_body) or original_body
|
||||
server = body_to_parse:match("@([^:/]+)") or -- user@host
|
||||
body_to_parse:match("([^:/]+)") or -- host
|
||||
body_to_parse:match("//([^:/]+)") -- //host
|
||||
end
|
||||
|
||||
return server
|
||||
local decoded = decode64(content:gsub("%s+", ""))
|
||||
if decoded and decoded:find("://", 1, true) then
|
||||
content = decoded
|
||||
end
|
||||
|
||||
local f = io.open(file_path, "r")
|
||||
if not f then os.exit(1) end
|
||||
local content = f:read("*a")
|
||||
f:close()
|
||||
|
||||
local decoded_content = base64_decode_subscription(content:gsub("%s+", ""))
|
||||
if decoded_content then
|
||||
content = decoded_content
|
||||
for line in content:gmatch("[^\r\n]+") do
|
||||
local text = util.trim(line)
|
||||
local ok, server = pcall(server_of, text)
|
||||
if not ok or not server or server == "" then
|
||||
server = server_of_yaml(text)
|
||||
end
|
||||
if server and server ~= "" then
|
||||
print(server)
|
||||
end
|
||||
end
|
||||
|
||||
for line in content:gmatch("([^\r\n]+)") do
|
||||
local server = get_server_from_url(line)
|
||||
if server and util.trim(server) ~= "" then
|
||||
print(util.trim(server))
|
||||
end
|
||||
end
|
||||
@@ -25,6 +25,12 @@ inc_job_counter
|
||||
|
||||
RELEASE_BRANCH=$(uci_get_config "release_branch" || echo "master")
|
||||
|
||||
if [ -x "/usr/bin/apk" ]; then
|
||||
PKG_TYPE="apk"
|
||||
else
|
||||
PKG_TYPE="opkg"
|
||||
fi
|
||||
|
||||
PLUGIN_DIRECT=0
|
||||
if [ -n "$3" ] && echo "$3" | grep -qE '^https?://'; then
|
||||
PLUGIN_DIRECT=1
|
||||
@@ -77,9 +83,9 @@ run_with_timeout() {
|
||||
}
|
||||
|
||||
LAST_VER=$(echo "$PLUGIN_LATEST" |sed "s/^v//g" |tr -d "\n")
|
||||
if [ -x "/bin/opkg" ]; then
|
||||
if [ "$PKG_TYPE" = "opkg" ]; then
|
||||
OP_CV=$(rm -f /var/lock/opkg.lock && opkg status luci-app-openclash 2>/dev/null |grep 'Version' |awk -F 'Version: ' '{print $2}' 2>/dev/null)
|
||||
elif [ -x "/usr/bin/apk" ]; then
|
||||
else
|
||||
OP_CV=$(rm -f /lib/apk/db/lock && apk list luci-app-openclash 2>/dev/null|grep "installed" | grep -oE '[0-9]+(\.[0-9]+)*' | head -1 2>/dev/null)
|
||||
fi
|
||||
OP_LV="$LAST_VER"
|
||||
@@ -125,10 +131,10 @@ fi
|
||||
|
||||
if [ "$PLUGIN_DIRECT" -eq 1 ]; then
|
||||
# Direct download URL (historical version / downgrade) — skip version compare
|
||||
if [ -x "/bin/opkg" ]; then
|
||||
if [ "$PKG_TYPE" = "opkg" ]; then
|
||||
DOWNLOAD_URL="$3"
|
||||
DOWNLOAD_PATH="/tmp/openclash.ipk"
|
||||
elif [ -x "/usr/bin/apk" ]; then
|
||||
else
|
||||
DOWNLOAD_URL="$3"
|
||||
DOWNLOAD_PATH="/tmp/openclash.apk"
|
||||
fi
|
||||
@@ -138,27 +144,27 @@ elif [ -n "$OP_CV" ] && [ -n "$OP_LV" ] && version_compare "$OP_CV" "$OP_LV" &&
|
||||
LOG_TIP "Start downloading【OpenClash - v$LAST_VER】..."
|
||||
if [ "$github_address_mod" != "0" ]; then
|
||||
if [ "$github_address_mod" == "https://cdn.jsdelivr.net/" ] || [ "$github_address_mod" == "https://fastly.jsdelivr.net/" ] || [ "$github_address_mod" == "https://testingcf.jsdelivr.net/" ]; then
|
||||
if [ -x "/bin/opkg" ]; then
|
||||
if [ "$PKG_TYPE" = "opkg" ]; then
|
||||
DOWNLOAD_URL="${github_address_mod}gh/vernesong/OpenClash@package/${RELEASE_BRANCH}/luci-app-openclash_${LAST_VER}_all.ipk"
|
||||
DOWNLOAD_PATH="/tmp/openclash.ipk"
|
||||
elif [ -x "/usr/bin/apk" ]; then
|
||||
else
|
||||
DOWNLOAD_URL="${github_address_mod}gh/vernesong/OpenClash@package/${RELEASE_BRANCH}/luci-app-openclash-${LAST_VER}.apk"
|
||||
DOWNLOAD_PATH="/tmp/openclash.apk"
|
||||
fi
|
||||
else
|
||||
if [ -x "/bin/opkg" ]; then
|
||||
if [ "$PKG_TYPE" = "opkg" ]; then
|
||||
DOWNLOAD_URL="${github_address_mod}https://raw.githubusercontent.com/vernesong/OpenClash/package/${RELEASE_BRANCH}/luci-app-openclash_${LAST_VER}_all.ipk"
|
||||
DOWNLOAD_PATH="/tmp/openclash.ipk"
|
||||
elif [ -x "/usr/bin/apk" ]; then
|
||||
else
|
||||
DOWNLOAD_URL="${github_address_mod}https://raw.githubusercontent.com/vernesong/OpenClash/package/${RELEASE_BRANCH}/luci-app-openclash-${LAST_VER}.apk"
|
||||
DOWNLOAD_PATH="/tmp/openclash.apk"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
if [ -x "/bin/opkg" ]; then
|
||||
if [ "$PKG_TYPE" = "opkg" ]; then
|
||||
DOWNLOAD_URL="https://raw.githubusercontent.com/vernesong/OpenClash/package/${RELEASE_BRANCH}/luci-app-openclash_${LAST_VER}_all.ipk"
|
||||
DOWNLOAD_PATH="/tmp/openclash.ipk"
|
||||
elif [ -x "/usr/bin/apk" ]; then
|
||||
else
|
||||
DOWNLOAD_URL="https://raw.githubusercontent.com/vernesong/OpenClash/package/${RELEASE_BRANCH}/luci-app-openclash-${LAST_VER}.apk"
|
||||
DOWNLOAD_PATH="/tmp/openclash.apk"
|
||||
fi
|
||||
@@ -194,7 +200,7 @@ if [ -n "$DOWNLOAD_URL" ]; then
|
||||
|
||||
pre_test_success=false
|
||||
|
||||
if [ -x "/bin/opkg" ]; then
|
||||
if [ "$PKG_TYPE" = "opkg" ]; then
|
||||
update_retry=0
|
||||
max_update_retry=2
|
||||
while [ $update_retry -lt $max_update_retry ]; do
|
||||
@@ -220,7 +226,7 @@ if [ -n "$DOWNLOAD_URL" ]; then
|
||||
LOG_ERROR "【$retry_count/$max_retries】【OpenClash - v$LAST_VER】Pre update test failed:【$(echo "$opkg_test_err" | tr '\n' ' ' | head -c 500)】..."
|
||||
fi
|
||||
fi
|
||||
elif [ -x "/usr/bin/apk" ]; then
|
||||
else
|
||||
update_retry=0
|
||||
max_update_retry=2
|
||||
while [ $update_retry -lt $max_update_retry ]; do
|
||||
@@ -257,9 +263,9 @@ if [ -n "$DOWNLOAD_URL" ]; then
|
||||
sleep 2
|
||||
continue
|
||||
else
|
||||
if [ -x "/bin/opkg" ]; then
|
||||
if [ "$PKG_TYPE" = "opkg" ]; then
|
||||
LOG_ERROR "【OpenClash - v$LAST_VER】Pre update test failed after 3 attempts, the file is saved in /tmp/openclash.ipk, please try to update manually with【opkg --force-downgrade install /tmp/openclash.ipk】"
|
||||
elif [ -x "/usr/bin/apk" ]; then
|
||||
else
|
||||
LOG_ERROR "【OpenClash - v$LAST_VER】Pre update test failed after 3 attempts, the file is saved in /tmp/openclash.apk, please try to update manually with【apk add -q --force-overwrite --clean-protected --allow-untrusted /tmp/openclash.apk】"
|
||||
fi
|
||||
early_exit
|
||||
@@ -283,6 +289,12 @@ if [ -n "$DOWNLOAD_URL" ]; then
|
||||
. /usr/share/openclash/log.sh
|
||||
. /usr/share/openclash/openclash_ps.sh
|
||||
|
||||
if [ -x "/usr/bin/apk" ]; then
|
||||
PKG_TYPE="apk"
|
||||
else
|
||||
PKG_TYPE="opkg"
|
||||
fi
|
||||
|
||||
UPDATE_LOCK="/tmp/lock/openclash_update_install.lock"
|
||||
mkdir -p /tmp/lock
|
||||
|
||||
@@ -308,9 +320,9 @@ check_install_success()
|
||||
local target_version="$1"
|
||||
local current_version=""
|
||||
|
||||
if [ -x "/bin/opkg" ]; then
|
||||
if [ "$PKG_TYPE" = "opkg" ]; then
|
||||
current_version=$(rm -f /var/lock/opkg.lock && opkg status luci-app-openclash 2>/dev/null |grep 'Version' |awk -F 'Version: ' '{print $2}' 2>/dev/null)
|
||||
elif [ -x "/usr/bin/apk" ]; then
|
||||
else
|
||||
current_version=$(apk list luci-app-openclash 2>/dev/null |grep "installed" | grep -oE '[0-9]+(\.[0-9]+)*' | head -1 2>/dev/null)
|
||||
fi
|
||||
|
||||
@@ -321,6 +333,22 @@ check_install_success()
|
||||
fi
|
||||
}
|
||||
|
||||
openclash_service_running() {
|
||||
ubus call service list '{"name":"openclash"}' 2>/dev/null | jsonfilter -e '@.openclash.instances.*.running' 2>/dev/null | grep -q 'true'
|
||||
}
|
||||
|
||||
stop_openclash_before_install() {
|
||||
if ! openclash_service_running; then
|
||||
return 0
|
||||
fi
|
||||
/etc/init.d/openclash stop >/dev/null 2>&1
|
||||
local i
|
||||
for i in $(seq 1 30); do
|
||||
openclash_service_running || return 0
|
||||
sleep 1
|
||||
done
|
||||
}
|
||||
|
||||
install_missing_packages() {
|
||||
local installed_before="$1"
|
||||
|
||||
@@ -328,11 +356,11 @@ install_missing_packages() {
|
||||
for pkg in $installed_before; do
|
||||
local retry_count=0
|
||||
local max_retries=3
|
||||
if [ -x "/bin/opkg" ]; then
|
||||
if [ "$PKG_TYPE" = "opkg" ]; then
|
||||
if opkg status "$pkg" >/dev/null 2>&1; then
|
||||
continue
|
||||
fi
|
||||
elif [ -x "/usr/bin/apk" ]; then
|
||||
else
|
||||
if apk list "$pkg" |grep "installed" >/dev/null 2>&1; then
|
||||
continue
|
||||
fi
|
||||
@@ -342,9 +370,9 @@ install_missing_packages() {
|
||||
|
||||
while [ $retry_count -lt $max_retries ]; do
|
||||
retry_count=$((retry_count + 1))
|
||||
if [ -x "/bin/opkg" ]; then
|
||||
if [ "$PKG_TYPE" = "opkg" ]; then
|
||||
pkg_err=$(opkg install "$pkg" 2>&1)
|
||||
elif [ -x "/usr/bin/apk" ]; then
|
||||
else
|
||||
pkg_err=$(apk add "$pkg" 2>&1)
|
||||
fi
|
||||
if [ $? -eq 0 ]; then
|
||||
@@ -365,13 +393,15 @@ install_retry_count=0
|
||||
max_install_retries=3
|
||||
install_success=false
|
||||
|
||||
stop_openclash_before_install
|
||||
|
||||
while [ $install_retry_count -lt $max_install_retries ]; do
|
||||
install_retry_count=$((install_retry_count + 1))
|
||||
LOG_TIP "【$install_retry_count/$max_install_retries】Installing the new version, please do not refresh the page or do other operations..."
|
||||
|
||||
packages_to_check="luci-compat kmod-inet-diag kmod-nft-tproxy kmod-ipt-nat iptables-mod-tproxy iptables-mod-extra ipset"
|
||||
installed_before=""
|
||||
if [ -x "/bin/opkg" ]; then
|
||||
if [ "$PKG_TYPE" = "opkg" ]; then
|
||||
for pkg in $packages_to_check; do
|
||||
if opkg status "$pkg" >/dev/null 2>&1; then
|
||||
installed_before="$installed_before $pkg"
|
||||
@@ -379,7 +409,7 @@ while [ $install_retry_count -lt $max_install_retries ]; do
|
||||
done
|
||||
opkg --force-downgrade install /tmp/openclash.ipk >/dev/null 2>&1
|
||||
install_err=$(cat /tmp/openclash_install.log 2>/dev/null)
|
||||
elif [ -x "/usr/bin/apk" ]; then
|
||||
else
|
||||
for pkg in $packages_to_check; do
|
||||
if apk list "$pkg" |grep "installed" >/dev/null 2>&1; then
|
||||
installed_before="$installed_before $pkg"
|
||||
@@ -404,19 +434,26 @@ while [ $install_retry_count -lt $max_install_retries ]; do
|
||||
done
|
||||
|
||||
if [ "$install_success" = true ]; then
|
||||
if [ -x "/bin/opkg" ]; then
|
||||
if [ "$PKG_TYPE" = "opkg" ]; then
|
||||
rm -rf /tmp/openclash.ipk >/dev/null 2>&1
|
||||
elif [ -x "/usr/bin/apk" ]; then
|
||||
else
|
||||
rm -rf /tmp/openclash.apk >/dev/null 2>&1
|
||||
fi
|
||||
else
|
||||
if [ -x "/bin/opkg" ]; then
|
||||
if [ "$PKG_TYPE" = "opkg" ]; then
|
||||
LOG_ERROR "OpenClash update failed after 3 attempts, the file is saved in /tmp/openclash.ipk, please try to update manually with【opkg --force-downgrade install /tmp/openclash.ipk】【$(echo "$install_err" | tr '\n' ' ' | head -c 500)】"
|
||||
elif [ -x "/usr/bin/apk" ]; then
|
||||
else
|
||||
LOG_ERROR "OpenClash update failed after 3 attempts, the file is saved in /tmp/openclash.apk, please try to update manually with【apk add -q --force-overwrite --clean-protected --allow-untrusted /tmp/openclash.apk】【$(echo "$install_err" | tr '\n' ' ' | head -c 500)】"
|
||||
fi
|
||||
fi
|
||||
dec_job_counter_and_restart "0"
|
||||
|
||||
restart_flag=0
|
||||
if [ "$(uci -q get openclash.config.enable)" = "1" ]; then
|
||||
if ! openclash_service_running; then
|
||||
restart_flag=1
|
||||
fi
|
||||
fi
|
||||
dec_job_counter_and_restart "$restart_flag"
|
||||
del_update_lock
|
||||
EOF
|
||||
chmod 4755 /tmp/openclash_update.sh
|
||||
|
||||
@@ -7,9 +7,6 @@ local json = require "luci.jsonc"
|
||||
|
||||
local M = {}
|
||||
|
||||
local VERSION_CACHE_FILE = "/tmp/openclash_version_history.json"
|
||||
local CDN_CACHE_FILE = "/tmp/openclash_cdn_info.json"
|
||||
|
||||
local function trim(s)
|
||||
if not s then return "" end
|
||||
return (s:gsub("^%s+", ""):gsub("%s+$", ""))
|
||||
@@ -19,7 +16,7 @@ function M.is_valid_version(s)
|
||||
if not s or s == "" then return false end
|
||||
s = trim(s)
|
||||
if s == "" then return false end
|
||||
if s:match("^<") then return false end
|
||||
if s:match("^<") or s:match("%s") or s:match(":") then return false end
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -42,44 +39,38 @@ function try_read(fd, maxlen)
|
||||
return nil
|
||||
end
|
||||
|
||||
local function read_json(path)
|
||||
if not fs.access(path) then return nil end
|
||||
local raw = fs.readfile(path)
|
||||
if not raw or raw == "" then return nil end
|
||||
local ok, parsed = pcall(json.parse, raw)
|
||||
if ok and parsed and type(parsed) == "table" then
|
||||
return parsed
|
||||
end
|
||||
return nil
|
||||
end
|
||||
local read_version_cache = fs.read_version_cache
|
||||
local update_version_cache = fs.update_version_cache
|
||||
|
||||
local function write_json(path, data)
|
||||
local tmp = path .. ".tmp"
|
||||
fs.writefile(tmp, json.stringify(data))
|
||||
os.rename(tmp, path)
|
||||
end
|
||||
M.read_version_cache = read_version_cache
|
||||
M.update_version_cache = update_version_cache
|
||||
|
||||
local function read_version_cache()
|
||||
return read_json(VERSION_CACHE_FILE)
|
||||
end
|
||||
|
||||
local function update_version_cache(updater)
|
||||
local parsed = read_version_cache() or {}
|
||||
updater(parsed)
|
||||
write_json(VERSION_CACHE_FILE, parsed)
|
||||
end
|
||||
|
||||
local DEFAULT_CDN_LIST = {
|
||||
"https://ghfast.top/",
|
||||
"https://github.dpik.top/",
|
||||
"https://gh-proxy.com/",
|
||||
"https://git.yylx.win/"
|
||||
}
|
||||
local CDN_LIST_FILE = "/usr/share/openclash/res/cdn.list"
|
||||
local CDN_LIST_REMOTE_URL = "https://raw.githubusercontent.com/vernesong/OpenClash/dev/luci-app-openclash/root/usr/share/openclash/res/cdn.list"
|
||||
local CDN_LIST_MAX_AGE = 7 * 24 * 3600
|
||||
|
||||
local function cdn_list()
|
||||
local list = fs.cdn_list()
|
||||
if #list > 0 then return list end
|
||||
return DEFAULT_CDN_LIST
|
||||
return fs.cdn_list()
|
||||
end
|
||||
|
||||
function M.update_cdn_list(force)
|
||||
local mt = fs.mtime(CDN_LIST_FILE)
|
||||
if not force and mt and (os.time() - mt) < CDN_LIST_MAX_AGE then
|
||||
return false, "fresh"
|
||||
end
|
||||
local child = nixio.fork()
|
||||
if child > 0 then
|
||||
return true, "started"
|
||||
elseif child == 0 then
|
||||
local tmp = CDN_LIST_FILE .. ".tmp"
|
||||
local cmd = string.format(
|
||||
'curl -sL -m 10 --connect-timeout 5 "%s" -o "%s" && grep -q "https://" "%s" && mv "%s" "%s"',
|
||||
CDN_LIST_REMOTE_URL, tmp, tmp, tmp, CDN_LIST_FILE)
|
||||
nixio.exec("/bin/sh", "-c", cmd)
|
||||
return nil, "exec_failed"
|
||||
else
|
||||
return false, "fork_error"
|
||||
end
|
||||
end
|
||||
|
||||
local function raw_url(path)
|
||||
@@ -312,6 +303,11 @@ local function launch_file_batch(job)
|
||||
end
|
||||
end
|
||||
|
||||
local function is_version_response(buf)
|
||||
if not buf or buf == "" then return false end
|
||||
return M.is_valid_version(trim(buf:match("^[^\n\r]*") or ""))
|
||||
end
|
||||
|
||||
local function collect_fork_results(jobs, max_jobs)
|
||||
local results = {}
|
||||
local active = 0
|
||||
@@ -355,7 +351,7 @@ local function collect_fork_results(jobs, max_jobs)
|
||||
end
|
||||
pcall(child.fdi.close, child.fdi)
|
||||
child.done = true
|
||||
if winner == "" and child.buf ~= "" then
|
||||
if winner == "" and child.buf ~= "" and is_version_response(child.buf) then
|
||||
winner = child.buf
|
||||
end
|
||||
end
|
||||
@@ -462,6 +458,7 @@ end
|
||||
|
||||
function M.fetch_version_history(branch, force, cdn, latest_only)
|
||||
local result = { plugin = {}, core_meta = {}, core_smart = {}, latest = nil, error = nil, oix_ver = "" }
|
||||
M.update_cdn_list()
|
||||
local cur_oix = M.is_oix_mode()
|
||||
local skip_core = cur_oix
|
||||
local github_address_mod = fs.uci_get_config("config", "github_address_mod") or "0"
|
||||
|
||||
@@ -15,11 +15,39 @@ STREAM_AUTO_SELECT=0
|
||||
FIREWALL_RELOAD=0
|
||||
MAX_FIREWALL_RELOAD=3
|
||||
FW4=$(command -v fw4)
|
||||
PROXY_FWMARK="0x162"
|
||||
|
||||
## Skip Proxies Address
|
||||
skip_proxies_address()
|
||||
{
|
||||
ruby -ryaml -rYAML -I "/usr/share/openclash" -E UTF-8 -e "
|
||||
REG4 = /^((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.){3}(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])$/
|
||||
REG6 = /^(?:(?:(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))|\[(?:(?:(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))\]$/i
|
||||
REG_DOMAIN = /([0-9a-zA-Z-]{1,}\.)+([a-zA-Z]{2,})/
|
||||
|
||||
def write_ips_set(ips)
|
||||
return if ips.nil? or ips.empty?
|
||||
firewall_v = '$FW4'.empty? ? 'ipt' : 'nft'
|
||||
set_commands = []
|
||||
ips.each do |ip|
|
||||
next if ip.nil? or ip.empty?
|
||||
if ip.match(REG4) then
|
||||
if firewall_v == 'nft' then
|
||||
set_commands << 'nft add element inet fw4 localnetwork { \"' + ip + '\" } 2>/dev/null'
|
||||
else
|
||||
set_commands << 'ipset add localnetwork \"' + ip + '\" 2>/dev/null'
|
||||
end
|
||||
elsif ip.match(REG6) then
|
||||
if firewall_v == 'nft' then
|
||||
set_commands << 'nft add element inet fw4 localnetwork6 { \"' + ip + '\" } 2>/dev/null'
|
||||
else
|
||||
set_commands << 'ipset add localnetwork6 \"' + ip + '\" 2>/dev/null'
|
||||
end
|
||||
end
|
||||
end
|
||||
system(set_commands.join('; ')) if not set_commands.empty?
|
||||
end
|
||||
|
||||
begin
|
||||
Value = YAML.load_file('$CONFIG_FILE');
|
||||
rescue Exception => e
|
||||
@@ -103,14 +131,11 @@ begin
|
||||
|
||||
domains_to_resolve = Array.new
|
||||
ips = Array.new
|
||||
reg_domain = /([0-9a-zA-Z-]{1,}\.)+([a-zA-Z]{2,})/
|
||||
reg4 = /^((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.){3}(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])$/
|
||||
reg6 = /^(?:(?:(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))|\[(?:(?:(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))\]$/i
|
||||
|
||||
servers_to_process.each do |server|
|
||||
if server.to_s.match(reg4) or server.to_s.match(reg6)
|
||||
if server.to_s.match(REG4) or server.to_s.match(REG6)
|
||||
ips.push(server)
|
||||
elsif server.to_s.match(reg_domain)
|
||||
elsif server.to_s.match(REG_DOMAIN)
|
||||
domains_to_resolve.push(server)
|
||||
end
|
||||
end
|
||||
@@ -119,18 +144,33 @@ begin
|
||||
domains_to_resolve.uniq!
|
||||
|
||||
if not domains_to_resolve.empty?
|
||||
host = '127.0.0.1'
|
||||
port = IO.popen('uci -q get openclash.config.cn_port').read.strip
|
||||
pw = IO.popen('uci -q get openclash.config.dashboard_password').read.strip
|
||||
|
||||
ips_mutex = Mutex.new
|
||||
queue = Queue.new
|
||||
domains_to_resolve.each{|d| queue << d}
|
||||
domains_to_resolve.each do |d|
|
||||
queue << [d, 'A']
|
||||
queue << [d, 'AAAA']
|
||||
end
|
||||
|
||||
threads = (1..[10, queue.size].min).map do
|
||||
Thread.new do
|
||||
while domain = queue.pop(true) rescue nil
|
||||
syscall = '/usr/share/openclash/openclash_debug_dns.lua 2>/dev/null \"' + domain + '\" \"true\"'
|
||||
while task = (queue.pop(true) rescue nil)
|
||||
domain, qtype = task
|
||||
syscall = 'curl -s -m 3 -H \"Authorization: Bearer ' + pw + '\" \"http://' + host + ':' + port + '/dns/query?name=' + domain + '&type=' + qtype + '\" | jsonfilter -e \"@.Answer[*].data\" 2>/dev/null'
|
||||
result = IO.popen(syscall).read.split(/\n+/)
|
||||
if result
|
||||
ips_mutex.synchronize do
|
||||
result.each{|ip| ips.push(ip)}
|
||||
result.each do |ip|
|
||||
next if ip.nil? or ip.empty?
|
||||
if qtype == 'AAAA'
|
||||
ips.push(ip) if ip.match(REG6)
|
||||
else
|
||||
ips.push(ip) if ip.match(REG4)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -142,28 +182,7 @@ begin
|
||||
ips.compact!
|
||||
ips.uniq!
|
||||
|
||||
# Add IPs to ipset/nft
|
||||
if not ips.empty? then
|
||||
firewall_v = '$FW4'.empty? ? 'ipt' : 'nft'
|
||||
set_commands = []
|
||||
ips.each do |ip|
|
||||
next if ip.nil? or ip.empty?
|
||||
if ip.match(reg4) then
|
||||
if firewall_v == 'nft' then
|
||||
set_commands << 'nft add element inet fw4 localnetwork { \"' + ip + '\" } 2>/dev/null'
|
||||
else
|
||||
set_commands << 'ipset add localnetwork \"' + ip + '\" 2>/dev/null'
|
||||
end
|
||||
elsif ip.match(reg6) then
|
||||
if firewall_v == 'nft' then
|
||||
set_commands << 'nft add element inet fw4 localnetwork6 { \"' + ip + '\" } 2>/dev/null'
|
||||
else
|
||||
set_commands << 'ipset add localnetwork6 \"' + ip + '\" 2>/dev/null'
|
||||
end
|
||||
end
|
||||
end
|
||||
system(set_commands.join('; ')) if not set_commands.empty?
|
||||
end
|
||||
write_ips_set(ips)
|
||||
rescue Exception => e
|
||||
YAML.LOG_ERROR('Set Proxies Address Skip: Failed【' + e.message + '】');
|
||||
end" 2>/dev/null >> $LOG_FILE
|
||||
@@ -199,6 +218,9 @@ do
|
||||
stream_auto_select_openai=$(uci_get_config "stream_auto_select_openai" || echo 0)
|
||||
stream_auto_select_claude=$(uci_get_config "stream_auto_select_claude" || echo 0)
|
||||
stream_auto_select_gemini=$(uci_get_config "stream_auto_select_gemini" || echo 0)
|
||||
stream_auto_select_bahamut=$(uci_get_config "stream_auto_select_bahamut" || echo 0)
|
||||
stream_auto_select_spotify=$(uci_get_config "stream_auto_select_spotify" || echo 0)
|
||||
stream_auto_select_steam=$(uci_get_config "stream_auto_select_steam" || echo 0)
|
||||
upnp_lease_file=$(uci -q get upnpd.config.upnp_lease_file)
|
||||
|
||||
#wait for core start complete
|
||||
@@ -208,11 +230,21 @@ do
|
||||
done >/dev/null 2>&1
|
||||
|
||||
#check the clash service status
|
||||
if ! ubus call service list '{"name":"openclash"}' 2>/dev/null | jsonfilter -e '@.openclash.instances.*.running' | grep -q 'true'; then
|
||||
uci -q set openclash.config.enable=0
|
||||
uci -q commit openclash
|
||||
/etc/init.d/openclash stop >/dev/null 2>&1
|
||||
exit 0
|
||||
CORE_JSON=$(ubus call service list '{"name":"openclash"}' 2>/dev/null)
|
||||
if [ -n "$CORE_JSON" ]; then
|
||||
# stop signals the watchdog itself, this exits it when the core vanished otherwise
|
||||
if [ -z "$(echo "$CORE_JSON" | jsonfilter -e '@.openclash.instances.openclash')" ]; then
|
||||
LOG_WATCHDOG "OpenClash Service Not Running, Exit..."
|
||||
exit 0
|
||||
fi
|
||||
# procd drops the respawn table once it gave up on the core (crash loop)
|
||||
if [ "$(echo "$CORE_JSON" | jsonfilter -e '@.openclash.instances.openclash.running')" != "true" ] \
|
||||
&& [ -z "$(echo "$CORE_JSON" | jsonfilter -e '@.openclash.instances.openclash.respawn.retry')" ]; then
|
||||
uci -q set openclash.config.enable=0
|
||||
uci -q commit openclash
|
||||
/etc/init.d/openclash stop >/dev/null 2>&1
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
## Porxy history
|
||||
@@ -244,7 +276,7 @@ fi
|
||||
LOG_WATCHDOG "Setting Firewall For Rules Order..."
|
||||
/etc/init.d/openclash reload "firewall"
|
||||
let FIREWALL_RELOAD++
|
||||
elif [ -n "$(ip link show utun 2>/dev/null)" ] && [ -z "$(ip route list table 354)" ]; then
|
||||
elif [ -n "$(ip link show utun 2>/dev/null)" ] && [ -n "$(ip rule show 2>/dev/null | grep "fwmark $PROXY_FWMARK")" ] && [ -z "$(ip route list table 354)" ]; then
|
||||
## 路由表检查
|
||||
LOG_WATCHDOG "Setting Firewall For IP Rules Table Recreate..."
|
||||
/etc/init.d/openclash reload "firewall"
|
||||
@@ -385,7 +417,7 @@ fi
|
||||
uci -q delete dhcp.@dnsmasq[0].resolvfile
|
||||
uci -q set dhcp.@dnsmasq[0].noresolv=1
|
||||
[ "$disable_masq_cache" -eq 1 ] && {
|
||||
uci -q set dhcp.@dnsmasq[0].cachesize=0
|
||||
uci -q set dhcp.@dnsmasq[0].cachesize=0
|
||||
}
|
||||
uci -q commit dhcp
|
||||
/etc/init.d/dnsmasq restart >/dev/null 2>&1
|
||||
@@ -463,6 +495,18 @@ fi
|
||||
LOG_INFO "【Gemini】Start Auto Select Unlock Proxy..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "Gemini" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_bahamut" -eq 1 ]; then
|
||||
LOG_INFO "【Bahamut Anime】Start Auto Select Unlock Proxy..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "Bahamut Anime" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_spotify" -eq 1 ]; then
|
||||
LOG_INFO "【Spotify】Start Auto Select Unlock Proxy..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "Spotify" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_steam" -eq 1 ]; then
|
||||
LOG_INFO "【Steam】Start Auto Select Unlock Proxy..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "Steam" >> $LOG_FILE
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
STREAM_AUTO_SELECT=$(expr "$STREAM_AUTO_SELECT" + 1)
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
# CDN proxy address list (one URL per line, "#" starts a comment)
|
||||
# Comment out a line with "#" to temporarily disable it
|
||||
https://ghfast.top/
|
||||
https://git.yylx.win/
|
||||
https://cdn.akaere.online/
|
||||
# Atom-feed-capable proxies
|
||||
https://github.chenc.dev/
|
||||
https://g.z321.cc.cd/
|
||||
https://ghp.keleyaa.com/
|
||||
https://xsadwsd.kdns.fr/
|
||||
https://777.z321.cc.cd/
|
||||
# Raw/Release download proxies
|
||||
https://gh-proxy.com/
|
||||
https://gh.jasonzeng.dev/
|
||||
https://ghproxy.monkeyray.net/
|
||||
https://down.mxw.xx.kg/
|
||||
https://github.tbap.top/
|
||||
https://ghm.078465.xyz/
|
||||
https://gh.monlor.com/
|
||||
https://gh.idayer.com/
|
||||
https://git.yylx.win/
|
||||
https://gh.acmsz.top/
|
||||
https://gh.jjj.gv.uy/
|
||||
https://g.blfrp.cn/
|
||||
https://ghfast.top/
|
||||
@@ -136,7 +136,7 @@ profile: # 存储 select 选择记录
|
||||
# Tun 配置
|
||||
tun:
|
||||
enable: false
|
||||
stack: system # gvisor/mixed
|
||||
stack: system # gvisor/mixed/mips
|
||||
dns-hijack:
|
||||
- 0.0.0.0:53 # 需要劫持的 DNS
|
||||
# auto-detect-interface: true # 自动识别出口网卡
|
||||
@@ -214,7 +214,7 @@ sniffer:
|
||||
HTTP: # 需要嗅探的端口
|
||||
ports: [80, 8080-8880]
|
||||
# 可覆盖 sniffer.override-destination
|
||||
override-destination: true
|
||||
override-destination: false
|
||||
force-domain:
|
||||
- +.v2ex.com
|
||||
# skip-src-address: # 对于来源ip跳过嗅探
|
||||
@@ -850,7 +850,7 @@ proxies: # socks5
|
||||
# max-connections: 1 # Maximum connections. Conflict with max-streams.
|
||||
# min-streams: 0 # Minimum multiplexed streams in a connection before opening a new connection. Conflict with max-streams.
|
||||
# max-streams: 0 # Maximum multiplexed streams in a connection before opening a new connection. Conflict with max-connections and min-streams.
|
||||
|
||||
|
||||
reality-opts:
|
||||
public-key: CrrQSjAG_YkHLwvM2M-7XkKJilgL5upBKCp0od0tLhE
|
||||
short-id: 10f897e26c4b9478
|
||||
@@ -1373,7 +1373,7 @@ proxies: # socks5
|
||||
- name: sudoku
|
||||
type: sudoku
|
||||
server: server_ip/domain # 1.2.3.4 or domain
|
||||
port: 443
|
||||
port: 443
|
||||
key: "<client_key>" # 如果你使用sudoku生成的ED25519密钥对,请填写密钥对中的私钥,否则填入和服务端相同的uuid
|
||||
aead-method: chacha20-poly1305 # 可选:chacha20-poly1305、aes-128-gcm、none(不建议;none 不提供 AEAD 保护)
|
||||
padding-min: 2 # 最小填充率(0-100)
|
||||
@@ -2211,7 +2211,7 @@ listeners:
|
||||
type: tun
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||
stack: system # gvisor / mixed
|
||||
stack: system # gvisor / mixed / mips
|
||||
dns-hijack:
|
||||
- 0.0.0.0:53 # 需要劫持的 DNS
|
||||
# auto-detect-interface: false # 自动识别出口网卡
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
默认(附带用于Clash的AdGuard DNS),default_with_clash_adg.yml,https://gist.githubusercontent.com/tindy2013/1fa08640a9088ac8652dbd40c5d2715b/raw/default_with_clash_adg.yml
|
||||
无Urltest,no_urltest.ini,https://gist.githubusercontent.com/tindy2013/1fa08640a9088ac8652dbd40c5d2715b/raw/no_urltest.ini
|
||||
带Urltest,urltest.ini,https://gist.githubusercontent.com/tindy2013/1fa08640a9088ac8652dbd40c5d2715b/raw/urltest.ini
|
||||
无Urltest,no_urltest.ini,https://gist.githubusercontent.com/tindy2013/1fa08640a9088ac8652dbd40c5d2715b/raw/no_urltest.ini
|
||||
ACL4SSR 规则标准版,ACL4SSR.ini,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR.ini
|
||||
ACL4SSR 规则 AdblockPlus,ACL4SSR_AdblockPlus.ini,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_AdblockPlus.ini
|
||||
ACL4SSR 规则 BackCN,ACL4SSR_BackCN.ini,https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_BackCN.ini
|
||||
|
||||
@@ -1,298 +1,117 @@
|
||||
#!/bin/sh
|
||||
|
||||
write_ruby_part()
|
||||
#The custom overwrite script never edits the config itself, its helper calls are recorded here
|
||||
#and executed by YAML.overwrite_run_custom() once it finished; the argument counts must stay in
|
||||
#sync with OVERWRITE_HELPERS in YAML.rb
|
||||
ruby_record()
|
||||
{
|
||||
local part="$1" sid="${OPENCLASH_OVERWRITE_SID:-unknown}"
|
||||
if [ -z "$part" ]; then
|
||||
return
|
||||
fi
|
||||
if [ "$OVERWRITE_PARENT" = "yaml_overwrite" ]; then
|
||||
mkdir -p /tmp/yaml_openclash_ruby_parts 2>/dev/null
|
||||
echo "threads << Thread.new do begin $part; rescue Exception => e; YAML.LOG_ERROR('Set Custom Overwrite Script Failed,【%s】' % [e.message]); end; end" >> "/tmp/yaml_openclash_ruby_parts/$sid"
|
||||
else
|
||||
echo "threads << Thread.new do begin $part; rescue Exception => e; YAML.LOG_ERROR('Set Custom Overwrite Script Failed,【%s】' % [e.message]); end; end" >> /tmp/yaml_openclash_ruby_parse
|
||||
fi
|
||||
}
|
||||
local op="$1" arity=0 count=0 arg
|
||||
shift
|
||||
|
||||
run_ruby_part()
|
||||
{
|
||||
local part="$1" show_error="${2:-true}"
|
||||
if [ -z "$part" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if [ "$show_error" = "false" ] || [ "$show_error" = "0" ]; then
|
||||
ruby -ryaml -rYAML -I "/usr/share/openclash" -E UTF-8 -e "begin $part; end" 2>/dev/null
|
||||
else
|
||||
ruby -ryaml -rYAML -I "/usr/share/openclash" -E UTF-8 -e "begin $part; rescue Exception => e; YAML.LOG_ERROR('Set Custom Overwrite Script Failed,【%s】' % [e.message]); end" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
case "$op" in
|
||||
ruby_arr_add_file|ruby_map_edit) arity=5;;
|
||||
ruby_arr_edit) arity=6;;
|
||||
ruby_arr_head_add_file|ruby_arr_insert|ruby_arr_insert_arr|ruby_arr_insert_hash|ruby_cover|ruby_merge) arity=4;;
|
||||
ruby_delete|ruby_edit|ruby_merge_hash) arity=3;;
|
||||
ruby_uniq) arity=2;;
|
||||
*) return;;
|
||||
esac
|
||||
|
||||
openclash_custom_overwrite() {
|
||||
local pid=$$
|
||||
while [ "$pid" != "1" ]; do
|
||||
pname=$(tr -d '\0' < /proc/"$pid"/cmdline 2>/dev/null)
|
||||
case "$pname" in
|
||||
*openclash_custom_overwrite.sh*)
|
||||
OVERWRITE_PARENT="custom"
|
||||
return 0
|
||||
;;
|
||||
*yaml_overwrite.sh*|*/tmp/yaml_overwrite.sh*)
|
||||
OVERWRITE_PARENT="yaml_overwrite"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
pid=$(awk '/^PPid:/ {print $2}' /proc/"$pid"/status 2>/dev/null)
|
||||
printf '%s' "$op" >> /tmp/yaml_openclash_custom_calls
|
||||
for arg in "$@"; do
|
||||
printf '\000%s' "$arg" >> /tmp/yaml_openclash_custom_calls
|
||||
count=$((count + 1))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
ruby_read()
|
||||
{
|
||||
local RUBY_YAML_PARSE
|
||||
if [ -z "$1" ] || [ -z "$2" ]; then
|
||||
return
|
||||
fi
|
||||
RUBY_YAML_PARSE="Value = YAML.load_file('$1'); if Value$2 then puts Value$2 end"
|
||||
if [ -n "$(echo "$2" |grep '.to_yaml' 2>/dev/null)" ]; then
|
||||
run_ruby_part "$RUBY_YAML_PARSE" "false" |sed '1d' 2>/dev/null
|
||||
else
|
||||
run_ruby_part "$RUBY_YAML_PARSE" "false"
|
||||
fi
|
||||
}
|
||||
|
||||
ruby_read_hash()
|
||||
{
|
||||
local RUBY_YAML_PARSE
|
||||
if [ -z "$1" ] || [ -z "$2" ]; then
|
||||
return
|
||||
fi
|
||||
RUBY_YAML_PARSE="Value = $1; if Value$2 then puts Value$2 end"
|
||||
run_ruby_part "$RUBY_YAML_PARSE" "false"
|
||||
}
|
||||
|
||||
ruby_read_hash_arr()
|
||||
{
|
||||
local RUBY_YAML_PARSE
|
||||
if [ -z "$1" ] || [ -z "$2" ]; then
|
||||
return
|
||||
fi
|
||||
RUBY_YAML_PARSE="Value = YAML.load_file('$1'); if Value$2 then Value$2.each do |i| if i$3 then puts i$3 end; end; end"
|
||||
run_ruby_part "$RUBY_YAML_PARSE" "false"
|
||||
while [ "$count" -lt "$arity" ]; do
|
||||
printf '\000' >> /tmp/yaml_openclash_custom_calls
|
||||
count=$((count + 1))
|
||||
done
|
||||
printf '\000' >> /tmp/yaml_openclash_custom_calls
|
||||
}
|
||||
|
||||
ruby_edit()
|
||||
{
|
||||
local RUBY_YAML_PARSE
|
||||
if [ -z "$1" ] || [ -z "$2" ]; then
|
||||
return
|
||||
fi
|
||||
if openclash_custom_overwrite; then
|
||||
RUBY_YAML_PARSE="yaml_file_path='$1'; Value$2=$3"
|
||||
write_ruby_part "$RUBY_YAML_PARSE"
|
||||
return
|
||||
fi
|
||||
RUBY_YAML_PARSE="Value = YAML.load_file('$1'); Value$2=$3; YAML.dump(Value, '$1')"
|
||||
run_ruby_part "$RUBY_YAML_PARSE"
|
||||
ruby_record ruby_edit "$1" "$2" "$3"
|
||||
}
|
||||
|
||||
ruby_cover()
|
||||
{
|
||||
local RUBY_YAML_PARSE
|
||||
if [ -z "$1" ] || [ -z "$2" ]; then
|
||||
return
|
||||
fi
|
||||
if openclash_custom_overwrite; then
|
||||
RUBY_YAML_PARSE="yaml_file_path='$1'; if File::exist?('$3') then Value_1 = YAML.load_file('$3'); if not '$4'.empty? then Value$2=Value_1['$4']; else Value$2=Value_1 end else if not '$4'.empty? then Value.delete('$4'); end; end"
|
||||
write_ruby_part "$RUBY_YAML_PARSE"
|
||||
return
|
||||
fi
|
||||
RUBY_YAML_PARSE="Value = YAML.load_file('$1'); if File::exist?('$3') then Value_1 = YAML.load_file('$3'); if not '$4'.empty? then Value$2=Value_1['$4']; else Value$2=Value_1 end else if not '$4'.empty? then Value.delete('$4'); end; end; YAML.dump(Value, '$1')"
|
||||
run_ruby_part "$RUBY_YAML_PARSE"
|
||||
ruby_record ruby_cover "$1" "$2" "$3" "$4"
|
||||
}
|
||||
|
||||
ruby_merge()
|
||||
{
|
||||
local RUBY_YAML_PARSE
|
||||
if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then
|
||||
return
|
||||
fi
|
||||
if openclash_custom_overwrite; then
|
||||
RUBY_YAML_PARSE="yaml_file_path='$1'; Value_1 = YAML.load_file('$3'); if not Value$2 then Value$2 = {}; end; if Value_1$4 && Value_1$4.is_a?(Hash) then Value$2.merge!(Value_1$4) end"
|
||||
write_ruby_part "$RUBY_YAML_PARSE"
|
||||
return
|
||||
fi
|
||||
RUBY_YAML_PARSE="Value = YAML.load_file('$1'); if File::exist?('$3') then Value_1 = YAML.load_file('$3'); if not Value$2 then Value$2 = {}; end; if Value_1$4 && Value_1$4.is_a?(Hash) then Value$2.merge!(Value_1$4) end end; YAML.dump(Value, '$1')"
|
||||
run_ruby_part "$RUBY_YAML_PARSE"
|
||||
ruby_record ruby_merge "$1" "$2" "$3" "$4"
|
||||
}
|
||||
|
||||
ruby_uniq()
|
||||
{
|
||||
local RUBY_YAML_PARSE
|
||||
if [ -z "$1" ] || [ -z "$2" ]; then
|
||||
return
|
||||
fi
|
||||
if openclash_custom_overwrite; then
|
||||
RUBY_YAML_PARSE="yaml_file_path='$1'; if Value$2 then Value$2=Value$2.uniq; end"
|
||||
write_ruby_part "$RUBY_YAML_PARSE"
|
||||
return
|
||||
fi
|
||||
RUBY_YAML_PARSE="Value = YAML.load_file('$1'); if Value$2 then Value$2=Value$2.uniq; end; YAML.dump(Value, '$1')"
|
||||
run_ruby_part "$RUBY_YAML_PARSE"
|
||||
ruby_record ruby_uniq "$1" "$2"
|
||||
}
|
||||
|
||||
ruby_merge_hash()
|
||||
{
|
||||
local RUBY_YAML_PARSE
|
||||
if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then
|
||||
return
|
||||
fi
|
||||
if openclash_custom_overwrite; then
|
||||
RUBY_YAML_PARSE="yaml_file_path='$1'; if not Value$2 then Value$2 = {}; end; Value$2.merge!({$3})"
|
||||
write_ruby_part "$RUBY_YAML_PARSE"
|
||||
return
|
||||
fi
|
||||
RUBY_YAML_PARSE="Value = YAML.load_file('$1'); if not Value$2 then Value$2 = {}; end; Value$2.merge!({$3}); YAML.dump(Value, '$1')"
|
||||
run_ruby_part "$RUBY_YAML_PARSE"
|
||||
ruby_record ruby_merge_hash "$1" "$2" "$3"
|
||||
}
|
||||
|
||||
ruby_arr_add_file()
|
||||
{
|
||||
local RUBY_YAML_PARSE
|
||||
if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ] || [ -z "$4" ] || [ -z "$5" ]; then
|
||||
return
|
||||
fi
|
||||
if openclash_custom_overwrite; then
|
||||
RUBY_YAML_PARSE="yaml_file_path='$1'; if File::exist?('$4') then Value_1 = YAML.load_file('$4'); if not Value$2 or Value$2.nil? then Value$2 = []; end; if Value_1$5 && Value_1$5.is_a?(Array) then idx = [$3.to_i, 0].max; idx = [idx, Value$2.length].min; Value_1$5.reverse.each{|x| Value$2.insert(idx,x); idx += 1}; Value$2=Value$2.uniq end end"
|
||||
write_ruby_part "$RUBY_YAML_PARSE"
|
||||
return
|
||||
fi
|
||||
RUBY_YAML_PARSE="Value = YAML.load_file('$1'); if File::exist?('$4') then Value_1 = YAML.load_file('$4'); if not Value$2 or Value$2.nil? then Value$2 = []; end; if Value_1$5 && Value_1$5.is_a?(Array) then idx = [$3.to_i, 0].max; idx = [idx, Value$2.length].min; Value_1$5.reverse.each{|x| Value$2.insert(idx,x); idx += 1}; Value$2=Value$2.uniq end end; YAML.dump(Value, '$1')"
|
||||
run_ruby_part "$RUBY_YAML_PARSE"
|
||||
ruby_record ruby_arr_add_file "$1" "$2" "$3" "$4" "$5"
|
||||
}
|
||||
|
||||
ruby_arr_head_add_file()
|
||||
{
|
||||
local RUBY_YAML_PARSE
|
||||
if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ] || [ -z "$4" ]; then
|
||||
return
|
||||
fi
|
||||
if openclash_custom_overwrite; then
|
||||
RUBY_YAML_PARSE="yaml_file_path='$1'; if File::exist?('$3') then Value_1 = YAML.load_file('$3'); if not Value$2 or Value$2.nil? then Value$2 = []; end; if Value_1$4 && Value_1$4.is_a?(Array) then Value$2=(Value_1$4+Value$2).uniq else Value$2=Value$2.uniq end end"
|
||||
write_ruby_part "$RUBY_YAML_PARSE"
|
||||
return
|
||||
fi
|
||||
RUBY_YAML_PARSE="Value = YAML.load_file('$1'); if File::exist?('$3') then Value_1 = YAML.load_file('$3'); if not Value$2 or Value$2.nil? then Value$2 = []; end; if Value_1$4 && Value_1$4.is_a?(Array) then Value$2=(Value_1$4+Value$2).uniq else Value$2=Value$2.uniq end end; YAML.dump(Value, '$1')"
|
||||
run_ruby_part "$RUBY_YAML_PARSE"
|
||||
ruby_record ruby_arr_head_add_file "$1" "$2" "$3" "$4"
|
||||
}
|
||||
|
||||
ruby_arr_insert()
|
||||
{
|
||||
local RUBY_YAML_PARSE
|
||||
if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ] || [ -z "$4" ]; then
|
||||
return
|
||||
fi
|
||||
if openclash_custom_overwrite; then
|
||||
RUBY_YAML_PARSE="yaml_file_path='$1'; if not Value$2 or Value$2.nil? then Value$2 = []; end; idx = [$3.to_i, 0].max; idx = [idx, Value$2.length].min; Value$2=Value$2.insert(idx,'$4').uniq"
|
||||
write_ruby_part "$RUBY_YAML_PARSE"
|
||||
return
|
||||
fi
|
||||
RUBY_YAML_PARSE="Value = YAML.load_file('$1'); if not Value$2 or Value$2.nil? then Value$2 = []; end; idx = [$3.to_i, 0].max; idx = [idx, Value$2.length].min; Value$2=Value$2.insert(idx,'$4').uniq; YAML.dump(Value, '$1')"
|
||||
run_ruby_part "$RUBY_YAML_PARSE"
|
||||
ruby_record ruby_arr_insert "$1" "$2" "$3" "$4"
|
||||
}
|
||||
|
||||
ruby_arr_insert_hash()
|
||||
{
|
||||
local RUBY_YAML_PARSE
|
||||
if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ] || [ -z "$4" ]; then
|
||||
return
|
||||
fi
|
||||
if openclash_custom_overwrite; then
|
||||
RUBY_YAML_PARSE="yaml_file_path='$1'; if not Value$2 or Value$2.nil? then Value$2 = []; end; idx = [$3.to_i, 0].max; idx = [idx, Value$2.length].min; Value$2=Value$2.insert(idx,$4).uniq"
|
||||
write_ruby_part "$RUBY_YAML_PARSE"
|
||||
return
|
||||
fi
|
||||
RUBY_YAML_PARSE="Value = YAML.load_file('$1'); if not Value$2 or Value$2.nil? then Value$2 = []; end; idx = [$3.to_i, 0].max; idx = [idx, Value$2.length].min; Value$2=Value$2.insert(idx,$4).uniq; YAML.dump(Value, '$1')"
|
||||
run_ruby_part "$RUBY_YAML_PARSE"
|
||||
ruby_record ruby_arr_insert_hash "$1" "$2" "$3" "$4"
|
||||
}
|
||||
|
||||
ruby_arr_insert_arr()
|
||||
{
|
||||
local RUBY_YAML_PARSE
|
||||
if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ] || [ -z "$4" ]; then
|
||||
return
|
||||
fi
|
||||
if openclash_custom_overwrite; then
|
||||
RUBY_YAML_PARSE="yaml_file_path='$1'; if not Value$2 or Value$2.nil? then Value$2 = []; end; if $4.is_a?(Array) then idx = [$3.to_i, 0].max; idx = [idx, Value$2.length].min; $4.reverse.each{|x| Value$2=Value$2.insert(idx,x); idx += 1}; Value$2=Value$2.uniq end"
|
||||
write_ruby_part "$RUBY_YAML_PARSE"
|
||||
return
|
||||
fi
|
||||
RUBY_YAML_PARSE="Value = YAML.load_file('$1'); if not Value$2 or Value$2.nil? then Value$2 = []; end; if $4.is_a?(Array) then idx = [$3.to_i, 0].max; idx = [idx, Value$2.length].min; $4.reverse.each{|x| Value$2=Value$2.insert(idx,x); idx += 1}; Value$2=Value$2.uniq end; YAML.dump(Value, '$1')"
|
||||
run_ruby_part "$RUBY_YAML_PARSE"
|
||||
ruby_record ruby_arr_insert_arr "$1" "$2" "$3" "$4"
|
||||
}
|
||||
|
||||
ruby_delete()
|
||||
{
|
||||
local RUBY_YAML_PARSE
|
||||
if [ -z "$1" ] || [ -z "$3" ]; then
|
||||
return
|
||||
fi
|
||||
if openclash_custom_overwrite; then
|
||||
if [ -z "$2" ]; then
|
||||
RUBY_YAML_PARSE="yaml_file_path='$1'; Value.delete('$3')"
|
||||
else
|
||||
RUBY_YAML_PARSE="yaml_file_path='$1'; if Value$2 then if Value$2.is_a?(Hash) then Value$2.delete('$3') elsif Value$2.is_a?(Array) then Value$2.delete('$3') end end"
|
||||
fi
|
||||
write_ruby_part "$RUBY_YAML_PARSE"
|
||||
return
|
||||
fi
|
||||
if [ -z "$2" ]; then
|
||||
RUBY_YAML_PARSE="Value = YAML.load_file('$1'); Value.delete('$3'); YAML.dump(Value, '$1')"
|
||||
else
|
||||
RUBY_YAML_PARSE="Value = YAML.load_file('$1'); if Value$2 then if Value$2.is_a?(Hash) then Value$2.delete('$3') elsif Value$2.is_a?(Array) then Value$2.delete('$3') end end; YAML.dump(Value, '$1')"
|
||||
fi
|
||||
run_ruby_part "$RUBY_YAML_PARSE"
|
||||
ruby_record ruby_delete "$1" "$2" "$3"
|
||||
}
|
||||
|
||||
ruby_map_edit()
|
||||
{
|
||||
local RUBY_YAML_PARSE
|
||||
if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ] || [ -z "$4" ]; then
|
||||
return
|
||||
fi
|
||||
if openclash_custom_overwrite; then
|
||||
RUBY_YAML_PARSE="yaml_file_path='$1'; if Value$2 && Value$2.is_a?(Hash) then if Value$2['$3'] && Value$2['$3'].is_a?(Hash) then Value$2['$3']$4 = '$5' end end"
|
||||
write_ruby_part "$RUBY_YAML_PARSE"
|
||||
return
|
||||
fi
|
||||
RUBY_YAML_PARSE="Value = YAML.load_file('$1'); if Value$2 && Value$2.is_a?(Hash) then if Value$2['$3'] && Value$2['$3'].is_a?(Hash) then Value$2['$3']$4 = '$5' end end; YAML.dump(Value, '$1')"
|
||||
run_ruby_part "$RUBY_YAML_PARSE"
|
||||
ruby_record ruby_map_edit "$1" "$2" "$3" "$4" "$5"
|
||||
}
|
||||
|
||||
ruby_arr_edit()
|
||||
{
|
||||
local RUBY_YAML_PARSE
|
||||
if [ -z "$1" ] || [ -z "$2" ]; then
|
||||
return
|
||||
fi
|
||||
if openclash_custom_overwrite; then
|
||||
RUBY_YAML_PARSE="yaml_file_path='$1'; if Value$2 && Value$2.is_a?(Array) then"
|
||||
if [ -n "$3" ] && [ -n "$4" ] && [ -n "$5" ] && [ -n "$6" ]; then
|
||||
RUBY_YAML_PARSE="$RUBY_YAML_PARSE Value$2.each{|x| if x.is_a?(Hash) && x$3 == '$4' then x$5='$6' end} end"
|
||||
elif [ -z "$3" ] && [ -n "$4" ] && [ -z "$5" ] && [ -n "$6" ]; then
|
||||
RUBY_YAML_PARSE="$RUBY_YAML_PARSE Value$2.map!{|x| if x == '$4' then '$6' else x end}; Value$2.uniq! end"
|
||||
ruby_record ruby_arr_edit "$1" "$2" "$3" "$4" "$5" "$6"
|
||||
}
|
||||
|
||||
ruby_read()
|
||||
{
|
||||
[ -z "$1" ] || [ -z "$2" ] && return
|
||||
if [ -n "$(echo "$2" |grep '.to_yaml' 2>/dev/null)" ]; then
|
||||
ruby -ryaml -rYAML -I "/usr/share/openclash" -E UTF-8 -e 'YAML.overwrite_read(ARGV[0], ARGV[1])' "$1" "$2" 2>/dev/null |sed '1d'
|
||||
else
|
||||
return
|
||||
ruby -ryaml -rYAML -I "/usr/share/openclash" -E UTF-8 -e 'YAML.overwrite_read(ARGV[0], ARGV[1])' "$1" "$2" 2>/dev/null
|
||||
fi
|
||||
write_ruby_part "$RUBY_YAML_PARSE"
|
||||
return
|
||||
fi
|
||||
if [ -n "$3" ] && [ -n "$4" ] && [ -n "$5" ] && [ -n "$6" ]; then
|
||||
RUBY_YAML_PARSE="Value = YAML.load_file('$1'); if Value$2 && Value$2.is_a?(Array) then Value$2.map!{|x| if x.is_a?(Hash) && x$3 == '$4' then x$5='$6' end; x}; Value$2.uniq! end; YAML.dump(Value, '$1')"
|
||||
elif [ -z "$3" ] && [ -n "$4" ] && [ -z "$5" ] && [ -n "$6" ]; then
|
||||
RUBY_YAML_PARSE="Value = YAML.load_file('$1'); if Value$2 && Value$2.is_a?(Array) then Value$2.map!{|x| if x == '$4' then '$6' else x end}; Value$2.uniq! end; YAML.dump(Value, '$1')"
|
||||
else
|
||||
return
|
||||
fi
|
||||
run_ruby_part "$RUBY_YAML_PARSE"
|
||||
}
|
||||
}
|
||||
|
||||
ruby_read_hash()
|
||||
{
|
||||
[ -z "$1" ] || [ -z "$2" ] && return
|
||||
ruby -ryaml -rYAML -I "/usr/share/openclash" -E UTF-8 -e 'YAML.overwrite_read_hash(ARGV[0], ARGV[1])' "$1" "$2" 2>/dev/null
|
||||
}
|
||||
|
||||
ruby_read_hash_arr()
|
||||
{
|
||||
[ -z "$1" ] || [ -z "$2" ] && return
|
||||
ruby -ryaml -rYAML -I "/usr/share/openclash" -E UTF-8 -e 'YAML.overwrite_read_hash_arr(ARGV[0], ARGV[1], ARGV[2])' "$1" "$2" "$3" 2>/dev/null
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,43 +1,141 @@
|
||||
#!/bin/sh
|
||||
. /lib/functions.sh
|
||||
|
||||
# The stock loader evaluates the whole export in the shell, seconds once the dynamic sections
|
||||
# hold hundreds of nodes, and they are never read through config_get; the stages that need
|
||||
# them call config_load_full.
|
||||
uci_load() {
|
||||
local PACKAGE="$1"
|
||||
local DATA
|
||||
local RET
|
||||
local VAR
|
||||
|
||||
_C=0
|
||||
if [ -z "$CONFIG_APPEND" ]; then
|
||||
for VAR in $CONFIG_LIST_STATE; do
|
||||
export ${NO_EXPORT:+-n} CONFIG_${VAR}=
|
||||
export ${NO_EXPORT:+-n} CONFIG_${VAR}_LENGTH=
|
||||
done
|
||||
export ${NO_EXPORT:+-n} CONFIG_LIST_STATE=
|
||||
export ${NO_EXPORT:+-n} CONFIG_SECTIONS=
|
||||
export ${NO_EXPORT:+-n} CONFIG_NUM_SECTIONS=0
|
||||
export ${NO_EXPORT:+-n} CONFIG_SECTION=
|
||||
fi
|
||||
|
||||
DATA="$(/sbin/uci ${UCI_CONFIG_DIR:+-c $UCI_CONFIG_DIR} ${LOAD_STATE:+-P /var/state} -S -n export "$PACKAGE" 2>/dev/null)"
|
||||
RET="$?"
|
||||
if [ "$RET" = 0 ] && [ -n "$DATA" ]; then
|
||||
DATA="$(printf '%s\n' "$DATA" | awk -v keep=1 '/^config /{ keep = ($2 != "proxies" && $2 != "proxy_groups" && $2 != "proxy_providers") } keep')"
|
||||
fi
|
||||
[ "$RET" != 0 -o -z "$DATA" ] || eval "$DATA"
|
||||
unset DATA
|
||||
|
||||
${CONFIG_SECTION:+config_cb}
|
||||
return "$RET"
|
||||
}
|
||||
|
||||
# Unfiltered loader, for the pipeline stages that build the YAML from the dynamic sections
|
||||
uci_load_full() {
|
||||
local PACKAGE="$1"
|
||||
local DATA
|
||||
local RET
|
||||
local VAR
|
||||
|
||||
_C=0
|
||||
if [ -z "$CONFIG_APPEND" ]; then
|
||||
for VAR in $CONFIG_LIST_STATE; do
|
||||
export ${NO_EXPORT:+-n} CONFIG_${VAR}=
|
||||
export ${NO_EXPORT:+-n} CONFIG_${VAR}_LENGTH=
|
||||
done
|
||||
export ${NO_EXPORT:+-n} CONFIG_LIST_STATE=
|
||||
export ${NO_EXPORT:+-n} CONFIG_SECTIONS=
|
||||
export ${NO_EXPORT:+-n} CONFIG_NUM_SECTIONS=0
|
||||
export ${NO_EXPORT:+-n} CONFIG_SECTION=
|
||||
fi
|
||||
|
||||
DATA="$(/sbin/uci ${UCI_CONFIG_DIR:+-c $UCI_CONFIG_DIR} ${LOAD_STATE:+-P /var/state} -S -n export "$PACKAGE" 2>/dev/null)"
|
||||
RET="$?"
|
||||
[ "$RET" != 0 -o -z "$DATA" ] || eval "$DATA"
|
||||
unset DATA
|
||||
|
||||
${CONFIG_SECTION:+config_cb}
|
||||
return "$RET"
|
||||
}
|
||||
|
||||
config_load_full() {
|
||||
[ -n "$IPKG_INSTROOT" ] && return 0
|
||||
uci_load_full "$@"
|
||||
}
|
||||
|
||||
uci_get_config() {
|
||||
local key="$1"
|
||||
uci -q get openclash.@overwrite[0]."$key" || uci -q get openclash.config."$key"
|
||||
}
|
||||
|
||||
uci_get_age_public_keys() {
|
||||
local name="$1"
|
||||
# Prints one "<tag>\t<name>\t<value>" line per age key, tag P = public / S = secret, from a
|
||||
# single "uci show" dump; a tag or a name restricts the output.
|
||||
uci_age_keys_dump() {
|
||||
[ -f /etc/config/openclash ] || return 0
|
||||
grep -q config_age_secret /etc/config/openclash 2>/dev/null || return 0
|
||||
|
||||
uci -q show openclash 2>/dev/null | awk -v only_tag="$1" -v only_name="$2" '
|
||||
BEGIN {
|
||||
sq = sprintf("%c", 39)
|
||||
escaped = sq sprintf("%c", 92) sq sq
|
||||
tab = sprintf("%c", 9)
|
||||
}
|
||||
{
|
||||
eq = index($0, "=")
|
||||
if (eq == 0) next
|
||||
key = substr($0, 1, eq - 1)
|
||||
val = substr($0, eq + 1)
|
||||
if (length(val) >= 2 && substr(val, 1, 1) == sq && substr(val, length(val), 1) == sq) {
|
||||
val = substr(val, 2, length(val) - 2)
|
||||
while ((p = index(val, escaped)) > 0) val = substr(val, 1, p - 1) sq substr(val, p + 4)
|
||||
}
|
||||
if (index(key, "openclash.") != 1) next
|
||||
rest = substr(key, 11)
|
||||
dot = 0
|
||||
for (i = 1; i <= length(rest); i++) if (substr(rest, i, 1) == ".") dot = i
|
||||
if (dot == 0) { type[rest] = val; next }
|
||||
id = substr(rest, 1, dot - 1)
|
||||
opt = substr(rest, dot + 1)
|
||||
if (opt == "name") names[id] = val
|
||||
else if (opt == "public") pubs[id] = (pubs[id] == "" ? val : pubs[id] "\n" val)
|
||||
else if (opt == "secret") secs[id] = (secs[id] == "" ? val : secs[id] "\n" val)
|
||||
}
|
||||
END {
|
||||
for (id in names) {
|
||||
if (type[id] != "config_age_secret" || names[id] == "") continue
|
||||
if (only_name != "" && names[id] != only_name) continue
|
||||
if (only_tag != "S" && pubs[id] != "") {
|
||||
c = split(pubs[id], v, "\n")
|
||||
for (i = 1; i <= c; i++) print "P" tab names[id] tab v[i]
|
||||
}
|
||||
if (only_tag != "P" && secs[id] != "") {
|
||||
c = split(secs[id], v, "\n")
|
||||
for (i = 1; i <= c; i++) print "S" tab names[id] tab v[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
uci_age_key_lookup() {
|
||||
local field="$1" name="$2" tag
|
||||
[ -n "$name" ] || return 0
|
||||
|
||||
_print_pub() {
|
||||
local section="$1"
|
||||
config_get cfg_name "$section" name
|
||||
if [ "$cfg_name" = "$name" ]; then
|
||||
config_get pub "$section" public
|
||||
[ -n "$pub" ] && printf '%s\n' "$pub"
|
||||
fi
|
||||
}
|
||||
case "$field" in
|
||||
public) tag="P" ;;
|
||||
secret) tag="S" ;;
|
||||
*) return 0 ;;
|
||||
esac
|
||||
|
||||
config_load openclash
|
||||
config_foreach _print_pub config_age_secret
|
||||
uci_age_keys_dump "$tag" "$name" | cut -f 3-
|
||||
}
|
||||
|
||||
uci_get_age_secret_keys() {
|
||||
local name="$1"
|
||||
[ -n "$name" ] || return 0
|
||||
|
||||
_print_sec() {
|
||||
local section="$1"
|
||||
config_get cfg_name "$section" name
|
||||
if [ "$cfg_name" = "$name" ]; then
|
||||
config_get sec "$section" secret
|
||||
[ -n "$sec" ] && printf '%s\n' "$sec"
|
||||
fi
|
||||
}
|
||||
|
||||
config_load openclash
|
||||
config_foreach _print_sec config_age_secret
|
||||
uci_age_key_lookup "secret" "$1"
|
||||
}
|
||||
|
||||
uci_set_age_keys_by_name() {
|
||||
|
||||
@@ -11,23 +11,34 @@ custom_fakeip_filter=$(uci_get_config "custom_fakeip_filter" || echo 0)
|
||||
custom_name_policy=$(uci_get_config "custom_name_policy" || echo 0)
|
||||
custom_proxy_server_policy=$(uci_get_config "custom_proxy_server_policy" || echo 0)
|
||||
custom_host=$(uci_get_config "custom_host" || echo 0)
|
||||
system_hostname=$(uci -q get system.@system[0].hostname 2>/dev/null)
|
||||
dnsmasq_domain=$(uci -q get dhcp.@dnsmasq[0].domain 2>/dev/null)
|
||||
enable_custom_dns=$(uci_get_config "enable_custom_dns" || echo 0)
|
||||
append_wan_dns=$(uci_get_config "append_wan_dns" || echo 0)
|
||||
custom_fallback_filter=$(uci_get_config "custom_fallback_filter" || echo 0)
|
||||
china_ip_route=$(uci_get_config "china_ip_route" || echo 0)
|
||||
china_ip6_route=$(uci_get_config "china_ip6_route" || echo 0)
|
||||
china_ip_route_domain_source=$(uci_get_config "china_ip_route_domain_source" || echo "mrs")
|
||||
enable_redirect_dns=$(uci_get_config "enable_redirect_dns" || echo 1)
|
||||
fake_ip_filter_mode=${34}
|
||||
fake_ip_filter_mode="${33}"
|
||||
[ -z "$fake_ip_filter_mode" ] && fake_ip_filter_mode="blacklist"
|
||||
default_dashboard=$(uci_get_config "default_dashboard" || echo "metacubexd")
|
||||
yacd_type=$(uci_get_config "yacd_type" || echo "Official")
|
||||
dashboard_type=$(uci_get_config "dashboard_type" || echo "Official")
|
||||
dashboard_forward_port=$(uci_get_config "dashboard_forward_port" || echo 0)
|
||||
dashboard_forward_ssl=$(uci_get_config "dashboard_forward_ssl" || echo 0)
|
||||
dashboard_custom_url=$(uci_get_config "dashboard_custom_url" || echo 0)
|
||||
|
||||
[ "$china_ip_route" -ne 0 ] && [ "$china_ip_route" -ne 1 ] && [ "$china_ip_route" -ne 2 ] && china_ip_route=0
|
||||
[ "$china_ip6_route" -ne 0 ] && [ "$china_ip6_route" -ne 1 ] && [ "$china_ip6_route" -ne 2 ] && china_ip6_route=0
|
||||
case "$china_ip_route_domain_source" in
|
||||
mrs|geosite) ;;
|
||||
*) china_ip_route_domain_source="mrs" ;;
|
||||
esac
|
||||
|
||||
en_mode_tun=${11:-0}
|
||||
if [ -z "${12}" ]; then
|
||||
stack_type=${30:-"system"}
|
||||
stack_type=${30:-"mips"}
|
||||
else
|
||||
stack_type=${12}
|
||||
fi
|
||||
@@ -35,25 +46,18 @@ fi
|
||||
if [ "$1" = "fake-ip" ] && [ "$enable_redirect_dns" != "2" ]; then
|
||||
TMP_FILTER_FILE="/tmp/yaml_openclash_fake_filter_include"
|
||||
> "$TMP_FILTER_FILE"
|
||||
|
||||
|
||||
process_pass_list() {
|
||||
[ ! -f "$1" ] && return
|
||||
awk -v mode="$fake_ip_filter_mode" '
|
||||
awk '
|
||||
!/^$/ && !/^#/ {
|
||||
# 跳过IPv4和IPv6地址
|
||||
if ($0 ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(\/[0-9]+)?$/ || $0 ~ /:/) {
|
||||
next
|
||||
}
|
||||
if (mode == "blacklist") {
|
||||
if ($0 ~ /^\+?\./ || $0 ~ /^\*\./) {
|
||||
print $0
|
||||
} else {
|
||||
print "+."$0
|
||||
}
|
||||
} else if (mode == "rule") {
|
||||
domain = $0
|
||||
sub(/^[\+\*\.]+/, "", domain)
|
||||
print "DOMAIN-SUFFIX," domain ",real-ip"
|
||||
sub(/^[\+\*\.]+/, "", $0)
|
||||
if ($0 != "") {
|
||||
print $0
|
||||
}
|
||||
}
|
||||
' "$1" >> "$TMP_FILTER_FILE" 2>/dev/null
|
||||
@@ -165,8 +169,7 @@ sys_dns_append()
|
||||
|
||||
PROXY_GROUPS=$(ruby -ryaml -rYAML -I "/usr/share/openclash" -E UTF-8 -e "
|
||||
begin
|
||||
Value = YAML.load_file('$CONFIG_FILE')
|
||||
File.open('/tmp/yaml_change_marshal', 'wb') { |f| Marshal.dump(Value, f) }
|
||||
Value = YAML.load_file_cached('$CONFIG_FILE', '/tmp/yaml_change_marshal')
|
||||
if Value.key?('proxy-groups') && Value['proxy-groups'].is_a?(Array)
|
||||
Value['proxy-groups'].each { |x| puts x['name'] if x.key?('name') }
|
||||
end
|
||||
@@ -310,6 +313,10 @@ config_load "openclash"
|
||||
config_foreach yml_auth_get "authentication"
|
||||
yml_dns_custom "$enable_custom_dns" "$5" "$append_wan_dns" "${16}"
|
||||
|
||||
OPENCLASH_CORS_DOMAIN="${36}" \
|
||||
OPENCLASH_CORS_PORT="$dashboard_forward_port" \
|
||||
OPENCLASH_CORS_SSL="$dashboard_forward_ssl" \
|
||||
OPENCLASH_CORS_CUSTOM_URL="$dashboard_custom_url" \
|
||||
ruby -ryaml -rYAML -I "/usr/share/openclash" -E UTF-8 -e "
|
||||
|
||||
def safe_load_yaml(file_path)
|
||||
@@ -326,18 +333,66 @@ def merge_list_from_file(dns_hash, key, file_path)
|
||||
(dns_hash[key] ||= []).unshift(*lines).uniq!
|
||||
end
|
||||
|
||||
def http_origin(raw_url)
|
||||
raw = raw_url.to_s.strip
|
||||
return nil if raw.empty?
|
||||
scheme = raw[/\A(https?):\/\//i, 1]
|
||||
return nil unless scheme
|
||||
scheme = scheme.downcase
|
||||
rest = raw.sub(/\Ahttps?:\/\//i, '')
|
||||
return nil if rest.include?('@')
|
||||
authority = rest.split(/[\/?#]/, 2).first.to_s
|
||||
return nil if authority.empty?
|
||||
host, port = if (m = authority.match(/\A\[([^\]]+)\](?::(\d+))?\z/))
|
||||
[m[1], m[2]]
|
||||
elsif (m = authority.match(/\A([^:]+)(?::(\d+))?\z/))
|
||||
[m[1], m[2]]
|
||||
else
|
||||
[nil, nil]
|
||||
end
|
||||
return nil if host.nil? || host.empty?
|
||||
host = '[' + host + ']' if host.include?(':')
|
||||
default_port = scheme == 'https' ? 443 : 80
|
||||
port = port ? port.to_i : default_port
|
||||
return nil unless port.between?(1, 65_535)
|
||||
scheme + '://' + host + (port == default_port ? '' : ':' + port.to_s)
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
|
||||
def controller_origin(domain, configured_port, ssl)
|
||||
domain = domain.to_s.strip
|
||||
return nil if domain.empty? || domain == '0'
|
||||
return nil if domain.include?('@')
|
||||
domain = domain.sub(/\Ahttps?:\/\//i, '')
|
||||
|
||||
port_text = configured_port.to_s.strip
|
||||
authority = domain.split(/[\/?#]/, 2).first.to_s
|
||||
embedded_port = authority[/\A\[[^\]]+\]:(\d+)\z/, 1] || authority[/\A[^:]+:(\d+)\z/, 1]
|
||||
port_text = embedded_port if (port_text.empty? || port_text == '0') && embedded_port
|
||||
|
||||
scheme = ssl.to_s == '1' ? 'https' : 'http'
|
||||
default_port = scheme == 'https' ? 443 : 80
|
||||
port_text = default_port.to_s if port_text.empty? || port_text == '0'
|
||||
return nil unless (port_text =~ /\A\d+\z/) && port_text.to_i.between?(1, 65_535)
|
||||
|
||||
host = if authority =~ /\A\[[^\]]+\]/
|
||||
authority[/\A\[([^\]]+)\]/ , 1]
|
||||
else
|
||||
authority.split(':', 2).first
|
||||
end
|
||||
return nil if host.nil? || host.empty?
|
||||
host = '[' + host + ']' if host.include?(':')
|
||||
port = port_text.to_i
|
||||
scheme + '://' + host + (port == default_port ? '' : ':' + port.to_s)
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
|
||||
|
||||
begin
|
||||
config_file = '$5'
|
||||
if File.exist?('/tmp/yaml_change_marshal')
|
||||
begin
|
||||
Value = Marshal.load(File.open('/tmp/yaml_change_marshal', 'rb') { |f| f.read })
|
||||
rescue
|
||||
Value = YAML.load_file(config_file)
|
||||
end
|
||||
else
|
||||
Value = YAML.load_file(config_file)
|
||||
end
|
||||
Value = YAML.load_file_cached(config_file, '/tmp/yaml_change_marshal')
|
||||
rescue Exception => e
|
||||
YAML.LOG_ERROR('Load File Failed,【%s】' % [e.message])
|
||||
exit
|
||||
@@ -373,10 +428,10 @@ begin
|
||||
ipv6_mode = '${29}'
|
||||
unified_delay = '${31}' == '1'
|
||||
respect_rules = '${32}' == '1'
|
||||
fake_ip_filter_mode = '${33}'
|
||||
fake_ip_filter_mode = '$fake_ip_filter_mode'
|
||||
routing_mark_setting = '${34}'
|
||||
quic_gso = '${35}' == '1'
|
||||
cors_origin = '${36}'
|
||||
cors_origin = http_origin(ENV['OPENCLASH_CORS_CUSTOM_URL']) || controller_origin(ENV['OPENCLASH_CORS_DOMAIN'], ENV['OPENCLASH_CORS_PORT'], ENV['OPENCLASH_CORS_SSL'])
|
||||
geo_custom_url = '${37}'
|
||||
geoip_custom_url = '${38}'
|
||||
geosite_custom_url = '${39}'
|
||||
@@ -398,6 +453,7 @@ begin
|
||||
custom_fakeip_filter = '$custom_fakeip_filter' == '1'
|
||||
china_ip_route = '$china_ip_route' != '0'
|
||||
china_ip6_route = '$china_ip6_route' != '0'
|
||||
china_ip_route_domain_source = '$china_ip_route_domain_source'
|
||||
custom_name_policy = '$custom_name_policy' == '1'
|
||||
custom_proxy_server_policy = '$custom_proxy_server_policy' == '1'
|
||||
custom_host = '$custom_host' == '1'
|
||||
@@ -406,7 +462,7 @@ begin
|
||||
Value['dns'] ||= {}
|
||||
threads = []
|
||||
|
||||
threads << Thread.new do
|
||||
threads << YAML::Inline.new do
|
||||
begin
|
||||
Value['redir-port'] = redir_port.to_i
|
||||
Value['tproxy-port'] = tproxy_port.to_i
|
||||
@@ -453,7 +509,7 @@ begin
|
||||
Value['find-process-mode'] = find_process_mode if find_process_mode != '0'
|
||||
|
||||
(Value['experimental'] ||= {})['quic-go-disable-gso'] = true if quic_gso
|
||||
if cors_origin != '0'
|
||||
if cors_origin
|
||||
(Value['external-controller-cors'] ||= {})['allow-origins'] = [cors_origin]
|
||||
Value['external-controller-cors']['allow-private-network'] = true
|
||||
end
|
||||
@@ -501,8 +557,8 @@ begin
|
||||
|
||||
if enable_sniffer
|
||||
sniffer_config = {
|
||||
'enable' => true, 'override-destination' => true,
|
||||
'sniff' => {'QUIC' => {'ports' => [443]}, 'TLS' => {'ports' => [443, 8443]}, 'HTTP' => {'ports' => [80, '8080-8880'], 'override-destination' => true}},
|
||||
'enable' => true, 'override-destination' => false,
|
||||
'sniff' => {'QUIC' => {'ports' => [443]}, 'TLS' => {'ports' => [443, 8443]}, 'HTTP' => {'ports' => [80, '8080-8880'], 'override-destination' => false}},
|
||||
'force-domain' => ['+.netflix.com', '+.nflxvideo.net', '+.amazonaws.com', '+.media.dssott.com'],
|
||||
'skip-domain' => ['Mijia Cloud', 'dlg.io.mi.com', '+.oray.com', '+.sunlogin.net', '+.push.apple.com']
|
||||
}
|
||||
@@ -521,7 +577,8 @@ begin
|
||||
'enable' => true, 'stack' => stack_type, 'device' => 'utun',
|
||||
'dns-hijack' => ['127.0.0.1:53'], 'endpoint-independent-nat' => true,
|
||||
'auto-route' => false, 'auto-detect-interface' => false,
|
||||
'auto-redirect' => false, 'strict-route' => false, 'disable-icmp-forwarding' => false
|
||||
'auto-redirect' => false, 'strict-route' => false, 'disable-icmp-forwarding' => false,
|
||||
'gso' => true
|
||||
}
|
||||
Value['tun'].delete('iproute2-table-index')
|
||||
else
|
||||
@@ -551,7 +608,7 @@ begin
|
||||
end
|
||||
end
|
||||
|
||||
threads << Thread.new do
|
||||
threads << YAML::Inline.new do
|
||||
begin
|
||||
if enable_custom_dns || append_wan_dns
|
||||
if (namedns_config = safe_load_yaml('/tmp/yaml_config.namedns.yaml')) && namedns_config['nameserver']
|
||||
@@ -606,7 +663,7 @@ begin
|
||||
end
|
||||
|
||||
# proxy-server-nameserver
|
||||
threads << Thread.new do
|
||||
threads << YAML::Inline.new do
|
||||
begin
|
||||
if enable_custom_dns
|
||||
if (proxydns = safe_load_yaml('/tmp/yaml_config.proxynamedns.yaml')) && proxydns['proxy-server-nameserver']
|
||||
@@ -619,7 +676,7 @@ begin
|
||||
end
|
||||
|
||||
# direct-nameserver
|
||||
threads << Thread.new do
|
||||
threads << YAML::Inline.new do
|
||||
begin
|
||||
if enable_custom_dns
|
||||
if (directdns = safe_load_yaml('/tmp/yaml_config.directnamedns.yaml')) && directdns['direct-nameserver']
|
||||
@@ -632,7 +689,7 @@ begin
|
||||
end
|
||||
|
||||
# nameserver-policy
|
||||
threads << Thread.new do
|
||||
threads << YAML::Inline.new do
|
||||
begin
|
||||
if custom_name_policy
|
||||
if (policy = safe_load_yaml('/etc/openclash/custom/openclash_custom_domain_dns_policy.list'))
|
||||
@@ -645,7 +702,7 @@ begin
|
||||
end
|
||||
|
||||
# proxy-server-nameserver-policy
|
||||
threads << Thread.new do
|
||||
threads << YAML::Inline.new do
|
||||
begin
|
||||
if custom_proxy_server_policy
|
||||
if (policy = safe_load_yaml('/etc/openclash/custom/openclash_custom_proxy_server_dns_policy.list'))
|
||||
@@ -658,19 +715,41 @@ begin
|
||||
end
|
||||
|
||||
# Fake-IP Filter
|
||||
threads << Thread.new do
|
||||
threads << YAML::Inline.new do
|
||||
begin
|
||||
if custom_fakeip_filter
|
||||
Value['dns']['fake-ip-filter-mode'] = fake_ip_filter_mode
|
||||
if fake_ip_mode == 'fake-ip'
|
||||
merge_list_from_file(Value['dns'], 'fake-ip-filter', '/etc/openclash/custom/openclash_custom_fake_filter.list')
|
||||
merge_list_from_file(Value['dns'], 'fake-ip-filter', '/tmp/yaml_openclash_fake_filter_include')
|
||||
end
|
||||
end
|
||||
# Set chnroute_pass domains
|
||||
if fake_ip_mode == 'fake-ip' && enable_redirect_dns != '2' && File.exist?('/tmp/yaml_openclash_fake_filter_include')
|
||||
pass_domains = File.readlines('/tmp/yaml_openclash_fake_filter_include').map { |l| l.gsub(/#.*$/, '').strip }.reject(&:empty?).uniq
|
||||
if pass_domains.any?
|
||||
filter_mode = Value.dig('dns', 'fake-ip-filter-mode')
|
||||
if filter_mode == 'whitelist'
|
||||
filters = Value.dig('dns', 'fake-ip-filter')
|
||||
if filters.is_a?(Array)
|
||||
deleted_filters = filters.select { |f| pass_domains.any? { |d| f.to_s.include?(d) } }
|
||||
if deleted_filters.any?
|
||||
Value['dns']['fake-ip-filter'] -= deleted_filters
|
||||
deleted_filters.each do |f|
|
||||
YAML.LOG_TIP('Because Need Ensure Bypassing IP Option Work, Deleted The Fake-IP-Filter Rule【%s】...' % [f])
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
filter_rules = filter_mode == 'rule' ? pass_domains.map { |d| 'DOMAIN-SUFFIX,' + d + ',real-ip' } : pass_domains.map { |d| '+.' + d }
|
||||
(Value['dns']['fake-ip-filter'] ||= []).unshift(*filter_rules).uniq!
|
||||
YAML.LOG_TIP('Because Need Ensure Bypassing IP Option Work, Added The Fake-IP-Filter Rule【%s】...' % [filter_rules.join(', ')])
|
||||
end
|
||||
end
|
||||
end
|
||||
if fake_ip_mode == 'fake-ip' && (china_ip_route || china_ip6_route)
|
||||
filter_mode = Value.dig('dns', 'fake-ip-filter-mode')
|
||||
if filter_mode == 'blacklist' || filter_mode.nil?
|
||||
filter_rule = 'rule-set:oc-cn-domain'
|
||||
filter_rule = china_ip_route_domain_source == 'geosite' ? 'geosite:cn' : 'rule-set:oc-cn-domain'
|
||||
(Value['dns']['fake-ip-filter'] ||= []) << filter_rule
|
||||
end
|
||||
if filter_mode == 'whitelist'
|
||||
@@ -684,23 +763,41 @@ begin
|
||||
end
|
||||
end
|
||||
if filter_mode == 'rule'
|
||||
filter_rule = 'RULE-SET,oc-cn-domain,real-ip'
|
||||
filter_rule = china_ip_route_domain_source == 'geosite' ? 'GEOSITE,cn,real-ip' : 'RULE-SET,oc-cn-domain,real-ip'
|
||||
(Value['dns']['fake-ip-filter'] ||= []).unshift(filter_rule)
|
||||
end
|
||||
Value['dns']['fake-ip-filter'].uniq!
|
||||
filters = Value.dig('dns', 'fake-ip-filter')
|
||||
filters.uniq! if filters.is_a?(Array)
|
||||
if filter_mode != 'whitelist'
|
||||
rule_set_hash = {'rule-providers+'=>{'oc-cn-domain'=>{'type'=>'http', 'interval'=>43200, 'behavior'=>'domain', 'format'=>'mrs', 'url'=>'https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/meta/geo/geosite/cn.mrs', 'path'=> './rule_provider/oc-cn-domain.mrs'}}}
|
||||
Value = YAML.overwrite(Value, rule_set_hash)
|
||||
if china_ip_route_domain_source == 'mrs'
|
||||
rule_set_hash = {'rule-providers+'=>{'oc-cn-domain'=>{'type'=>'http', 'interval'=>43200, 'behavior'=>'domain', 'format'=>'mrs', 'url'=>'https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/meta/geo/geosite/cn.mrs', 'path'=> './rule_provider/oc-cn-domain.mrs'}}}
|
||||
Value = YAML.overwrite(Value, rule_set_hash)
|
||||
end
|
||||
YAML.LOG_TIP('Because Need Ensure Bypassing IP Option Work, Added The Fake-IP-Filter Rule【%s】...' % [filter_rule])
|
||||
end
|
||||
end
|
||||
if fake_ip_mode == 'fake-ip'
|
||||
cn_domain_provider_ready = (china_ip_route || china_ip6_route) && filter_mode != 'whitelist' && china_ip_route_domain_source == 'mrs'
|
||||
unless cn_domain_provider_ready || Value.dig('rule-providers', 'oc-cn-domain')
|
||||
dangling_filters = Value.dig('dns', 'fake-ip-filter')
|
||||
if dangling_filters.is_a?(Array)
|
||||
dangling_filters = dangling_filters.select { |f| f.to_s.strip.match?(/\A(rule-set:oc-cn-domain|RULE-SET,oc-cn-domain,real-ip)\z/i) }
|
||||
if dangling_filters.any?
|
||||
Value['dns']['fake-ip-filter'] -= dangling_filters
|
||||
dangling_filters.each do |f|
|
||||
YAML.LOG_WARN('Rule-Provider【oc-cn-domain】Is Not Registered, Deleted The Fake-IP-Filter Rule【%s】to Avoid Core Start Failed...' % [f])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue Exception => e
|
||||
YAML.LOG_ERROR('Set Fake-IP-Filter Failed,【%s】' % [e.message])
|
||||
end
|
||||
end
|
||||
|
||||
# Custom Hosts
|
||||
threads << Thread.new do
|
||||
threads << YAML::Inline.new do
|
||||
begin
|
||||
if custom_host
|
||||
if (hosts_content = safe_load_yaml('/etc/openclash/custom/openclash_custom_hosts.list')) && !hosts_content.empty?
|
||||
@@ -719,7 +816,7 @@ begin
|
||||
end
|
||||
|
||||
# Authentication
|
||||
threads << Thread.new do
|
||||
threads << YAML::Inline.new do
|
||||
begin
|
||||
if (auth_config = safe_load_yaml('/tmp/yaml_openclash_auth'))
|
||||
Value['authentication'] = auth_config
|
||||
@@ -734,11 +831,30 @@ begin
|
||||
begin
|
||||
threads.clear
|
||||
|
||||
# Built-in hosts for common OpenWrt router hostnames, resolve to router LAN IP
|
||||
begin
|
||||
Value['hosts'] ||= {}
|
||||
builtin_hosts = {
|
||||
'openwrt.lan' => 'lan',
|
||||
'immortalwrt.lan' => 'lan',
|
||||
'lede.lan' => 'lan',
|
||||
'router.lan' => 'lan'
|
||||
}
|
||||
system_hostname = '$system_hostname'
|
||||
dnsmasq_domain = '$dnsmasq_domain'
|
||||
builtin_hosts[system_hostname.to_s + '.lan'] = 'lan' unless system_hostname.to_s.empty?
|
||||
builtin_hosts[system_hostname.to_s + '.' + dnsmasq_domain.to_s] = 'lan' unless system_hostname.to_s.empty? || dnsmasq_domain.to_s.empty?
|
||||
builtin_hosts.each { |k, v| Value['hosts'][k] ||= v }
|
||||
Value['dns']['use-hosts'] = true
|
||||
rescue Exception => e
|
||||
YAML.LOG_ERROR('Set Built-in Hosts Failed,【%s】' % [e.message])
|
||||
end
|
||||
|
||||
# DNS Loop Check
|
||||
if enable_redirect_dns == '1'
|
||||
dns_options = ['nameserver', 'fallback', 'default-nameserver', 'proxy-server-nameserver', 'nameserver-policy', 'direct-nameserver', 'proxy-server-nameserver-policy']
|
||||
dns_options.each do |option|
|
||||
threads << Thread.new(option) do |opt|
|
||||
threads << YAML::Inline.new(option) do |opt|
|
||||
begin
|
||||
next unless Value['dns'].key?(opt) && !Value['dns'][opt].nil?
|
||||
if opt != 'nameserver-policy' && opt != 'proxy-server-nameserver-policy'
|
||||
@@ -821,9 +937,10 @@ rescue Exception => e
|
||||
ensure
|
||||
begin
|
||||
YAML.dump(Value, config_file)
|
||||
YAML.cache_write(config_file, Value, '/tmp/yaml_change_marshal')
|
||||
rescue Exception => e
|
||||
YAML.LOG_ERROR('Write file failed:【%s】' % [e.message])
|
||||
File.delete('/tmp/yaml_change_marshal') rescue nil
|
||||
end
|
||||
File.delete('/tmp/yaml_change_marshal') rescue nil
|
||||
end
|
||||
" 2>/dev/null >> $LOG_FILE
|
||||
" 2>/dev/null >> $LOG_FILE
|
||||
@@ -49,205 +49,15 @@ fi
|
||||
LOG_OUT "Start Getting【$CONFIG_NAME】Groups Setting..."
|
||||
LOG_OUT "Deleting Old Configuration..."
|
||||
|
||||
del_options()
|
||||
{
|
||||
local section="$1"
|
||||
local config
|
||||
config_get "config" "$section" "config" ""
|
||||
|
||||
if [ "$config" = "$CONFIG_NAME" ]; then
|
||||
uci -q delete openclash."$section"
|
||||
fi
|
||||
}
|
||||
|
||||
config_load "openclash"
|
||||
config_foreach del_options "groups"
|
||||
config_foreach del_options "servers"
|
||||
config_foreach del_options "proxy-provider"
|
||||
uci -q commit openclash
|
||||
|
||||
# The proxy-groups block is imported by uci.rb, its field table is the one the set
|
||||
# direction reads back.
|
||||
ruby -ryaml -rYAML -I "/usr/share/openclash" -E UTF-8 -e "
|
||||
begin
|
||||
Value = YAML.load_file('$CONFIG_FILE');
|
||||
rescue Exception => e
|
||||
YAML.LOG_ERROR('Load File Failed,【' + e.message + '】');
|
||||
require 'uci';
|
||||
failed = UCI.import_groups('$CONFIG_FILE', '$CONFIG_NAME');
|
||||
if not failed.empty? then
|
||||
YAML.LOG_ERROR('Write Uci Config Failed,【' + failed.map{ |t, e| t.to_s + ' => ' + e.to_s }.join('; ') + '】');
|
||||
end;
|
||||
|
||||
threads_g = [];
|
||||
threadsp = [];
|
||||
threads_uci = [];
|
||||
uci_commands = [];
|
||||
uci_name_tmp = [];
|
||||
max_threads = 30
|
||||
queue = SizedQueue.new(max_threads)
|
||||
|
||||
if not Value.key?('proxy-groups') or Value['proxy-groups'].nil? then
|
||||
proxy-groups = [];
|
||||
end;
|
||||
|
||||
CONFIG_GROUP = (['DIRECT', 'REJECT', 'GLOBAL', 'REJECT-DROP', 'PASS', 'COMPATIBLE'] + (Value['proxy-groups']&.map { |x| x['name'] } || [])).uniq;
|
||||
Value['proxy-groups'].each_with_index do |x, index|
|
||||
uci_name_tmp << %x{uci -q add openclash groups 2>&1}.chomp
|
||||
queue.push(nil)
|
||||
threadsp << Thread.new {
|
||||
begin
|
||||
next unless x['name'] && x['type'];
|
||||
uci_set='set openclash.' + uci_name_tmp[index] + '.'
|
||||
uci_add='add_list openclash.' + uci_name_tmp[index] + '.'
|
||||
|
||||
YAML.LOG('Start Getting【${CONFIG_NAME} - ' + x['type'].to_s + ' - ' + x['name'].to_s + '】Group Setting...');
|
||||
|
||||
threads_g << Thread.new {
|
||||
#name
|
||||
if x.key?('name') then
|
||||
uci_commands << uci_set + 'name=\"' + x['name'].to_s + '\"'
|
||||
end;
|
||||
};
|
||||
|
||||
threads_g << Thread.new {
|
||||
#type
|
||||
if x.key?('type') then
|
||||
uci_commands << uci_set + 'type=\"' + x['type'].to_s + '\"'
|
||||
end;
|
||||
};
|
||||
|
||||
threads_g << Thread.new {
|
||||
#enabled
|
||||
uci_commands << uci_set + 'enabled=\"1\"'
|
||||
#config
|
||||
uci_commands << uci_set + 'config=\"' + '${CONFIG_NAME}' + '\"'
|
||||
#old_name
|
||||
uci_commands << uci_set + 'old_name=\"' + x['name'] + '\"'
|
||||
};
|
||||
|
||||
threads_g << Thread.new {
|
||||
#strategy
|
||||
if x.key?('strategy') and x['type'] == 'load-balance' then
|
||||
uci_commands << uci_set + 'strategy=\"' + x['strategy'].to_s + '\"'
|
||||
end;
|
||||
};
|
||||
|
||||
threads_g << Thread.new {
|
||||
#strategy-smart
|
||||
if x.key?('strategy') and x['type'] == 'smart' then
|
||||
uci_commands << uci_set + 'strategy_smart=\"' + x['strategy'].to_s + '\"'
|
||||
end;
|
||||
};
|
||||
|
||||
threads_g << Thread.new {
|
||||
#uselightgbm
|
||||
if x.key?('uselightgbm') and x['type'] == 'smart' then
|
||||
uci_commands << uci_set + 'uselightgbm=\"' + x['uselightgbm'].to_s + '\"'
|
||||
end;
|
||||
};
|
||||
|
||||
threads_g << Thread.new {
|
||||
#collectdata
|
||||
if x.key?('collectdata') and x['type'] == 'smart' then
|
||||
uci_commands << uci_set + 'collectdata=\"' + x['collectdata'].to_s + '\"'
|
||||
end;
|
||||
};
|
||||
|
||||
threads_g << Thread.new {
|
||||
#policy_priority
|
||||
if x.key?('policy-priority') and x['type'] == 'smart' then
|
||||
uci_commands << uci_set + 'policy_priority=\"' + x['policy-priority'].to_s + '\"'
|
||||
end;
|
||||
};
|
||||
|
||||
threads_g << Thread.new {
|
||||
#disable-udp
|
||||
if x.key?('disable-udp') then
|
||||
uci_commands << uci_set + 'disable_udp=\"' + x['disable-udp'].to_s + '\"'
|
||||
end;
|
||||
};
|
||||
|
||||
threads_g << Thread.new {
|
||||
if x['type'] == 'url-test' or x['type'] == 'fallback' or x['type'] == 'load-balance' or x['type'] == 'smart' then
|
||||
#test_url
|
||||
if x.key?('url') then
|
||||
uci_commands << uci_set + 'test_url=\"' + x['url'].to_s + '\"'
|
||||
end;
|
||||
|
||||
#test_interval
|
||||
if x.key?('interval') then
|
||||
uci_commands << uci_set + 'test_interval=\"' + x['interval'].to_s + '\"'
|
||||
end;
|
||||
|
||||
#test_tolerance
|
||||
if x['type'] == 'url-test' then
|
||||
if x.key?('tolerance') then
|
||||
uci_commands << uci_set + 'tolerance=\"' + x['tolerance'].to_s + '\"'
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
};
|
||||
|
||||
threads_g << Thread.new {
|
||||
#Policy Filter
|
||||
if x.key?('filter') then
|
||||
uci_commands << uci_set + 'policy_filter=\"' + x['filter'].to_s + '\"'
|
||||
end
|
||||
};
|
||||
|
||||
threads_g << Thread.new {
|
||||
#interface-name
|
||||
if x.key?('interface-name') then
|
||||
uci_commands << uci_set + 'interface_name=\"' + x['interface-name'].to_s + '\"'
|
||||
end
|
||||
};
|
||||
|
||||
threads_g << Thread.new {
|
||||
#routing-mark
|
||||
if x.key?('routing-mark') then
|
||||
uci_commands << uci_set + 'routing_mark=\"' + x['routing-mark'].to_s + '\"'
|
||||
end
|
||||
};
|
||||
|
||||
threads_g << Thread.new {
|
||||
#other_group
|
||||
if x.key?('proxies') then
|
||||
x['proxies'].each{
|
||||
|y|
|
||||
if CONFIG_GROUP.include?(y) then
|
||||
uci_commands << uci_add + 'other_group=\"^' + y.to_s + '$\"'
|
||||
end
|
||||
}
|
||||
end
|
||||
};
|
||||
|
||||
threads_g << Thread.new {
|
||||
#icon
|
||||
if x.key?('icon') then
|
||||
uci_commands << uci_set + 'icon=\"' + x['icon'].to_s + '\"'
|
||||
end
|
||||
};
|
||||
threads_g.each(&:join);
|
||||
rescue Exception => e
|
||||
YAML.LOG_ERROR('Resolve Groups Failed,【${CONFIG_NAME} - ' + x['type'] + ' - ' + x['name'] + ': ' + e.message + '】');
|
||||
ensure
|
||||
queue.pop
|
||||
end;
|
||||
};
|
||||
end;
|
||||
threadsp.each(&:join);
|
||||
batch_size = 30;
|
||||
(0...uci_commands.length).step(batch_size) do |i|
|
||||
threads_uci << Thread.new{
|
||||
IO.popen('uci -q batch', 'w') do |pipe|
|
||||
uci_commands[i, batch_size].each { |cmd| pipe.puts(cmd) }
|
||||
end
|
||||
};
|
||||
end;
|
||||
threads_uci.each(&:join);
|
||||
uci_name_tmp.each do |x|
|
||||
if x =~ /uci -q delete/ then
|
||||
system(x);
|
||||
end;
|
||||
end;
|
||||
system('uci -q commit openclash');
|
||||
system('rm -rf /tmp/yaml_other_group.yaml 2>/dev/null');
|
||||
" 2>/dev/null >> $LOG_FILE
|
||||
" >> $LOG_FILE 2>&1
|
||||
|
||||
/usr/share/openclash/yml_proxys_get.sh "$CONFIG_FILE" >/dev/null 2>&1
|
||||
del_lock
|
||||
del_lock
|
||||
@@ -42,289 +42,21 @@ if [ -z "$CONFIG_NAME" ]; then
|
||||
CONFIG_NAME="config.yaml"
|
||||
fi
|
||||
|
||||
set_groups()
|
||||
{
|
||||
if [ -z "$1" ]; then
|
||||
return
|
||||
fi
|
||||
# Keep the ruby heap small, the group matching loop reads the whole package into memory
|
||||
RUBY_GC_HEAP_GROWTH_FACTOR=1.1
|
||||
RUBY_GC_HEAP_GROWTH_MAX_SLOTS=200000
|
||||
RUBY_GC_MALLOC_LIMIT_MAX=67108864
|
||||
export RUBY_GC_HEAP_GROWTH_FACTOR RUBY_GC_HEAP_GROWTH_MAX_SLOTS RUBY_GC_MALLOC_LIMIT_MAX
|
||||
|
||||
if [ "$add_for_this" -eq 1 ]; then
|
||||
return
|
||||
fi
|
||||
ruby -ryaml -rYAML -I "/usr/share/openclash" -E UTF-8 -e "
|
||||
require 'uci';
|
||||
begin
|
||||
UCI.write_groups('$GROUP_FILE', '${CONFIG_NAME}');
|
||||
rescue ::Exception => e
|
||||
YAML.LOG_ERROR('Resolve Groups Failed,【${CONFIG_NAME} - ' + e.message + '】');
|
||||
end;
|
||||
" 2>/dev/null >> $LOG_FILE
|
||||
|
||||
if [ "$1" = "all" ] || [[ "$3" =~ ${1} ]] || [ -n "$(echo ${3} |grep -Eo ${1})" ]; then
|
||||
set_group=1
|
||||
add_for_this=1
|
||||
echo " - \"${2}\"" >>$GROUP_FILE
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
#加入节点
|
||||
yml_servers_add()
|
||||
{
|
||||
|
||||
local section="$1"
|
||||
local enabled config name
|
||||
add_for_this=0
|
||||
config_get_bool "enabled" "$section" "enabled" "1"
|
||||
config_get "config" "$section" "config" ""
|
||||
config_get "name" "$section" "name" ""
|
||||
|
||||
if [ -n "$config" ] && [ "$config" != "$CONFIG_NAME" ] && [ "$config" != "all" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if [ "$enabled" = "0" ]; then
|
||||
return
|
||||
else
|
||||
if [ -z "$4" ]; then
|
||||
config_list_foreach "$section" "groups" set_groups "$name" "$2"
|
||||
fi
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
add_other_group()
|
||||
{
|
||||
local section="$1"
|
||||
local name enabled config
|
||||
config_get_bool "enabled" "$section" "enabled" "1"
|
||||
config_get "config" "$section" "config" ""
|
||||
config_get "name" "$section" "name" ""
|
||||
|
||||
if [ "$enabled" = "0" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if [ -n "$config" ] && [ "$config" != "$CONFIG_NAME" ] && [ "$config" != "all" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if [ -z "$name" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if [ "$3" = "$name" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if [ "$2" = "all" ] || [[ "$name" =~ ${2} ]] || [ -n "$(echo ${name} |grep -Eo ${2})" ]; then
|
||||
set_group=1
|
||||
echo " - ${name}" >>$GROUP_FILE
|
||||
fi
|
||||
}
|
||||
|
||||
#加入其它策略组
|
||||
set_other_groups()
|
||||
{
|
||||
if [ -z "$1" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$1" =~ "DIRECT" ]] || [ -n "$(echo ${1} |grep 'DIRECT')" ]; then
|
||||
set_group=1
|
||||
echo " - DIRECT" >>$GROUP_FILE
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$1" =~ "REJECT" ]] || [ -n "$(echo ${1} |grep 'REJECT')" ]; then
|
||||
set_group=1
|
||||
echo " - REJECT" >>$GROUP_FILE
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$1" =~ "REJECT-DROP" ]] || [ -n "$(echo ${1} |grep 'REJECT-DROP')" ]; then
|
||||
set_group=1
|
||||
echo " - REJECT-DROP" >>$GROUP_FILE
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$1" =~ "PASS" ]] || [ -n "$(echo ${1} |grep 'PASS')" ]; then
|
||||
set_group=1
|
||||
echo " - PASS" >>$GROUP_FILE
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$1" =~ "GLOBAL" ]] || [ -n "$(echo ${1} |grep 'GLOBAL')" ]; then
|
||||
set_group=1
|
||||
echo " - GLOBAL" >>$GROUP_FILE
|
||||
return
|
||||
fi
|
||||
|
||||
config_foreach add_other_group "groups" "$1" "$2" #比对策略组
|
||||
}
|
||||
|
||||
#加入代理集
|
||||
set_proxy_provider()
|
||||
{
|
||||
local section="$1"
|
||||
local enabled config name
|
||||
add_for_this=0
|
||||
config_get_bool "enabled" "$section" "enabled" "1"
|
||||
config_get "config" "$section" "config" ""
|
||||
config_get "name" "$section" "name" ""
|
||||
|
||||
if [ -n "$config" ] && [ "$config" != "$CONFIG_NAME" ] && [ "$config" != "all" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if [ "$enabled" = "0" ]; then
|
||||
return
|
||||
else
|
||||
if [ -z "$3" ]; then
|
||||
config_list_foreach "$section" "groups" set_provider_groups "$name" "$2"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
set_provider_groups()
|
||||
{
|
||||
if [ -z "$1" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if [ "$add_for_this" -eq 1 ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$3" =~ ${1} ]] || [ -n "$(echo ${3} |grep -Eo ${1})" ] || [ "$1" = "all" ]; then
|
||||
set_proxy_provider=1
|
||||
add_for_this=1
|
||||
echo " - ${2}" >>$GROUP_FILE
|
||||
fi
|
||||
}
|
||||
|
||||
#创建策略组
|
||||
yml_groups_set()
|
||||
{
|
||||
local section="$1"
|
||||
local enabled config type name disable_udp strategy old_name test_url test_interval tolerance policy_filter uselightgbm collectdata policy_priority other_parameters icon
|
||||
config_get_bool "enabled" "$section" "enabled" "1"
|
||||
config_get "config" "$section" "config" ""
|
||||
config_get "type" "$section" "type" ""
|
||||
config_get "name" "$section" "name" ""
|
||||
config_get "disable_udp" "$section" "disable_udp" ""
|
||||
config_get "strategy" "$section" "strategy" ""
|
||||
config_get "old_name" "$section" "old_name" ""
|
||||
config_get "test_url" "$section" "test_url" ""
|
||||
config_get "test_interval" "$section" "test_interval" ""
|
||||
config_get "tolerance" "$section" "tolerance" ""
|
||||
config_get "policy_filter" "$section" "policy_filter" ""
|
||||
config_get "uselightgbm" "$section" "uselightgbm" ""
|
||||
config_get "collectdata" "$section" "collectdata" ""
|
||||
config_get "policy_priority" "$section" "policy_priority" ""
|
||||
config_get "other_parameters" "$section" "other_parameters" ""
|
||||
config_get "icon" "$section" "icon" ""
|
||||
|
||||
if [ "$enabled" = "0" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if [ -n "$config" ] && [ "$config" != "$CONFIG_NAME" ] && [ "$config" != "all" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if [ -z "$type" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if [ -z "$name" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
LOG_OUT "Start Writing【$CONFIG_NAME - $type - $name】Group To Config File..."
|
||||
|
||||
echo " - name: $name" >>$GROUP_FILE
|
||||
echo " type: $type" >>$GROUP_FILE
|
||||
echo " proxies: $name" >>$GROUP_FILE
|
||||
|
||||
set_group=0
|
||||
set_proxy_provider=0
|
||||
|
||||
config_list_foreach "$section" "other_group" set_other_groups "$name" #加入其他策略组
|
||||
config_foreach yml_servers_add "servers" "$name" "$type" #加入服务器节点
|
||||
|
||||
echo " use: $name" >>$GROUP_FILE
|
||||
|
||||
config_foreach set_proxy_provider "proxy-provider" "$name" #加入代理集
|
||||
|
||||
#Prevent the un support symbol name
|
||||
name=$(echo "$name" |sed 's/\//\\\//g' 2>/dev/null)
|
||||
if [ "$set_group" -eq 1 ]; then
|
||||
sed -i "/^ \{0,\}proxies: ${name}/c\ proxies:" $GROUP_FILE
|
||||
else
|
||||
sed -i "/proxies: ${name}/d" $GROUP_FILE 2>/dev/null
|
||||
fi
|
||||
|
||||
if [ "$set_proxy_provider" -eq 1 ]; then
|
||||
sed -i "/^ \{0,\}use: ${name}/c\ use:" $GROUP_FILE
|
||||
else
|
||||
sed -i "/use: ${name}/d" $GROUP_FILE 2>/dev/null
|
||||
fi
|
||||
|
||||
if [ "$set_group" -eq 0 ] && [ "$set_proxy_provider" -eq 0 ]; then
|
||||
echo " proxies:" >>$GROUP_FILE
|
||||
echo " - DIRECT" >>$GROUP_FILE
|
||||
fi
|
||||
|
||||
if [ "$type" = "load-balance" ]; then
|
||||
[ -n "$strategy" ] && {
|
||||
echo " strategy: $strategy" >>$GROUP_FILE
|
||||
}
|
||||
fi
|
||||
|
||||
[ -n "$disable_udp" ] && {
|
||||
echo " disable-udp: $disable_udp" >>$GROUP_FILE
|
||||
}
|
||||
|
||||
[ -n "$test_url" ] && {
|
||||
echo " url: $test_url" >>$GROUP_FILE
|
||||
}
|
||||
[ -n "$test_interval" ] && {
|
||||
echo " interval: \"$test_interval\"" >>$GROUP_FILE
|
||||
}
|
||||
[ -n "$tolerance" ] && {
|
||||
echo " tolerance: \"$tolerance\"" >>$GROUP_FILE
|
||||
}
|
||||
[ -n "$policy_filter" ] && {
|
||||
echo " filter: \"$policy_filter\"" >>$GROUP_FILE
|
||||
}
|
||||
[ -n "$interface_name" ] && {
|
||||
echo " interface-name: \"$interface_name\"" >>$GROUP_FILE
|
||||
}
|
||||
[ -n "$routing_mark" ] && {
|
||||
echo " routing-mark: \"$routing_mark\"" >>$GROUP_FILE
|
||||
}
|
||||
|
||||
if [ "$type" = "smart" ]; then
|
||||
[ -n "$uselightgbm" ] && {
|
||||
echo " uselightgbm: $uselightgbm" >>$GROUP_FILE
|
||||
}
|
||||
[ -n "$collectdata" ] && {
|
||||
echo " collectdata: $collectdata" >>$GROUP_FILE
|
||||
}
|
||||
[ -n "$policy_priority" ] && {
|
||||
echo " policy-priority: \"$policy_priority\"" >>$GROUP_FILE
|
||||
}
|
||||
fi
|
||||
|
||||
#icon
|
||||
if [ -n "$icon" ]; then
|
||||
echo " icon: $icon" >> "$GROUP_FILE"
|
||||
fi
|
||||
|
||||
#other_parameters
|
||||
if [ -n "$other_parameters" ]; then
|
||||
echo -e "$other_parameters" >> "$GROUP_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
echo "proxy-groups:" >$GROUP_FILE
|
||||
config_load "openclash"
|
||||
config_foreach yml_groups_set "groups"
|
||||
sed -i "s/#delete_//g" "$CONFIG_FILE" 2>/dev/null
|
||||
|
||||
/usr/share/openclash/yml_proxys_set.sh "$CONFIG_FILE" >/dev/null 2>&1
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -14,7 +14,7 @@ yml_other_set()
|
||||
{
|
||||
ruby -ryaml -rYAML -I "/usr/share/openclash" -E UTF-8 -e "
|
||||
begin
|
||||
Value = YAML.load_file('$2');
|
||||
Value = YAML.load_file_cached('$2', '/tmp/yaml_change_marshal');
|
||||
rescue Exception => e
|
||||
YAML.LOG_ERROR('Load File Failed,【' + e.message + '】');
|
||||
end;
|
||||
@@ -24,11 +24,11 @@ yml_other_set()
|
||||
# GEOIP replace
|
||||
geoip_pattern = /^GEOIP,([A-Za-z]{2}),([^,]+)(,.*)?/;
|
||||
match_pattern = /(^MATCH.*|^FINAL.*)/;
|
||||
thread_pool << Thread.new{
|
||||
thread_pool << YAML::Inline.new{
|
||||
#BT/P2P DIRECT Rules
|
||||
begin
|
||||
if $3 == 1 then
|
||||
if system('strings /etc/openclash/GeoSite.dat /etc/openclash/GeoSite.dat |grep -i category-public-tracker >/dev/null 2>&1') then
|
||||
if system('grep -qai category-public-tracker /etc/openclash/GeoSite.dat >/dev/null 2>&1') then
|
||||
bt_rules = ['GEOSITE,category-public-tracker,DIRECT'];
|
||||
else
|
||||
bt_rules = [
|
||||
@@ -126,6 +126,7 @@ yml_other_set()
|
||||
rescue Exception => e
|
||||
CONFIG_GROUP = ['DIRECT', 'REJECT', 'GLOBAL', 'REJECT-DROP', 'PASS', 'COMPATIBLE'];
|
||||
end;
|
||||
CONFIG_GROUP_HASH = CONFIG_GROUP.map{|x| [x, true]}.to_h;
|
||||
|
||||
#Custom Rules
|
||||
begin
|
||||
@@ -147,9 +148,10 @@ yml_other_set()
|
||||
anchors_orig[:geo_rule] = nil
|
||||
end;
|
||||
|
||||
custom_data_cache = {};
|
||||
custom_files.each{|file_info|
|
||||
if File::exist?(file_info[:file]) then
|
||||
custom_data = YAML.load_file(file_info[:file]);
|
||||
custom_data = custom_data_cache.fetch(file_info[:file]){ custom_data_cache[file_info[:file]] = YAML.load_file(file_info[:file]); };
|
||||
next if custom_data == false;
|
||||
|
||||
rules_array = case custom_data.class.to_s
|
||||
@@ -201,7 +203,7 @@ yml_other_set()
|
||||
|
||||
valid_rules = transformed_rules.select{|x|
|
||||
RULE_GROUP = ((x.split(',')[-1] =~ rule_suffix_regex) ? x.split(',')[-2] : x.split(',')[-1]).strip;
|
||||
if CONFIG_GROUP.include?(RULE_GROUP) then
|
||||
if CONFIG_GROUP_HASH[RULE_GROUP] then
|
||||
true;
|
||||
else
|
||||
YAML.LOG_WARN('Skiped The Custom Rule Because Group & Proxy Not Found:【' + x + '】');
|
||||
@@ -211,7 +213,7 @@ yml_other_set()
|
||||
|
||||
if Value.has_key?('rules') and not Value['rules'].to_a.empty? then
|
||||
if file_info[:position] == 'top' then
|
||||
valid_rules.reverse.each{|x| Value['rules'].insert(0,x)};
|
||||
Value['rules'] = valid_rules + Value['rules'];
|
||||
else
|
||||
ruby_add_index = nil;
|
||||
if anchors_orig[:dst80_rule]
|
||||
@@ -225,8 +227,11 @@ yml_other_set()
|
||||
end;
|
||||
ruby_add_index ||= -1;
|
||||
|
||||
insert_rules = ruby_add_index == -1 ? valid_rules : valid_rules.reverse;
|
||||
insert_rules.each{|x| Value['rules'].insert(ruby_add_index,x)};
|
||||
if ruby_add_index == -1 then
|
||||
Value['rules'] = Value['rules'] + valid_rules;
|
||||
else
|
||||
Value['rules'] = Value['rules'][0...ruby_add_index] + valid_rules + Value['rules'][ruby_add_index..-1];
|
||||
end;
|
||||
end;
|
||||
Value['rules'] = Value['rules'].uniq;
|
||||
else
|
||||
@@ -244,7 +249,7 @@ yml_other_set()
|
||||
['sub-rules'].each{|key|
|
||||
custom_files.each{|file_info|
|
||||
if File::exist?(file_info[:file]) then
|
||||
custom_data = YAML.load_file(file_info[:file]);
|
||||
custom_data = custom_data_cache.fetch(file_info[:file]){ custom_data_cache[file_info[:file]] = YAML.load_file(file_info[:file]); };
|
||||
if custom_data != false and custom_data.class.to_s == 'Hash' then
|
||||
if not custom_data[key].to_a.empty? and custom_data[key].class.to_s == 'Hash' then
|
||||
if Value.has_key?(key) and not Value[key].to_a.empty? then
|
||||
@@ -281,7 +286,7 @@ yml_other_set()
|
||||
end;
|
||||
};
|
||||
|
||||
thread_pool << Thread.new{
|
||||
thread_pool << YAML::Inline.new{
|
||||
threads = [];
|
||||
|
||||
#provider CDN
|
||||
@@ -290,17 +295,17 @@ yml_other_set()
|
||||
provider_configs.each do |provider_type, path_prefix|
|
||||
if Value.key?(provider_type) && Value[provider_type].is_a?(Hash) then
|
||||
Value[provider_type].each{|name, config|
|
||||
threads << Thread.new {
|
||||
threads << YAML::Inline.new {
|
||||
# CDN
|
||||
if '$github_address_mod' != '0' and config['url'] then
|
||||
if config['url'] =~ /^https:\/\/raw.githubusercontent.com/ then
|
||||
if '$github_address_mod' == 'https://cdn.jsdelivr.net/' or
|
||||
'$github_address_mod' == 'https://fastly.jsdelivr.net/' or
|
||||
if '$github_address_mod' == 'https://cdn.jsdelivr.net/' or
|
||||
'$github_address_mod' == 'https://fastly.jsdelivr.net/' or
|
||||
'$github_address_mod' == 'https://testingcf.jsdelivr.net/' then
|
||||
url_parts = config['url'].split('/');
|
||||
if url_parts.length >= 5 then
|
||||
config['url'] = '$github_address_mod' + 'gh/' + url_parts[3] + '/' +
|
||||
url_parts[4] + '@' + config['url'].split(url_parts[2] +
|
||||
config['url'] = '$github_address_mod' + 'gh/' + url_parts[3] + '/' +
|
||||
url_parts[4] + '@' + config['url'].split(url_parts[2] +
|
||||
'/' + url_parts[3] + '/' + url_parts[4] + '/')[1];
|
||||
end;
|
||||
else
|
||||
@@ -322,7 +327,7 @@ yml_other_set()
|
||||
begin
|
||||
if '$tolerance' != '0' and Value.key?('proxy-groups') and Value['proxy-groups'].is_a?(Array) then
|
||||
Value['proxy-groups'].each{|group|
|
||||
threads << Thread.new {
|
||||
threads << YAML::Inline.new {
|
||||
if group['type'] == 'url-test' then
|
||||
group['tolerance'] = ${tolerance};
|
||||
end;
|
||||
@@ -338,7 +343,7 @@ yml_other_set()
|
||||
if '$urltest_interval_mod' != '0' then
|
||||
if Value.key?('proxy-groups') and Value['proxy-groups'].is_a?(Array) then
|
||||
Value['proxy-groups'].each{|group|
|
||||
threads << Thread.new {
|
||||
threads << YAML::Inline.new {
|
||||
if ['url-test', 'fallback', 'load-balance', 'smart'].include?(group['type']) then
|
||||
group['interval'] = ${urltest_interval_mod};
|
||||
end;
|
||||
@@ -347,7 +352,7 @@ yml_other_set()
|
||||
end;
|
||||
if Value.key?('proxy-providers') then
|
||||
Value['proxy-providers'].each{|name, provider|
|
||||
threads << Thread.new {
|
||||
threads << YAML::Inline.new {
|
||||
if provider['health-check'] and provider['health-check']['enable'] then
|
||||
provider['health-check']['interval'] = ${urltest_interval_mod};
|
||||
end;
|
||||
@@ -364,7 +369,7 @@ yml_other_set()
|
||||
if '$urltest_address_mod' != '0' then
|
||||
if Value.key?('proxy-providers') then
|
||||
Value['proxy-providers'].each{|name, provider|
|
||||
threads << Thread.new {
|
||||
threads << YAML::Inline.new {
|
||||
if provider['health-check'] and provider['health-check']['enable'] then
|
||||
provider['health-check']['url'] = '$urltest_address_mod';
|
||||
end;
|
||||
@@ -373,7 +378,7 @@ yml_other_set()
|
||||
end;
|
||||
if Value.key?('proxy-groups') and Value['proxy-groups'].is_a?(Array) then
|
||||
Value['proxy-groups'].each{|group|
|
||||
threads << Thread.new {
|
||||
threads << YAML::Inline.new {
|
||||
if ['url-test', 'fallback', 'load-balance', 'smart'].include?(group['type']) then
|
||||
group['url'] = '$urltest_address_mod';
|
||||
end;
|
||||
@@ -389,7 +394,7 @@ yml_other_set()
|
||||
begin
|
||||
if ('${8}' == '1' or '${9}' == '1' or '${11}' != '0' or '${12}' != '0' or '${12}' == '1' or '${13}' == '1' or '${14}' != '0') and Value.key?('proxy-groups') and Value['proxy-groups'].is_a?(Array) then
|
||||
Value['proxy-groups'].each{|group|
|
||||
threads << Thread.new {
|
||||
threads << YAML::Inline.new {
|
||||
if '${8}' == '1' and ['url-test', 'load-balance'].include?(group['type']) then
|
||||
group['type'] = 'smart';
|
||||
end;
|
||||
@@ -431,6 +436,7 @@ yml_other_set()
|
||||
rescue Exception => e
|
||||
YAML.LOG_ERROR('Write file failed:【%s】' % [e.message])
|
||||
end
|
||||
File.delete('/tmp/yaml_change_marshal') rescue nil
|
||||
end" 2>/dev/null >> $LOG_FILE
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
#!/bin/sh
|
||||
|
||||
# One-line JSON summary of a config file for the subscription result view.
|
||||
# Rule lines are read without their trailing options (no-resolve/no-resolve6/src).
|
||||
# Age encrypted files are decrypted by YAML.rb; a missing key degrades to need_key=1.
|
||||
# The device ruby ships without the json gem, so the JSON is serialized by hand.
|
||||
|
||||
CONFIG_FILE="$1"
|
||||
|
||||
if [ -z "$CONFIG_FILE" ] || [ ! -s "$CONFIG_FILE" ]; then
|
||||
echo '{"status":"error","message":"config file missing"}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
ruby -ryaml -rYAML -I "/usr/share/openclash" -E UTF-8 - "$CONFIG_FILE" 2>/dev/null <<'RUBY'
|
||||
def jstr(str)
|
||||
str.to_s.gsub("\\") { "\\\\" }.gsub("\"") { "\\\"" }.gsub("\n") { "\\n" }.gsub("\r") { "" }.gsub("\t") { "\\t" }
|
||||
end
|
||||
|
||||
def jdump(obj)
|
||||
case obj
|
||||
when Hash
|
||||
"{" + obj.map { |k, v| "\"" + jstr(k) + "\":" + jdump(v) }.join(",") + "}"
|
||||
when Array
|
||||
"[" + obj.map { |v| jdump(v) }.join(",") + "]"
|
||||
when Integer, Float
|
||||
obj.to_s
|
||||
when TrueClass, FalseClass
|
||||
obj ? "true" : "false"
|
||||
when NilClass
|
||||
"null"
|
||||
else
|
||||
"\"" + jstr(obj) + "\""
|
||||
end
|
||||
end
|
||||
|
||||
path = ARGV[0]
|
||||
res = { "status" => "success" }
|
||||
begin
|
||||
res["mtime"] = File.mtime(path).strftime("%Y-%m-%d %H:%M:%S")
|
||||
header = File.binread(path, 512).to_s
|
||||
res["age"] = header.include?("BEGIN AGE ENCRYPTED FILE")
|
||||
cfg = YAML.load_file(path)
|
||||
cfg = {} unless cfg.is_a?(Hash)
|
||||
proxies = cfg["proxies"].is_a?(Array) ? cfg["proxies"] : []
|
||||
pprov = cfg["proxy-providers"].is_a?(Hash) ? cfg["proxy-providers"] : {}
|
||||
groups = cfg["proxy-groups"].is_a?(Array) ? cfg["proxy-groups"] : []
|
||||
rules = cfg["rules"].is_a?(Array) ? cfg["rules"] : []
|
||||
rprov = cfg["rule-providers"].is_a?(Hash) ? cfg["rule-providers"] : {}
|
||||
types = {}
|
||||
proxies.each do |proxy|
|
||||
t = (proxy.is_a?(Hash) ? proxy["type"] : nil).to_s
|
||||
t = "unknown" if t.empty?
|
||||
types[t] = types[t].to_i + 1
|
||||
end
|
||||
prov_items = []
|
||||
prov_nodes = {}
|
||||
prov_nodes_total = 0
|
||||
pprov.each do |pname, pconf|
|
||||
count = nil
|
||||
ptypes = {}
|
||||
if pconf.is_a?(Hash)
|
||||
candidates = []
|
||||
ppath = pconf["path"].to_s
|
||||
candidates << ppath if ppath.start_with?("/")
|
||||
candidates << "/etc/openclash/proxy_provider/#{pname}.yaml"
|
||||
candidates.each do |cand|
|
||||
next if cand.empty? || !File.exist?(cand)
|
||||
begin
|
||||
pf = YAML.load_file(cand)
|
||||
rescue StandardError
|
||||
next
|
||||
end
|
||||
next unless pf.is_a?(Hash) && pf["proxies"].is_a?(Array)
|
||||
count = pf["proxies"].size
|
||||
pf["proxies"].each do |px|
|
||||
t = (px.is_a?(Hash) ? px["type"] : nil).to_s
|
||||
t = "unknown" if t.empty?
|
||||
ptypes[t] = ptypes[t].to_i + 1
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
prov_nodes[pname.to_s] = count if count
|
||||
prov_nodes_total += count if count
|
||||
prov_items << { "name" => pname.to_s, "nodes" => count, "types" => ptypes }
|
||||
end
|
||||
group_names = {}
|
||||
groups.each do |group|
|
||||
next unless group.is_a?(Hash)
|
||||
group_names[group["name"].to_s] = true
|
||||
end
|
||||
ref_counts = {}
|
||||
groups.each do |group|
|
||||
next unless group.is_a?(Hash)
|
||||
seen = {}
|
||||
mlist = group["proxies"].is_a?(Array) ? group["proxies"] : []
|
||||
mlist.each do |m|
|
||||
m = m.to_s
|
||||
next if m == group["name"].to_s || !group_names[m] || seen[m]
|
||||
seen[m] = true
|
||||
ref_counts[m] = ref_counts[m].to_i + 1
|
||||
end
|
||||
end
|
||||
items = []
|
||||
groups.each do |group|
|
||||
next unless group.is_a?(Hash)
|
||||
t = group["type"].to_s
|
||||
next if items.size >= 80
|
||||
grefs = ref_counts[group["name"].to_s].to_i
|
||||
node_count = 0
|
||||
members = []
|
||||
member_names = group["proxies"].is_a?(Array) ? group["proxies"] : []
|
||||
member_names.each do |m|
|
||||
m = m.to_s
|
||||
if m == "DIRECT"
|
||||
members << { "k" => "direct", "v" => "DIRECT" }
|
||||
elsif m.start_with?("REJECT") || m == "PASS"
|
||||
members << { "k" => "reject", "v" => m }
|
||||
elsif group_names[m]
|
||||
members << { "k" => "ref", "v" => m }
|
||||
else
|
||||
node_count += 1
|
||||
end
|
||||
end
|
||||
if group["use"].is_a?(Array)
|
||||
group["use"].each do |u|
|
||||
u = u.to_s
|
||||
members << { "k" => "provider", "v" => u }
|
||||
node_count += prov_nodes[u] if prov_nodes[u]
|
||||
end
|
||||
end
|
||||
members << { "k" => "all", "v" => ".*" } if group["include-all-proxies"] == true
|
||||
members << { "k" => "nodes", "v" => node_count } if node_count > 0
|
||||
items << { "n" => group["name"].to_s, "t" => t, "refs" => grefs, "members" => members }
|
||||
end
|
||||
refs = 0
|
||||
rtypes = {}
|
||||
targets = {}
|
||||
target_order = []
|
||||
# options at the end of a rule line must not be mistaken for the target
|
||||
rule_opts = { "no-resolve" => true, "no-resolve6" => true, "src" => true }
|
||||
rules.each do |rule|
|
||||
s = rule.to_s.strip
|
||||
next if s.empty?
|
||||
parts = s.split(",").map { |p| p.to_s.strip }
|
||||
parts.pop while parts.size >= 3 && rule_opts[parts.last.downcase]
|
||||
next if parts.size < 2
|
||||
rtype = parts[0].upcase
|
||||
rtypes[rtype] = rtypes[rtype].to_i + 1
|
||||
refs += 1 if rtype == "RULE-SET"
|
||||
target = parts.last.to_s
|
||||
next if target.empty?
|
||||
unless targets[target]
|
||||
targets[target] = { "t" => target, "c" => 0, "sets" => [], "rules" => [] }
|
||||
target_order << target
|
||||
end
|
||||
entry = targets[target]
|
||||
entry["c"] += 1
|
||||
label = parts[0...-1].join(",")
|
||||
if rtype == "RULE-SET"
|
||||
entry["sets"] << parts[1].to_s if parts.size >= 3 && entry["sets"].size < 48
|
||||
else
|
||||
entry["rules"] << label if !label.empty? && entry["rules"].size < 48
|
||||
end
|
||||
end
|
||||
mode = "empty"
|
||||
mode = "provider" if proxies.empty? && !pprov.empty?
|
||||
mode = "proxies" unless proxies.empty?
|
||||
res["mode"] = mode
|
||||
res["nodes"] = { "total" => proxies.size, "types" => types }
|
||||
res["providers"] = { "proxy" => pprov.size, "rule" => rprov.size, "nodes_total" => prov_nodes_total, "items" => prov_items }
|
||||
res["groups"] = { "total" => groups.size, "items" => items }
|
||||
res["rules"] = { "total" => rules.size, "refs" => refs, "types" => rtypes, "targets" => target_order.map { |name| targets[name] } }
|
||||
rescue StandardError => err
|
||||
msg = err.message.to_s
|
||||
if msg.include?("Encrypted file") || msg.include?("age decrypt")
|
||||
res["age"] = true
|
||||
res["need_key"] = true
|
||||
else
|
||||
res["status"] = "error"
|
||||
res["message"] = msg
|
||||
end
|
||||
end
|
||||
puts jdump(res)
|
||||
RUBY
|
||||
Reference in new issue
Block a user