├── AdGuardHome ├── AdGuardHome.yaml ├── install.sh └── sogou.txt ├── FantasqueSansMono ├── FantasqueSansMono-Bold.otf ├── FantasqueSansMono-BoldItalic.otf ├── FantasqueSansMono-Italic.otf └── FantasqueSansMono-Regular.otf ├── ImmortalWrt_opkg.txt ├── README.md ├── SmartDNS ├── all_domains.conf ├── apple_cdn.conf ├── apple_cn.conf ├── china_domains.conf ├── domestic_domains.conf ├── google_cn.conf ├── microsoft_cdn.conf ├── smartdns.conf └── smartdns_s.conf ├── SmartDash ├── README.md ├── app.py ├── install_smartdash.sh └── templates │ └── index.html ├── agh_admin.py ├── bcrypt10.py ├── block.txt ├── china_ip.txt ├── dns.sh ├── doh.py ├── iKuai ├── config.json ├── ikuai-ip-update.py ├── install-iksip.sh └── readme.md ├── miniChat ├── README.md ├── index.html ├── install_minichat_service.sh └── server.py └── unique_ip.txt /AdGuardHome/AdGuardHome.yaml: -------------------------------------------------------------------------------- 1 | http: 2 | pprof: 3 | port: 6060 4 | enabled: false 5 | address: 0.0.0.0:80 6 | session_ttl: 720h 7 | users: 8 | - name: admin 9 | password: $2a$10$0a2kCxxgVNEmBJEWt28F/.BPMz/92vkH5XqOjWTCzOY2EAnmPOuNq 10 | auth_attempts: 5 11 | block_auth_min: 15 12 | http_proxy: "" 13 | language: "" 14 | theme: auto 15 | dns: 16 | bind_hosts: 17 | - 0.0.0.0 18 | port: 53 19 | anonymize_client_ip: false 20 | ratelimit: 0 21 | ratelimit_subnet_len_ipv4: 24 22 | ratelimit_subnet_len_ipv6: 56 23 | ratelimit_whitelist: [] 24 | refuse_any: true 25 | upstream_dns: 26 | - 127.0.0.1:5353 27 | upstream_dns_file: "" 28 | bootstrap_dns: 29 | - 9.9.9.10 30 | - 149.112.112.10 31 | - 2620:fe::10 32 | - 2620:fe::fe:10 33 | fallback_dns: [] 34 | upstream_mode: load_balance 35 | fastest_timeout: 1s 36 | allowed_clients: [] 37 | disallowed_clients: [] 38 | blocked_hosts: 39 | - version.bind 40 | - id.server 41 | - hostname.bind 42 | trusted_proxies: 43 | - 127.0.0.0/8 44 | - ::1/128 45 | cache_size: 0 46 | cache_ttl_min: 0 47 | cache_ttl_max: 0 48 | cache_optimistic: false 49 | bogus_nxdomain: [] 50 | aaaa_disabled: false 51 | enable_dnssec: false 52 | edns_client_subnet: 53 | custom_ip: "" 54 | enabled: false 55 | use_custom: false 56 | max_goroutines: 300 57 | handle_ddr: true 58 | ipset: [] 59 | ipset_file: "" 60 | bootstrap_prefer_ipv6: false 61 | upstream_timeout: 10s 62 | private_networks: [] 63 | use_private_ptr_resolvers: true 64 | local_ptr_upstreams: [] 65 | use_dns64: false 66 | dns64_prefixes: [] 67 | serve_http3: false 68 | use_http3_upstreams: false 69 | serve_plain_dns: true 70 | hostsfile_enabled: true 71 | tls: 72 | enabled: false 73 | server_name: "" 74 | force_https: false 75 | port_https: 443 76 | port_dns_over_tls: 853 77 | port_dns_over_quic: 853 78 | port_dnscrypt: 0 79 | dnscrypt_config_file: "" 80 | allow_unencrypted_doh: false 81 | certificate_chain: "" 82 | private_key: "" 83 | certificate_path: "" 84 | private_key_path: "" 85 | strict_sni_check: false 86 | querylog: 87 | dir_path: "" 88 | ignored: [] 89 | interval: 2160h 90 | size_memory: 1000 91 | enabled: true 92 | file_enabled: true 93 | statistics: 94 | dir_path: "" 95 | ignored: [] 96 | interval: 24h 97 | enabled: true 98 | filters: 99 | - enabled: true 100 | url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt 101 | name: AdGuard DNS filter 102 | id: 1 103 | - enabled: false 104 | url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_2.txt 105 | name: AdAway Default Blocklist 106 | id: 2 107 | whitelist_filters: [] 108 | user_rules: [] 109 | dhcp: 110 | enabled: false 111 | interface_name: "" 112 | local_domain_name: lan 113 | dhcpv4: 114 | gateway_ip: "" 115 | subnet_mask: "" 116 | range_start: "" 117 | range_end: "" 118 | lease_duration: 86400 119 | icmp_timeout_msec: 1000 120 | options: [] 121 | dhcpv6: 122 | range_start: "" 123 | lease_duration: 86400 124 | ra_slaac_only: false 125 | ra_allow_slaac: false 126 | filtering: 127 | blocking_ipv4: "" 128 | blocking_ipv6: "" 129 | blocked_services: 130 | schedule: 131 | time_zone: Local 132 | ids: [] 133 | protection_disabled_until: null 134 | safe_search: 135 | enabled: false 136 | bing: true 137 | duckduckgo: true 138 | ecosia: true 139 | google: true 140 | pixabay: true 141 | yandex: true 142 | youtube: true 143 | blocking_mode: default 144 | parental_block_host: family-block.dns.adguard.com 145 | safebrowsing_block_host: standard-block.dns.adguard.com 146 | rewrites: [] 147 | safe_fs_patterns: 148 | - /opt/AdGuardHome/userfilters/* 149 | safebrowsing_cache_size: 1048576 150 | safesearch_cache_size: 1048576 151 | parental_cache_size: 1048576 152 | cache_time: 30 153 | filters_update_interval: 24 154 | blocked_response_ttl: 10 155 | filtering_enabled: true 156 | parental_enabled: false 157 | safebrowsing_enabled: false 158 | protection_enabled: true 159 | clients: 160 | runtime_sources: 161 | whois: true 162 | arp: true 163 | rdns: true 164 | dhcp: true 165 | hosts: true 166 | persistent: [] 167 | log: 168 | enabled: true 169 | file: "" 170 | max_backups: 0 171 | max_size: 100 172 | max_age: 3 173 | compress: false 174 | local_time: false 175 | verbose: false 176 | os: 177 | group: "" 178 | user: "" 179 | rlimit_nofile: 0 180 | schema_version: 29 181 | -------------------------------------------------------------------------------- /AdGuardHome/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # AdGuard Home Installation Script 4 | 5 | # Exit the script if a pipeline fails (-e), prevent accidental filename 6 | # expansion (-f), and consider undefined variables as errors (-u). 7 | set -e -f -u 8 | 9 | # Function log is an echo wrapper that writes to stderr if the caller 10 | # requested verbosity level greater than 0. Otherwise, it does nothing. 11 | log() { 12 | if [ "$verbose" -gt '0' ] 13 | then 14 | echo "$1" 1>&2 15 | fi 16 | } 17 | 18 | # Function error_exit is an echo wrapper that writes to stderr and stops the 19 | # script execution with code 1. 20 | error_exit() { 21 | echo "$1" 1>&2 22 | 23 | exit 1 24 | } 25 | 26 | # Function usage prints the note about how to use the script. 27 | # 28 | # TODO(e.burkov): Document each option. 29 | usage() { 30 | echo 'install.sh: usage: [-c channel] [-C cpu_type] [-h] [-O os] [-o output_dir]'\ 31 | '[-r|-R] [-u|-U] [-v|-V]' 1>&2 32 | 33 | exit 2 34 | } 35 | 36 | # Function maybe_sudo runs passed command with root privileges if use_sudo isn't 37 | # equal to 0. 38 | # 39 | # TODO(e.burkov): Use everywhere the sudo_cmd isn't quoted. 40 | maybe_sudo() { 41 | if [ "$use_sudo" -eq 0 ] 42 | then 43 | "$@" 44 | else 45 | "$sudo_cmd" "$@" 46 | fi 47 | } 48 | 49 | # Function is_command checks if the command exists on the machine. 50 | is_command() { 51 | command -v "$1" >/dev/null 2>&1 52 | } 53 | 54 | # Function is_little_endian checks if the CPU is little-endian. 55 | # 56 | # See https://serverfault.com/a/163493/267530. 57 | is_little_endian() { 58 | # The ASCII character "I" has the octal code of 111. In the two-byte octal 59 | # display mode (-o), hexdump will print it either as "000111" on a little 60 | # endian system or as a "111000" on a big endian one. Return the sixth 61 | # character to compare it against the number '1'. 62 | # 63 | # Do not use echo -n, because its behavior in the presence of the -n flag is 64 | # explicitly implementation-defined in POSIX. Use hexdump instead of od, 65 | # because OpenWrt and its derivatives have the former but not the latter. 66 | is_little_endian_result="$( 67 | printf 'I'\ 68 | | hexdump -o\ 69 | | awk '{ print substr($2, 6, 1); exit; }' 70 | )" 71 | readonly is_little_endian_result 72 | 73 | [ "$is_little_endian_result" -eq '1' ] 74 | } 75 | 76 | # Function check_required checks if the required software is available on the 77 | # machine. The required software: 78 | # 79 | # unzip (macOS) / tar (other unixes) 80 | # 81 | # curl/wget are checked in function configure. 82 | check_required() { 83 | required_darwin="unzip" 84 | required_unix="tar" 85 | readonly required_darwin required_unix 86 | 87 | case "$os" 88 | in 89 | ('freebsd'|'linux'|'openbsd') 90 | required="$required_unix" 91 | ;; 92 | ('darwin') 93 | required="$required_darwin" 94 | ;; 95 | (*) 96 | # Generally shouldn't happen, since the OS has already been validated. 97 | error_exit "unsupported operating system: '$os'" 98 | ;; 99 | esac 100 | readonly required 101 | 102 | # Don't use quotes to get word splitting. 103 | for cmd in $required 104 | do 105 | log "checking $cmd" 106 | if ! is_command "$cmd" 107 | then 108 | log "the full list of required software: [$required]" 109 | 110 | error_exit "$cmd is required to install AdGuard Home via this script" 111 | fi 112 | done 113 | } 114 | 115 | # Function check_out_dir requires the output directory to be set and exist. 116 | check_out_dir() { 117 | if [ "$out_dir" = '' ] 118 | then 119 | error_exit 'output directory should be presented' 120 | fi 121 | 122 | if ! [ -d "$out_dir" ] 123 | then 124 | log "$out_dir directory will be created" 125 | fi 126 | } 127 | 128 | # Function parse_opts parses the options list and validates it's combinations. 129 | parse_opts() { 130 | while getopts "C:c:hO:o:rRuUvV" opt "$@" 131 | do 132 | case "$opt" 133 | in 134 | (C) 135 | cpu="$OPTARG" 136 | ;; 137 | (c) 138 | channel="$OPTARG" 139 | ;; 140 | (h) 141 | usage 142 | ;; 143 | (O) 144 | os="$OPTARG" 145 | ;; 146 | (o) 147 | out_dir="$OPTARG" 148 | ;; 149 | (R) 150 | reinstall='0' 151 | ;; 152 | (U) 153 | uninstall='0' 154 | ;; 155 | (r) 156 | reinstall='1' 157 | ;; 158 | (u) 159 | uninstall='1' 160 | ;; 161 | (V) 162 | verbose='0' 163 | ;; 164 | (v) 165 | verbose='1' 166 | ;; 167 | (*) 168 | log "bad option $OPTARG" 169 | 170 | usage 171 | ;; 172 | esac 173 | done 174 | 175 | if [ "$uninstall" -eq '1' ] && [ "$reinstall" -eq '1' ] 176 | then 177 | error_exit 'the -r and -u options are mutually exclusive' 178 | fi 179 | } 180 | 181 | # Function set_channel sets the channel if needed and validates the value. 182 | set_channel() { 183 | # Validate. 184 | case "$channel" 185 | in 186 | ('development'|'edge'|'beta'|'release') 187 | # All is well, go on. 188 | ;; 189 | (*) 190 | error_exit \ 191 | "invalid channel '$channel' 192 | supported values are 'development', 'edge', 'beta', and 'release'" 193 | ;; 194 | esac 195 | 196 | # Log. 197 | log "channel: $channel" 198 | } 199 | 200 | # Function set_os sets the os if needed and validates the value. 201 | set_os() { 202 | # Set if needed. 203 | if [ "$os" = '' ] 204 | then 205 | os="$( uname -s )" 206 | case "$os" 207 | in 208 | ('Darwin') 209 | os='darwin' 210 | ;; 211 | ('FreeBSD') 212 | os='freebsd' 213 | ;; 214 | ('Linux') 215 | os='linux' 216 | ;; 217 | ('OpenBSD') 218 | os='openbsd' 219 | ;; 220 | (*) 221 | error_exit "unsupported operating system: '$os'" 222 | ;; 223 | esac 224 | fi 225 | 226 | # Validate. 227 | case "$os" 228 | in 229 | ('darwin'|'freebsd'|'linux'|'openbsd') 230 | # All right, go on. 231 | ;; 232 | (*) 233 | error_exit "unsupported operating system: '$os'" 234 | ;; 235 | esac 236 | 237 | # Log. 238 | log "operating system: $os" 239 | } 240 | 241 | # Function set_cpu sets the cpu if needed and validates the value. 242 | set_cpu() { 243 | # Set if needed. 244 | if [ "$cpu" = '' ] 245 | then 246 | cpu="$( uname -m )" 247 | case "$cpu" 248 | in 249 | ('x86_64'|'x86-64'|'x64'|'amd64') 250 | cpu='amd64' 251 | ;; 252 | ('i386'|'i486'|'i686'|'i786'|'x86') 253 | cpu='386' 254 | ;; 255 | ('armv5l') 256 | cpu='armv5' 257 | ;; 258 | ('armv6l') 259 | cpu='armv6' 260 | ;; 261 | ('armv7l' | 'armv8l') 262 | cpu='armv7' 263 | ;; 264 | ('aarch64'|'arm64') 265 | cpu='arm64' 266 | ;; 267 | ('mips'|'mips64') 268 | if is_little_endian 269 | then 270 | cpu="${cpu}le" 271 | fi 272 | cpu="${cpu}_softfloat" 273 | ;; 274 | (*) 275 | error_exit "unsupported cpu type: $cpu" 276 | ;; 277 | esac 278 | fi 279 | 280 | # Validate. 281 | case "$cpu" 282 | in 283 | ('amd64'|'386'|'armv5'|'armv6'|'armv7'|'arm64') 284 | # All right, go on. 285 | ;; 286 | ('mips64le_softfloat'|'mips64_softfloat'|'mipsle_softfloat'|'mips_softfloat') 287 | # That's right too. 288 | ;; 289 | (*) 290 | error_exit "unsupported cpu type: $cpu" 291 | ;; 292 | esac 293 | 294 | # Log. 295 | log "cpu type: $cpu" 296 | } 297 | 298 | # Function fix_darwin performs some configuration changes for macOS if needed. 299 | # 300 | # TODO(a.garipov): Remove after the final v0.107.0 release. 301 | # 302 | # See https://github.com/AdguardTeam/AdGuardHome/issues/2443. 303 | fix_darwin() { 304 | if [ "$os" != 'darwin' ] 305 | then 306 | return 0 307 | fi 308 | 309 | # Set the package extension. 310 | pkg_ext='zip' 311 | 312 | # It is important to install AdGuard Home into the /Applications directory 313 | # on macOS. Otherwise, it may grant not enough privileges to the AdGuard 314 | # Home. 315 | out_dir='/Applications' 316 | } 317 | 318 | # Function fix_freebsd performs some fixes to make it work on FreeBSD. 319 | fix_freebsd() { 320 | if ! [ "$os" = 'freebsd' ] 321 | then 322 | return 0 323 | fi 324 | 325 | rcd='/usr/local/etc/rc.d' 326 | readonly rcd 327 | 328 | if ! [ -d "$rcd" ] 329 | then 330 | mkdir "$rcd" 331 | fi 332 | } 333 | 334 | # download_curl uses curl(1) to download a file. The first argument is the URL. 335 | # The second argument is optional and is the output file. 336 | download_curl() { 337 | curl_output="${2:-}" 338 | if [ "$curl_output" = '' ] 339 | then 340 | curl -L -S -s "$1" 341 | else 342 | curl -L -S -o "$curl_output" -s "$1" 343 | fi 344 | } 345 | 346 | # download_wget uses wget(1) to download a file. The first argument is the URL. 347 | # The second argument is optional and is the output file. 348 | download_wget() { 349 | wget_output="${2:--}" 350 | 351 | wget --no-verbose -O "$wget_output" "$1" 352 | } 353 | 354 | # download_fetch uses fetch(1) to download a file. The first argument is the 355 | # URL. The second argument is optional and is the output file. 356 | download_fetch() { 357 | fetch_output="${2:-}" 358 | if [ "$fetch_output" = '' ] 359 | then 360 | fetch -o '-' "$1" 361 | else 362 | fetch -o "$fetch_output" "$1" 363 | fi 364 | } 365 | 366 | # Function set_download_func sets the appropriate function for downloading 367 | # files. 368 | set_download_func() { 369 | if is_command 'curl' 370 | then 371 | # Go on and use the default, download_curl. 372 | return 0 373 | elif is_command 'wget' 374 | then 375 | download_func='download_wget' 376 | elif is_command 'fetch' 377 | then 378 | download_func='download_fetch' 379 | else 380 | error_exit "either curl or wget is required to install AdGuard Home via this script" 381 | fi 382 | } 383 | 384 | # Function set_sudo_cmd sets the appropriate command to run a command under 385 | # superuser privileges. 386 | set_sudo_cmd() { 387 | case "$os" 388 | in 389 | ('openbsd') 390 | sudo_cmd='doas' 391 | ;; 392 | ('darwin'|'freebsd'|'linux') 393 | # Go on and use the default, sudo. 394 | ;; 395 | (*) 396 | error_exit "unsupported operating system: '$os'" 397 | ;; 398 | esac 399 | } 400 | 401 | # Function configure sets the script's configuration. 402 | configure() { 403 | set_channel 404 | set_os 405 | set_cpu 406 | fix_darwin 407 | set_download_func 408 | set_sudo_cmd 409 | check_out_dir 410 | 411 | pkg_name="AdGuardHome_${os}_${cpu}.${pkg_ext}" 412 | url="https://static.adtidy.org/adguardhome/${channel}/${pkg_name}" 413 | agh_dir="${out_dir}/AdGuardHome" 414 | readonly pkg_name url agh_dir 415 | 416 | log "AdGuard Home will be installed into $agh_dir" 417 | } 418 | 419 | # Function is_root checks for root privileges to be granted. 420 | is_root() { 421 | if [ "$( id -u )" -eq '0' ] 422 | then 423 | log 'script is executed with root privileges' 424 | 425 | return 0 426 | fi 427 | 428 | if is_command "$sudo_cmd" 429 | then 430 | log 'note that AdGuard Home requires root privileges to install using this script' 431 | 432 | return 1 433 | fi 434 | 435 | error_exit \ 436 | 'root privileges are required to install AdGuard Home using this script 437 | please, restart it with root privileges' 438 | } 439 | 440 | # Function rerun_with_root downloads the script, runs it with root privileges, 441 | # and exits the current script. It passes the necessary configuration of the 442 | # current script to the child script. 443 | # 444 | # TODO(e.burkov): Try to avoid restarting. 445 | rerun_with_root() { 446 | script_url=\ 447 | 'https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh' 448 | readonly script_url 449 | 450 | r='-R' 451 | if [ "$reinstall" -eq '1' ] 452 | then 453 | r='-r' 454 | fi 455 | 456 | u='-U' 457 | if [ "$uninstall" -eq '1' ] 458 | then 459 | u='-u' 460 | fi 461 | 462 | v='-V' 463 | if [ "$verbose" -eq '1' ] 464 | then 465 | v='-v' 466 | fi 467 | 468 | readonly r u v 469 | 470 | log 'restarting with root privileges' 471 | 472 | # Group curl/wget together with an echo, so that if the former fails before 473 | # producing any output, the latter prints an exit command for the following 474 | # shell to execute to prevent it from getting an empty input and exiting 475 | # with a zero code in that case. 476 | { "$download_func" "$script_url" || echo 'exit 1'; }\ 477 | | $sudo_cmd sh -s -- -c "$channel" -C "$cpu" -O "$os" -o "$out_dir" "$r" "$u" "$v" 478 | 479 | # Exit the script. Since if the code of the previous pipeline is non-zero, 480 | # the execution won't reach this point thanks to set -e, exit with zero. 481 | exit 0 482 | } 483 | 484 | # Function download downloads the file from the URL and saves it to the 485 | # specified filepath. 486 | download() { 487 | log "downloading package from $url -> $pkg_name" 488 | 489 | if ! "$download_func" "$url" "$pkg_name" 490 | then 491 | error_exit "cannot download the package from $url into $pkg_name" 492 | fi 493 | 494 | log "successfully downloaded $pkg_name" 495 | } 496 | 497 | # Function unpack unpacks the passed archive depending on it's extension. 498 | unpack() { 499 | log "unpacking package from $pkg_name into $out_dir" 500 | if ! mkdir -m 0700 -p "$out_dir" 501 | then 502 | error_exit "cannot create directory $out_dir" 503 | fi 504 | 505 | case "$pkg_ext" 506 | in 507 | ('zip') 508 | unzip "$pkg_name" -d "$out_dir" 509 | ;; 510 | ('tar.gz') 511 | tar -C "$out_dir" -f "$pkg_name" -x -z 512 | ;; 513 | (*) 514 | error_exit "unexpected package extension: '$pkg_ext'" 515 | ;; 516 | esac 517 | 518 | log "successfully unpacked, contents: $( echo; ls -l -A "$agh_dir" )" 519 | 520 | rm "$pkg_name" 521 | } 522 | 523 | # Function handle_existing detects the existing AGH installation and takes care 524 | # of removing it if needed. 525 | handle_existing() { 526 | if ! [ -d "$agh_dir" ] 527 | then 528 | log 'no need to uninstall' 529 | 530 | if [ "$uninstall" -eq '1' ] 531 | then 532 | exit 0 533 | fi 534 | 535 | return 0 536 | fi 537 | 538 | if [ "$( ls -1 -A "$agh_dir" )" != '' ] 539 | then 540 | log 'the existing AdGuard Home installation is detected' 541 | 542 | if [ "$reinstall" -ne '1' ] && [ "$uninstall" -ne '1' ] 543 | then 544 | error_exit \ 545 | "to reinstall/uninstall the AdGuard Home using this script specify one of the '-r' or '-u' flags" 546 | fi 547 | 548 | # TODO(e.burkov): Remove the stop once v0.107.1 released. 549 | if ( cd "$agh_dir" && ! ./AdGuardHome -s stop || ! ./AdGuardHome -s uninstall ) 550 | then 551 | # It doesn't terminate the script since it is possible 552 | # that AGH just not installed as service but appearing 553 | # in the directory. 554 | log "cannot uninstall AdGuard Home from $agh_dir" 555 | fi 556 | 557 | rm -r "$agh_dir" 558 | 559 | log 'AdGuard Home was successfully uninstalled' 560 | fi 561 | 562 | if [ "$uninstall" -eq '1' ] 563 | then 564 | exit 0 565 | fi 566 | } 567 | 568 | # Function install_service tries to install AGH as service. 569 | install_service() { 570 | # Installing the service as root is required at least on FreeBSD. 571 | use_sudo='0' 572 | if [ "$os" = 'freebsd' ] 573 | then 574 | use_sudo='1' 575 | fi 576 | 577 | if ( cd "$agh_dir" && maybe_sudo ./AdGuardHome -s install ) 578 | then 579 | return 0 580 | fi 581 | 582 | log "installation failed, removing $agh_dir" 583 | 584 | rm -r "$agh_dir" 585 | 586 | # Some devices detected to have armv7 CPU face the compatibility 587 | # issues with actual armv7 builds. We should try to install the 588 | # armv5 binary instead. 589 | # 590 | # See https://github.com/AdguardTeam/AdGuardHome/issues/2542. 591 | if [ "$cpu" = 'armv7' ] 592 | then 593 | cpu='armv5' 594 | reinstall='1' 595 | 596 | log "trying to use $cpu cpu" 597 | 598 | rerun_with_root 599 | fi 600 | 601 | error_exit 'cannot install AdGuardHome as a service' 602 | } 603 | 604 | 605 | 606 | # Entrypoint 607 | 608 | # Set default values of configuration variables. 609 | channel='release' 610 | reinstall='0' 611 | uninstall='0' 612 | verbose='0' 613 | cpu='' 614 | os='' 615 | out_dir='/opt' 616 | pkg_ext='tar.gz' 617 | download_func='download_curl' 618 | sudo_cmd='sudo' 619 | 620 | parse_opts "$@" 621 | 622 | echo 'starting AdGuard Home installation script' 623 | 624 | configure 625 | check_required 626 | 627 | if ! is_root 628 | then 629 | rerun_with_root 630 | fi 631 | # Needs rights. 632 | fix_freebsd 633 | 634 | handle_existing 635 | 636 | download 637 | unpack 638 | 639 | install_service 640 | 641 | echo "\ 642 | AdGuard Home is now installed and running 643 | you can control the service status with the following commands: 644 | $sudo_cmd ${agh_dir}/AdGuardHome -s start|stop|restart|status|install|uninstall" 645 | -------------------------------------------------------------------------------- /AdGuardHome/sogou.txt: -------------------------------------------------------------------------------- 1 | ||this_ruleset_is_made_by_sukkaw.ruleset.skk.moe 2 | ||otheve.beacon.qq.com 3 | ||oth.str.mdt.qq.com 4 | ||get.sogou.com 5 | ||shouji.sogou.com 6 | ||account.sogou.com 7 | ||pinyin.sogou.com 8 | ||ime.sogou.com 9 | ||macime.sogou.com 10 | ||sginput.qq.com 11 | User-Agent: SogouInput 12 | User-Agent: com.sogou.sogouinput.BaseKeyboard 13 | -------------------------------------------------------------------------------- /FantasqueSansMono/FantasqueSansMono-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LidaoNote/OpenCode/78571de9f359f94e9759fd3945d8c33f8a5f6898/FantasqueSansMono/FantasqueSansMono-Bold.otf -------------------------------------------------------------------------------- /FantasqueSansMono/FantasqueSansMono-BoldItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LidaoNote/OpenCode/78571de9f359f94e9759fd3945d8c33f8a5f6898/FantasqueSansMono/FantasqueSansMono-BoldItalic.otf -------------------------------------------------------------------------------- /FantasqueSansMono/FantasqueSansMono-Italic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LidaoNote/OpenCode/78571de9f359f94e9759fd3945d8c33f8a5f6898/FantasqueSansMono/FantasqueSansMono-Italic.otf -------------------------------------------------------------------------------- /FantasqueSansMono/FantasqueSansMono-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LidaoNote/OpenCode/78571de9f359f94e9759fd3945d8c33f8a5f6898/FantasqueSansMono/FantasqueSansMono-Regular.otf -------------------------------------------------------------------------------- /ImmortalWrt_opkg.txt: -------------------------------------------------------------------------------- 1 | src/gz immortalwrt_core https://downloads.immortalwrt.org/releases/23.05.3/targets/x86/64/packages 2 | src/gz immortalwrt_base https://downloads.immortalwrt.org/releases/23.05.3/packages/x86_64/base 3 | src/gz immortalwrt_luci https://downloads.immortalwrt.org/releases/23.05.3/packages/x86_64/luci 4 | src/gz immortalwrt_packages https://downloads.immortalwrt.org/releases/23.05.3/packages/x86_64/packages 5 | src/gz immortalwrt_routing https://downloads.immortalwrt.org/releases/23.05.3/packages/x86_64/routing 6 | src/gz immortalwrt_telephony https://downloads.immortalwrt.org/releases/23.05.3/packages/x86_64/telephony 7 | 8 | src/gz immortalwrt_core https://mirrors.vsean.net/openwrt/releases/23.05.3/targets/x86/64/packages 9 | src/gz immortalwrt_base https://mirrors.vsean.net/openwrt/releases/23.05.3/packages/x86_64/base 10 | src/gz immortalwrt_luci https://mirrors.vsean.net/openwrt/releases/23.05.3/packages/x86_64/luci 11 | src/gz immortalwrt_packages https://mirrors.vsean.net/openwrt/releases/23.05.3/packages/x86_64/packages 12 | src/gz immortalwrt_routing https://mirrors.vsean.net/openwrt/releases/23.05.3/packages/x86_64/routing 13 | src/gz immortalwrt_telephony https://mirrors.vsean.net/openwrt/releases/23.05.3/packages/x86_64/telephony -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenCode 仓库说明文档 2 | 3 | ## 引言 4 | 5 | OpenCode 仓库由离岛维护,关联 [YouTube 频道](https://youtube.com/@Lidao),是一个集自动化脚本、实用工具和示例代码于一体的资源库,旨在帮助用户提升效率并学习编程技术。 6 | 7 | ## 仓库结构 8 | 9 | 仓库包含以下目录和文件: 10 | 11 | ### 目录 12 | 13 | | 目录名称 | 描述 | 14 | |---------------------|----------------------------------------------------------------------| 15 | | AdGuardHome | 包含与 AdGuardHome 相关的配置文件和脚本。 | 16 | | FantastiqueSansMono | 包含 FantastiqueSansMono 字体文件相关资源,适用于界面设计或文档。 | 17 | | SmartDNS | 包含 SmartDNS 相关的配置文件或脚本,用于智能 DNS 解析和网络优化。 | 18 | | SmartDash | 包含 SmartDash 相关的工具和脚本,这是一个简单的 SmartDNS 仪表盘。 | 19 | | miniChat | 一个小型聊天应用的代码,用于学习或测试通信功能。 | 20 | 21 | ### 文件 22 | 23 | | 文件名称 | 描述 | 24 | |---------------------|----------------------------------------------------------------------| 25 | | ImmortalWrt_opkg.txt| ImmortalWrt 系统的 opkg 包列表,包含国外和国内源地址。 | 26 | | README.md | 仓库的主说明文档,提供基本信息和使用指南。 | 27 | | agh_admin.py | AdGuardHome 管理脚本,用于设置 AdGuardHome 的管理员账号和密码。 | 28 | | bcrypt10.py | 使用 bcrypt 进行加密的 Python 脚本,能进行 10 次密码哈希。 | 29 | | block.txt | 黑名单文件,包含国内屏蔽的 IP 或域名。 | 30 | | china_ip.txt | 中国 IP 地址列表。 | 31 | | dns.sh | DNS 相关脚本,用于一键 SmartDNS / AdGuardHome 安装和卸载。 | 32 | | doh.py | DNS over HTTPS (DoH) 相关脚本,用于加密 DNS 查询。 | 33 | | unique_ip.txt | DNS 污染后的 IP 地址返回列表,来源是维基百科。 | 34 | 35 | ## 贡献指南 36 | 37 | 欢迎为 OpenCode 仓库贡献代码或建议。用户可以通过以下方式参与: 38 | 39 | - 通过 [GitHub Issues](https://github.com/LidaoNote/OpenCode/issues) 报告问题或提出改进建议。 40 | - 提交包含新脚本、工具或示例代码的拉取请求。 41 | - 提供功能请求或反馈。 42 | 43 | 在贡献之前,请查看仓库的贡献指南(如果存在)或联系维护者以获取指导。 44 | 45 | ## 许可证 46 | 47 | OpenCode 仓库中的代码允许自由修改和分发,但如果用于商业用途,需获得作者的明确授权。请通过 [GitHub Issues](https://github.com/LidaoNote/OpenCode/issues) 或 YouTube 频道关联的联系方式与作者沟通授权事宜。 48 | 49 | ## 引用 50 | 51 | - [OpenCode GitHub 仓库](https://github.com/LidaoNote/OpenCode) 52 | - [离岛 YouTube 频道](https://youtube.com/@Lidao) -------------------------------------------------------------------------------- /SmartDNS/apple_cdn.conf: -------------------------------------------------------------------------------- 1 | a1.mzstatic.com 2 | a2.mzstatic.com 3 | a3.mzstatic.com 4 | a4.mzstatic.com 5 | a5.mzstatic.com 6 | adcdownload.apple.com.akadns.net 7 | adcdownload.apple.com 8 | amp-api.media.apple.com 9 | amp-api-updates.apps.apple.com 10 | api-p-ap-c.smoot.apple.com 11 | api-p-ap-d.smoot.apple.com 12 | api-p-ap-e.smoot.apple.com 13 | app-site-association.cdn-apple.com 14 | appldnld.apple.com 15 | appldnld.g.aaplimg.com 16 | appleid.cdn-apple.com 17 | apps.apple.com 18 | apps.mzstatic.com 19 | bag-cdn.itunes-apple.com.akadns.net 20 | cdn-cn1.apple-mapkit.com 21 | cdn-cn2.apple-mapkit.com 22 | cdn-cn3.apple-mapkit.com 23 | cdn-cn4.apple-mapkit.com 24 | cdn.apple-mapkit.com 25 | cdn1.apple-mapkit.com 26 | cdn2.apple-mapkit.com 27 | cdn3.apple-mapkit.com 28 | cdn4.apple-mapkit.com 29 | cds-cdn.v.aaplimg.com 30 | cds.apple.com.akadns.net 31 | cds.apple.com 32 | cdsassets.apple.com 33 | cl1-cdn.origin-apple.com.akadns.net 34 | cl1.apple.com 35 | cl2-cn.apple.com 36 | cl2.apple.com 37 | cl3-cdn.origin-apple.com.akadns.net 38 | cl3.apple.com 39 | cl4-cdn.origin-apple.com.akadns.net 40 | cl4-cn.apple.com 41 | cl4.apple.com 42 | cl5-cdn.origin-apple.com.akadns.net 43 | cl5.apple.com 44 | clientflow.apple.com.akadns.net 45 | clientflow.apple.com 46 | cn-smp-paymentservices.apple.com 47 | configuration.apple.com.akadns.net 48 | configuration.apple.com 49 | crl.apple.com 50 | cstat.apple.com 51 | cstat.cdn-apple.com 52 | dd-cdn.origin-apple.com.akadns.net 53 | dejavu.apple.com 54 | download.developer.apple.com 55 | experiments.apple.com 56 | gs-loc-cn.apple.com 57 | gs-loc.apple.com 58 | gsp10-ssl-cn.ls.apple.com 59 | gsp12-cn.ls.apple.com 60 | gsp13-cn.ls.apple.com 61 | gsp4-cn.ls.apple.com.edgekey.net.globalredir.akadns.net 62 | gsp4-cn.ls.apple.com.edgekey.net 63 | gsp4-cn.ls.apple.com 64 | gsp5-cn.ls.apple.com 65 | gsp85-cn-ssl.ls.apple.com 66 | gspe19-2-cn-ssl.ls.apple.com 67 | gspe19-2-cn-ssl.ls-apple.com.akadns.net 68 | gspe19-cn-ssl.ls.apple.com 69 | gspe19-cn.ls-apple.com.akadns.net 70 | gspe19-cn.ls.apple.com 71 | gspe21-ssl.ls.apple.com 72 | gspe21.ls.apple.com 73 | gspe35-ssl.ls.apple.com 74 | gspe79-cn-ssl.ls.apple.com 75 | guzzoni-apple-com.v.aaplimg.com 76 | guzzoni.apple.com 77 | guzzoni.smoot.apple.com 78 | iadsdk.apple.com 79 | icloud-cdn.icloud.com.akadns.net 80 | icloud.cdn-apple.com 81 | images.apple.com.akadns.net 82 | images.apple.com.edgekey.net.globalredir.akadns.net 83 | images.apple.com 84 | init-kt.apple.com 85 | init-p01md-lb.push-apple.com.akadns.net 86 | init-p01md.apple.com 87 | init-p01st-lb.push-apple.com.akadns.net 88 | init-p01st.push.apple.com 89 | init-s01st-lb.push-apple.com.akadns.net 90 | init-s01st.push.apple.com 91 | init.ess.apple.com 92 | iosapps.itunes.g.aaplimg.com 93 | ipcdn.apple.com 94 | iphone-ld.apple.com 95 | iphone-ld.origin-apple.com.akadns.net 96 | is1-ssl.mzstatic.com 97 | is1.mzstatic.com 98 | is2-ssl.mzstatic.com 99 | is2.mzstatic.com 100 | is3-ssl.mzstatic.com 101 | is3.mzstatic.com 102 | is4-ssl.mzstatic.com 103 | is4.mzstatic.com 104 | is5-ssl.mzstatic.com 105 | is5.mzstatic.com 106 | is-ssl.mzstatic.com-cn-lb.itunes-apple.com.akadns.net 107 | itunes-apple.com.akadns.net 108 | itunes.apple.com 109 | itunesconnect.apple.com 110 | mesu-cdn.apple.com.akadns.net 111 | mesu-china.apple.com.akadns.net 112 | mesu.apple.com 113 | ml.cdn-apple.com 114 | music.apple.com 115 | ocsp-lb.apple.com.akadns.net 116 | ocsp2-lb.apple.com.akadns.net 117 | ocsp.apple.com 118 | ocsp2.apple.com 119 | oscdn.apple.com 120 | oscdn.origin-apple.com.akadns.net 121 | osxapps.itunes.g.aaplimg.com 122 | pancake.apple.com 123 | pancake.cdn-apple.com.akadns.net 124 | pba0.apple.com 125 | probe.siri.apple.com 126 | prod-support.apple-support.akadns.net 127 | publicassets.cdn-apple.com 128 | reserve-prime.apple.com 129 | s.mzstatic.com 130 | seed-sequoia.siri.apple.com 131 | seed-swallow.siri.apple.com 132 | seed.siri.apple.com 133 | sequoia.apple.com 134 | sh-pod2-smp-device.apple.com 135 | shazam-insights.cdn-apple.com 136 | smp-device-content.apple.com 137 | static.gc.apple.com 138 | stocks-sparkline-lb.apple.com.akadns.net 139 | stocks-sparkline.apple.com 140 | store.apple.com.edgekey.net.globalredir.akadns.net 141 | store.apple.com.edgekey.net 142 | store.apple.com 143 | store.storeimages.apple.com.akadns.net 144 | store.storeimages.cdn-apple.com 145 | support-china.apple-support.akadns.net 146 | support.apple.com 147 | swallow-apple-com.v.aaplimg.com 148 | swallow.apple.com 149 | swcatalog-cdn.apple.com.akadns.net 150 | swcatalog.apple.com 151 | swcdn.apple.com 152 | swcdn.g.aaplimg.com 153 | swdist.apple.com.akadns.net 154 | swdist.apple.com 155 | swscan-cdn.apple.com.akadns.net 156 | swscan.apple.com 157 | sylvan.apple.com 158 | tj-pod1-smp-device.apple.com 159 | updates-http.cdn-apple.com.akadns.net 160 | updates-http.cdn-apple.com 161 | updates.cdn-apple.com 162 | valid.apple.com 163 | valid.origin-apple.com.akadns.net 164 | weather-data.apple.com.akadns.net 165 | weather-data.apple.com 166 | weather-map.apple.com 167 | weather-map2.apple.com 168 | weatherkit.apple.com 169 | www.apple.com.edgekey.net.globalredir.akadns.net 170 | www.apple.com.edgekey.net 171 | www.apple.com 172 | xp.apple.com -------------------------------------------------------------------------------- /SmartDNS/apple_cn.conf: -------------------------------------------------------------------------------- 1 | gspe19-cn-ssl.ls.apple.com 2 | cn-ssl.ls.apple.com 3 | cn.ls.apple.com 4 | cn.apple.com 5 | icloud.com.cn 6 | apple.com.cn -------------------------------------------------------------------------------- /SmartDNS/domestic_domains.conf: -------------------------------------------------------------------------------- 1 | 10.in-addr.arpa 2 | 10010.com 3 | 10086.cn 4 | 111.com 5 | 115.com 6 | 115cdn.com 7 | 115cdn.net 8 | 115img.com 9 | 12306.cn 10 | 12306.com 11 | 123pan.com 12 | 126.com 13 | 126.net 14 | 127.com 15 | 127.net 16 | 139.com 17 | 16.172.in-addr.arpa 18 | 163.com 19 | 163jiasu.com 20 | 163yun.com 21 | 166.com 22 | 166.net 23 | 168.192.in-addr.arpa 24 | 1688.com 25 | 17.172.in-addr.arpa 26 | 178.com 27 | 18.172.in-addr.arpa 28 | 188.com 29 | 189.cn 30 | 19.172.in-addr.arpa 31 | 1905.com 32 | 20.172.in-addr.arpa 33 | 21.172.in-addr.arpa 34 | 2144.cn 35 | 21cn.com 36 | 22.172.in-addr.arpa 37 | 22.cn 38 | 2288.org 39 | 23.172.in-addr.arpa 40 | 24.172.in-addr.arpa 41 | 25.172.in-addr.arpa 42 | 254.169.in-addr.arpa 43 | 25pp.com 44 | 26.172.in-addr.arpa 45 | 263.net 46 | 27.172.in-addr.arpa 47 | 28.172.in-addr.arpa 48 | 29.172.in-addr.arpa 49 | 3.cn 50 | 30.172.in-addr.arpa 51 | 31.172.in-addr.arpa 52 | 321fenx.com 53 | 3322.net 54 | 3322.org 55 | 360.cn 56 | 360.com 57 | 360.net 58 | 360buyimg.com 59 | 360doc.com 60 | 360in.com 61 | 360kuai.com 62 | 360os.com 63 | 360safe.com 64 | 360tpcdn.com 65 | 360webcache.com 66 | 36kr.com 67 | 36krcdn.com 68 | 39.net 69 | 3dmgame.com 70 | 4399.com 71 | 51cto.com 72 | 51ctocdn.cn 73 | 52pt.site 74 | 52tesla.com 75 | 56.com 76 | 58.com 77 | 6.cn 78 | 6600.org 79 | 66law.cn 80 | 71.am.com 81 | 7766.org 82 | 8686c.com 83 | 88.com 84 | 8800.org 85 | 8866.org 86 | 88y.cn 87 | 91.com 88 | 95516.com 89 | 95588.com 90 | 9966.org 91 | 9game.cn 92 | 9xiu.com 93 | abchina.com 94 | acfun.cn 95 | acg.rip 96 | acg.tv 97 | acgvideo.com 98 | acm.org 99 | addlink.cn 100 | aicdn.com 101 | aicoinstorge.com 102 | aidoru-online.me 103 | air-matters.com 104 | air-matters.io 105 | aixifan.com 106 | ali213.net 107 | aliapp.org 108 | alibaba-inc.com 109 | alibaba.com 110 | alibabachengdun.com 111 | alibabadns.com 112 | alibabausercontent.com 113 | alicdn.com 114 | alicloudccp.com 115 | alidns.com 116 | aligames.com 117 | aliimg.com 118 | alikunlun.com 119 | alikunlun.net 120 | alios.cn 121 | alipan.com 122 | alipay.cn 123 | alipay.com 124 | alipayobjects.com 125 | aliyun.com 126 | aliyuncs.com 127 | aliyundrive.com 128 | aliyundrive.net 129 | alpharatio.cc 130 | amap.com 131 | animebytes.tv 132 | anjuke.com 133 | anthelion.me 134 | anticheatexpert.com 135 | antpcdn.com 136 | appsimg.com 137 | aqara.cn 138 | asiancinema.me 139 | assets.msn.cn 140 | assets1.xboxlive.cn 141 | assets2.xboxlive.cn 142 | asusrouter.com 143 | aterm.me 144 | authing.cn 145 | authing.co 146 | autonavi.com 147 | avgv.cc 148 | avistaz.to 149 | awesome-hd.me 150 | awtmt.com 151 | b23.tv 152 | baidu.cn 153 | baidu.com 154 | baidubce.com 155 | baidubcr.com 156 | baiducontent.com 157 | baidupcs.com 158 | baidustatic.com 159 | baike.com 160 | baishancdnx.cn 161 | baixing.com 162 | baixing.net 163 | baomitu.com 164 | battle.net 165 | bcebos.com 166 | bcelive.com 167 | bdatu.com 168 | bdimg.com 169 | bdstatic.com 170 | bdydns.com 171 | beitai.pt 172 | bendibao.com 173 | benghuai.com 174 | beyond-hd.me 175 | bh3.com 176 | bibliotik.me 177 | biliapi.com 178 | biliapi.net 179 | bilibili.cn 180 | bilibili.com 181 | bilicdn1.com 182 | bilicomic.com 183 | bilicomics.com 184 | biligame.com 185 | biligame.net 186 | biliimg.com 187 | bilivideo.cn 188 | bilivideo.com 189 | bilivideo.net 190 | blackwell-synergy.com 191 | blizzard.com 192 | blutopia.xyz 193 | blzstatic.cn 194 | boc.cn 195 | bosszhipin.com 196 | broadcasthe.net 197 | bt.byr.cn 198 | bt.neu6.edu.cn 199 | btschool.club 200 | bulicdn2.com 201 | byteacctimg.com 202 | bytecdn.cn 203 | bytecdntp.com 204 | byted-static.com 205 | bytedance.com 206 | bytednsdoc.com 207 | bytegoofy.com 208 | byteimg.com 209 | bytetcc.com 210 | c-ctrip.com 211 | c-t.work 212 | cabdirect.org 213 | cailianpress.com 214 | cainiao.com 215 | cainiaoyizhan.com 216 | caiyunapp.com 217 | camera360.com 218 | camscanner.com 219 | captive.apple.com 220 | ccb.com 221 | ccfbits.org 222 | ccgslb.com 223 | ccgslb.net 224 | ccrgt.com 225 | cctv.cn 226 | cctv.com 227 | cctvpic.com 228 | cdn-go.cn 229 | cdngslb.com 230 | cdnjtzy.com 231 | cdnstatic.tencentcs.com 232 | cgpeers.com 233 | changyan.com 234 | chaoxing.cc 235 | chaoxing.com 236 | chaoxingv.com 237 | chazidian.com 238 | chdbits.co 239 | china.com 240 | chinalive.com 241 | chinamobile.com 242 | chinanetcenter.com 243 | chinaso.com 244 | chinaunicom.cn 245 | chinaz.com 246 | chiphell.com 247 | chuimg.com 248 | chunyu.mobi 249 | chushou.tv 250 | cibntv.net 251 | cinemageddon.net 252 | cinematik.net 253 | cinemaz.to 254 | classix-unlimited.co.uk 255 | cloudglab.com 256 | cls.cn 257 | cmbchina.cn 258 | cmbchina.com 259 | cmbimg.cn 260 | cmbimg.com 261 | cmburl.cn 262 | cmfchina.com 263 | cmfunds.cn 264 | cmpassport.com 265 | cmvideo.cn 266 | cn.download.nvidia.com 267 | cnblogs.com 268 | cnnic.cn 269 | cnspeedtest.cn 270 | cntv.cn 271 | codehub.cn 272 | coding.me 273 | coding.net 274 | coloros.com 275 | com.cn 276 | comicat.org 277 | concertos.live 278 | config.ubnt.com 279 | console.gl-inet.com 280 | coolapk.com 281 | coolapkmarket.com 282 | coolapkmarket.net 283 | cowcs.com 284 | cowtransfer.com 285 | csdn.com 286 | csdn.net 287 | csdnimg.cn 288 | ctfile.com 289 | ctrip.com 290 | cz88.net 291 | dancf.com 292 | dangdang.com 293 | daocloud.io 294 | dbank.com 295 | dbankcdn.cn 296 | dbankcdn.com 297 | dbankcloud.com 298 | dewu.com 299 | dfcfw.com 300 | dianping.com 301 | dicmusic.club 302 | dida365.com 303 | didi.cn 304 | didialift.com 305 | didichuxing.com 306 | didiglobal.com 307 | didistatic.com 308 | digicert.cn 309 | dingtalk.com 310 | discfan.net 311 | dizzylab.net 312 | dl.bscstorage.net 313 | dl.delivery.mp.microsoft.com 314 | dl.eccdnx.com 315 | dl.pinyuncloud.com 316 | dl.ubnt.com 317 | dnspod.cn 318 | dnspod.com 319 | dnsv1.com 320 | docer.com 321 | docin.com 322 | docschina.org 323 | doh.pub 324 | doi.org 325 | donews.com 326 | douban.com 327 | douban.fm 328 | doubanio.com 329 | douyin.com 330 | douyincdn.com 331 | douyinliving.com 332 | douyinpic.com 333 | douyinstatic.com 334 | douyinvod.com 335 | douyu.com 336 | douyu.tv 337 | douyucdn.cn 338 | douyucdn2.cn 339 | douyuscdn.com 340 | douyutv.com 341 | dpfile.com 342 | duapp.com 343 | duba.net 344 | duokan.com 345 | dxdhd.com 346 | dxy.cn 347 | dxycdn.com 348 | eastgame.org 349 | eastmoney.com 350 | eatuo.com 351 | ebay.cn 352 | edge-byted.com 353 | edu.cn 354 | ele.me 355 | elemecdn.com 356 | elseviercdn.cn 357 | email.cn 358 | empornium.me 359 | et8.org 360 | etao.com 361 | eudic.net 362 | exoticaz.to 363 | fang.com 364 | feelgood.cn 365 | feiliao.com 366 | feishu.cn 367 | feishu.net 368 | feishucdn.com 369 | feishupkg.com 370 | feizhu.com 371 | feng.com 372 | fengkongcloud.com 373 | filelist.io 374 | fliggy.com 375 | flyme.cn 376 | flyme.com 377 | fqnovel.com 378 | fqnovelpic.com 379 | frdic.com 380 | freshhema.com 381 | fun.tv 382 | funshion.com 383 | futu5.com 384 | futunn.com 385 | gamersky.com 386 | gaode.com 387 | gaokao.cn 388 | gazellegames.net 389 | gdtimg.com 390 | geetest.com 391 | geilicdn.com 392 | gfxpeers.net 393 | gifshow.com 394 | gitcode.com 395 | gitee.com 396 | gitee.io 397 | godic.net 398 | goofish.com 399 | gorouter.info 400 | gotokeep.com 401 | gov.cn 402 | gstore.val.manlaxy.com 403 | gtimg.cn 404 | gtimg.com 405 | guancha.cn 406 | guoguo-app.com 407 | hao123.com 408 | haosou.com 409 | haowu.link 410 | hc-cdn.com 411 | hd-space.org 412 | hd4.xyz 413 | hd4fans.org 414 | hdarea.co 415 | hdatmos.club 416 | hdbd.us 417 | hdbits.org 418 | hdchina.org 419 | hdcity.city 420 | hddolby.com 421 | hdfans.org 422 | hdhome.org 423 | hdpost.top 424 | hdroute.org 425 | hdsky.me 426 | hdslb.com 427 | hdtime.org 428 | hdupt.com 429 | hdzone.me 430 | hellobike.com 431 | hemamax.com 432 | hemaos.com 433 | hemashare.cn 434 | heytap.com 435 | heytapcs.com 436 | heytapdownload.com 437 | heytapimage.com 438 | heytapmobi.com 439 | hichina.com 440 | hicloud.com 441 | hihonor.com 442 | hihonorcdn.com 443 | hihonorcloud.com 444 | hitpt.com 445 | hitv.com 446 | home.arpa 447 | homerouter.cpe 448 | hongxiu.com 449 | honor.cn 450 | honor.com 451 | hotspot.cslwifi.com 452 | huawei.cn 453 | huawei.com 454 | huaweimobilewifi.com 455 | huaweistatic.com 456 | hudbt.hust.edu.cn 457 | huolala.cn 458 | huoshan.com 459 | huoshanstatic.com 460 | huoshanzhibo.com 461 | hupu.com 462 | huxiu.com 463 | huxiucdn.com 464 | huya.com 465 | hwccpc.cn 466 | hwccpc.com 467 | hwht.com 468 | hwtrip.com 469 | i.dell.com 470 | ialicdn.com 471 | ibreader.com 472 | ibuscloud.com 473 | ibytedapm.com 474 | icetorrent.org 475 | iciba.com 476 | iconfont.cn 477 | icourse163.org 478 | id6.com 479 | id6.me 480 | idqqimg.com 481 | ieee.org 482 | iesdouyin.com 483 | ifeng.com 484 | ifengimg.com 485 | igamecj.com 486 | infoq.cn 487 | injections.adguard.org 488 | instant.arubanetworks.com 489 | ip138.com 490 | ipanda.cn 491 | ipanda.com 492 | ipanda.net 493 | ipip.net 494 | iqiyi.com 495 | iqiyipic.com 496 | itc.cn 497 | ithome.com 498 | ixgvideo.com 499 | ixigua.com 500 | ixiguavideo.com 501 | j99.info 502 | jb51.net 503 | jcloudcache.com 504 | jd.com 505 | jd.hk 506 | jdcache.com 507 | jdcdn.com 508 | jdcloud.com 509 | jdpay.com 510 | jianshu.com 511 | jianshu.io 512 | jiemian.com 513 | jiguang.cn 514 | jomodns.com 515 | jomoxc.com 516 | joyhd.net 517 | jpopsuki.eu 518 | jpush.cn 519 | jstarkan.com 520 | jstor.org 521 | kaiyanapp.com 522 | karagarga.in 523 | kdslife.com 524 | keepcdn.com 525 | keepfrds.com 526 | kingsoft-office-service.com 527 | kkmh.com 528 | klook.com 529 | koubei.com 530 | ks-cdn.com 531 | ksapisrv.com 532 | ksmobile.com 533 | ksord.com 534 | ksosoft.com 535 | ksurl.cn 536 | ksyun.com 537 | ku6.com 538 | kuaishou.com 539 | kuaishouzt.com 540 | kugou.com 541 | kuwo.cn 542 | kwaicdn.com 543 | lagou.com 544 | laiqukankan.com 545 | lancdn.com 546 | landian.vip 547 | landiannews.com 548 | le.com 549 | leaguehd.com 550 | leetcode-cn.com 551 | leetcode.cn 552 | leiniao.com 553 | leleketang.com 554 | lenovomm.com 555 | letvimg.com 556 | lf127.net 557 | lianjia.com 558 | linkedin.cn 559 | livechina.cn 560 | livechina.com 561 | ljcdn.com 562 | local.adguard.org 563 | lofter.com 564 | ludashi.com 565 | luojilab.com 566 | lv.queniujq.cn 567 | lztr.me 568 | m-team.cc 569 | m1905.cn 570 | m1905.com 571 | madsrevolution.net 572 | maoyan.com 573 | maoyun.tv 574 | mcd.cn 575 | media.cdn.queniuqe.com 576 | meipai.com 577 | meitu.com 578 | meituan.com 579 | meituan.net 580 | meitudata.com 581 | meitustat.com 582 | meixincdn.com 583 | meizu.cn 584 | meizu.com 585 | mgtv.com 586 | mi-fds.com 587 | mi-idc.com 588 | mi-img.com 589 | mi.com 590 | mifile.cn 591 | migu.cn 592 | migucloud.com 593 | miguvideo.com 594 | mihayo.com 595 | mihoyo.com 596 | mijia.tech 597 | miot-spec.org 598 | miui.com 599 | miyoushe.com 600 | mmstat.com 601 | mobike.com 602 | mobile.hotspot 603 | mobileservice.cn 604 | moecat.best 605 | mojicdn.com 606 | mrw.so 607 | msg.vg 608 | msstatic.com 609 | mtyun.com 610 | mubu.com 611 | mxhichina.com 612 | myalicdn.com 613 | myanonamouse.net 614 | myapp.com 615 | mybank.cn 616 | myoppo.com 617 | myqcloud.com 618 | myzaker.com 619 | mzres.com 620 | nanyangpt.com 621 | nature.com 622 | ncore.cc 623 | nebulance.io 624 | neea.cn 625 | neixin.cn 626 | net.cn 627 | netease.com 628 | netease.im 629 | netstatic.net 630 | news.cn 631 | nga.cn 632 | ngabbs.com 633 | nicept.net 634 | nlark.com 635 | nmc.cn 636 | npmmirror.com 637 | npupt.com 638 | ntt.setup 639 | nubia.cn 640 | nubia.com 641 | oceanengine.com 642 | ocsp.globalsign.com 643 | ocsp2.globalsign.com 644 | officewebapps.cn 645 | oneplus.cn 646 | oneplusbbs.com 647 | onlinedown.com 648 | open.cd 649 | openinstall.io 650 | oppaiti.me 651 | oppo.cn 652 | oppo.com 653 | oppoer.me 654 | oppomobile.com 655 | oppopay.com 656 | opposhop.cn 657 | opstatics.com 658 | oray.com 659 | oray.net 660 | orayimg.com 661 | org.cn 662 | orpheus.network 663 | osapublishing.org 664 | oschina.net 665 | oup.com 666 | ourbits.club 667 | ourdvs.com 668 | outlook.cn 669 | passthepopcorn.me 670 | pchome.net 671 | pchpic.net 672 | pddpic.com 673 | pddugc.com 674 | peiluyou.com 675 | php.cn 676 | pi.hole 677 | pinduoduo.com 678 | pinduoduo.net 679 | pingan.com 680 | pingwest.com 681 | plex.direct 682 | pornbits.net 683 | pphimalayanrt.com 684 | pplive.cn 685 | pplive.com 686 | pps.tv 687 | ppsimg.com 688 | pptv.com 689 | privatehd.to 690 | psbc.com 691 | pstatp.com 692 | pterclub.com 693 | pthome.net 694 | ptsbao.club 695 | pubyun.com 696 | pubyun.net 697 | qbox.me 698 | qcc.com 699 | qcloud.com 700 | qcloudimg.com 701 | qdaily.com 702 | qh-cdn.com 703 | qhimg.com 704 | qhimgs.com 705 | qhmsg.com 706 | qhres.com 707 | qhres2.com 708 | qhstatic.com 709 | qhupdate.com 710 | qichacha.com 711 | qidian.com 712 | qihucdn.com 713 | qiku.com 714 | qingcdn.com 715 | qiniu.com 716 | qixin.com 717 | qiyi.com 718 | qiyipic.com 719 | qiyou.cn 720 | qlivecdn.com 721 | qlogo.cn 722 | qpic.cn 723 | qq.com 724 | qqmail.com 725 | qy.net 726 | qyer.com 727 | qyerstatic.com 728 | qzone.com 729 | realme.com 730 | realmebbs.com 731 | realmemobile.com 732 | redacted.ch 733 | repeater.asus.com 734 | researchgate.net 735 | roblox.cn 736 | robloxdev.cn 737 | ronghub.com 738 | router.asus.com 739 | routerlogin.com 740 | routerlogin.net 741 | rsproxy.cn 742 | ruanmei.com 743 | ruguoapp.com 744 | ruiwen.com 745 | s-reader.com 746 | samsungcloudcn.com 747 | sandai.net 748 | sankuai.com 749 | saxyit.com 750 | sciencemag.org 751 | scopus.com 752 | sdbits.org 753 | securelogin.com.cn 754 | segmentfault.com 755 | setmeup.arubanetworks.com 756 | sevencdn.com 757 | sharepoint.cn 758 | shifen.com 759 | shyhhema.com 760 | sifou.com 761 | sina.cn 762 | sina.com 763 | sinaapp.com 764 | sinaedge.com 765 | sinaimg.cn 766 | sinaimg.com 767 | sinajs.cn 768 | sinajs.com 769 | skyey2.com 770 | sm.cn 771 | smtcdns.net 772 | smzdm.com 773 | snssdk.com 774 | so.com 775 | sogo.com 776 | sogou.com 777 | sogoucdn.com 778 | sohu-inc.com 779 | sohu.com 780 | sohucs.com 781 | soku.com 782 | solidot.org 783 | soso.com 784 | soufunimg.com 785 | soulapp.cn 786 | soulvoice.club 787 | speedtest.cn 788 | springer.com 789 | springernature.com 790 | springsunday.net 791 | sspai.com 792 | staticdn.net 793 | steam.clngaa.com 794 | steam.ksyna.com 795 | steamchina.com 796 | steamserver.net 797 | suning.cn 798 | suning.com 799 | szbdyd.com 800 | t.cn 801 | t3go.cn 802 | takungpao.com 803 | tantanapp.com 804 | tanx.com 805 | taobao.com 806 | taobaocdn.com 807 | taopiaopiao.com 808 | tb.cn 809 | tbcache.com 810 | tbcdn.cn 811 | tcdnlive.com 812 | tencent-cloud.com 813 | tencent-cloud.net 814 | tencent.com 815 | tencentcs.cn 816 | tencentmusic.com 817 | tianyancha.com 818 | tieba.com 819 | tjupt.org 820 | tmall.com 821 | tnkjmec.com 822 | totheglory.im 823 | toutiao.com 824 | toutiaocloud.com 825 | toutiaoimg.cn 826 | toutiaoimg.com 827 | toutiaopage.com 828 | toutiaostatic.com 829 | toutiaovod.com 830 | tplinkap.net 831 | tplinkmodem.net 832 | tplinkplclogin.net 833 | tplinkrepeater.net 834 | tplinkwifi.net 835 | tplogin.cn 836 | tripcdn.cn 837 | trontv.com 838 | tudou.com 839 | tvmao.com 840 | tvzhe.com 841 | u2.dmhy.org 842 | uc.cn 843 | uczzd.cn 844 | udache.com 845 | udesk.cn 846 | udqqimg.com 847 | uhdbits.org 848 | ui.direct 849 | unionpay.com 850 | upaiyun.com 851 | upyun.com 852 | url.cn 853 | v-56.com 854 | variflight.com 855 | veryzhun.com 856 | vip.com 857 | vivo.com 858 | vmall.com 859 | vmallres.com 860 | volccdn.com 861 | volces.com 862 | volcvideo.com 863 | wallstreetcn.com 864 | wandoujia.com 865 | wangsu.com 866 | weathercn.com 867 | webok.net 868 | wegame.com 869 | wegameplus.com 870 | weibo.cn 871 | weibo.com 872 | weibocdn.com 873 | weidian.com 874 | weixin.com 875 | weixinbridge.com 876 | weiyun.com 877 | west.cn 878 | wifi8.com 879 | wiley.com 880 | wmsj.cn 881 | wmsjsteam.com 882 | wo.cn 883 | wosms.cn 884 | wps.cn 885 | wpscdn.cn 886 | wpscdn.com 887 | wscn.net 888 | www.intel.cn 889 | www.layuicdn.com 890 | x3322.net 891 | xhscdn.com 892 | xiachufang.com 893 | xiami.com 894 | xiami.net 895 | xiaoaisound.com 896 | xiaodutv.com 897 | xiaohongshu.com 898 | xiaomi.cn 899 | xiaomi.com 900 | xiaomi.net 901 | xiaomiev.com 902 | xiaomixiaoai.com 903 | xiaomiyoupin.com 904 | xiaote.net 905 | ximalaya.com 906 | xincai.com 907 | xinhuanet.com 908 | xiniu.com 909 | xmcdn.com 910 | xoyocdn.com 911 | xunlei.com 912 | xunyou.mobi 913 | xycdn.com 914 | xycloud.com 915 | yangkeduo.com 916 | ydstatic.com 917 | yeah.net 918 | yicai.com 919 | yinxiang.com 920 | yitao.com 921 | ykimg.com 922 | youdao.com 923 | youku.com 924 | youzanyun.com 925 | yqkk.link 926 | ys7.com 927 | yuanshen.com 928 | yuewen.com 929 | yumchina.com 930 | yunjiasu-cdn.net 931 | yunos.com 932 | yunpan.cn 933 | yunpan.com 934 | yupoo.com 935 | yuque.com 936 | yximgs.com 937 | yy.com 938 | yzcdn.cn 939 | zdic.net 940 | zdmimg.com 941 | zhangzs.com 942 | zhihu.com 943 | zhimg.com 944 | zhipin.com 945 | zhuanzfx.com 946 | zijieapi.com 947 | zjcdn.com 948 | zui.com 949 | zuimeitianqi.com 950 | zuoyebang.com 951 | -------------------------------------------------------------------------------- /SmartDNS/google_cn.conf: -------------------------------------------------------------------------------- 1 | 265.com 2 | 2mdn-cn.net 3 | 2mdn.net 4 | admob-cn.com 5 | adservice.google.com 6 | app-analytics-services.com 7 | app-measurement-cn.com 8 | app-measurement.com 9 | apps5.oingo.com 10 | avail.googleflights.net 11 | beacons.gcp.gvt2.com 12 | beacons.gvt2.com 13 | beacons2.gvt2.com 14 | beacons3.gvt2.com 15 | c.admob.com 16 | c.android.clients.google.com 17 | cache-management-prod.google.com 18 | cache.pack.google.com 19 | clickserve.cc-dt.com 20 | clickserve.dartsearch.net 21 | clickserver.googleads.com 22 | clientservices.googleapis.com 23 | cn.widevine.com 24 | cnappinstall.googleadapis.com 25 | content.googleadapis.com 26 | crashlyticsreports-pa.googleapis.com 27 | crl.pki.goog 28 | dartsearch-cn.net 29 | dg-meta.video.google.com 30 | dl.google.com 31 | dl.l.google.com 32 | doubleclick-cn.net 33 | doubleclick.net 34 | download.mlcc.google.com 35 | download.qatp1.net 36 | download.tensorflow.google.com 37 | emmapplecodevice.googleapis.com 38 | firebase-settings.crashlytics.com 39 | fontfiles.googleapis.com 40 | fonts.googleapis.com 41 | go.corp.google.com 42 | gonglchuangl.net 43 | gongyichuangyi.net 44 | google-analytics-cn.com 45 | google-analytics.com 46 | googleadservices-cn.com 47 | googleadservices.com 48 | googleanalytics.com 49 | googleapis-cn.com 50 | googleapps-cn.com 51 | googleflights-cn.net 52 | googleoptimize-cn.com 53 | googleoptimize.com 54 | googlesyndication-cn.com 55 | googlesyndication.com 56 | googletagmanager-cn.com 57 | googletagmanager.com 58 | googletagservices-cn.com 59 | googletagservices.com 60 | googletraveladservices-cn.com 61 | googletraveladservices.com 62 | googlevads-cn.com 63 | gstatic-cn.com 64 | gstaticadssl.l.google.com 65 | gtm.oasisfeng.com 66 | gvt1-cn.com 67 | gvt2-cn.com 68 | imasdk.googleapis.com 69 | l2-uberproxy.corp.google.com 70 | logger-dev.corp.google.com 71 | logger.corp.google.com 72 | login.corp.google.com 73 | monitoring.qpdp1.net 74 | ocsp.pki.goog 75 | pagead-googlehosted.l.google.com 76 | performanceparameters.googleapis.com 77 | pki-goog.l.google.com 78 | prod-controlbe.floonet.goog 79 | prod-databe.floonet.goog 80 | prod.databe.floonet.goog 81 | proxyconfig.corp.google.com 82 | qagpublic.qatp1.net 83 | qgadmin.qcpp1.net 84 | qiao-cn.com 85 | qpx.googleflights.net 86 | qualysapi.qatp1.net 87 | qualysguard.qpdp1.net 88 | r.cert.corp.google.com 89 | rapture-prod.corp.google.com 90 | recaptcha-cn.net 91 | recaptcha.net 92 | redirector.bdn.dev 93 | redirector.c.chat.google.com 94 | redirector.c.mail.google.com 95 | redirector.c.pack.google.com 96 | redirector.c.play.google.com 97 | redirector.c.youtubeeducation.com 98 | redirector.gcpcdn.gvt1.com 99 | redirector.gvt1.com 100 | redirector.offline-maps.gvt1.com 101 | redirector.snap.gvt1.com 102 | redirector.xn--ngstr-lra8j.com 103 | safebrowsing-cache.google.com 104 | safebrowsing.googleapis.com 105 | scanservice1.qcpp1.net 106 | service.urchin.com 107 | ssl-google-analytics.l.google.com 108 | sslredirect.corp.google.com 109 | staging-controlbe.floonet.goog 110 | staging-databe.floonet.goog 111 | staging.databe.floonet.goog 112 | streaming-uberproxy-rotation.corp.google.com 113 | streaming-uberproxy.corp.google.com 114 | sup-ssh-relay.corp.google.com 115 | sup-ssh-relay2.corp.google.com 116 | sup.corp.google.com 117 | sup.l.google.com 118 | tac.googleapis.com 119 | test.gbugs-qa.chromium.org 120 | tools.google.com 121 | tools.l.google.com 122 | uberproxy-debug4.corp.google.com 123 | uberproxy.corp.google.com 124 | uberproxy6.corp.google.com 125 | update.crashlytics.com 126 | update.googleapis.com 127 | wear.googleapis.com 128 | www-google-analytics.l.google.com 129 | www-googletagmanager.l.google.com 130 | www.destinationurl.com 131 | www.pxcc.com 132 | xn--flw351e.com 133 | -------------------------------------------------------------------------------- /SmartDNS/microsoft_cdn.conf: -------------------------------------------------------------------------------- 1 | download.windowsupdate.com 2 | ctldl.windowsupdate.com 3 | cn.windowssearch.com 4 | download.visualstudio.microsoft.com 5 | officecdn.microsoft.com 6 | developer.microsoft.com 7 | download.microsoft.com 8 | devblogs.microsoft.com 9 | learn.microsoft.com 10 | download.prss.microsoft.com 11 | docs.microsoft.com 12 | wscont2.apps.microsoft.com 13 | wscont1.apps.microsoft.com 14 | sdx.microsoft.com 15 | dl.delivery.mp.microsoft.com 16 | storeedgefd.dsx.mp.microsoft.com 17 | fs.microsoft.com -------------------------------------------------------------------------------- /SmartDNS/smartdns.conf: -------------------------------------------------------------------------------- 1 | bind [::]:5353 2 | bind-tcp [::]:5353 3 | 4 | # 缓存配置 5 | cache-size 1024 6 | cache-persist yes 7 | cache-file /etc/smartdns/file 8 | cache-checkpoint-time 86400 # 设置缓存定时保存 9 | 10 | # 缓存预获取 11 | prefetch-domain yes 12 | 13 | # 过期缓存 14 | serve-expired yes 15 | serve-expired-ttl 259200 # 过期缓存多长时间未访问则从缓存中释放 16 | serve-expired-reply-ttl 1 # 缓存中域名TTL超时后返回给客户端的TTL时间 17 | serve-expired-prefetch-time 21600 # 过期缓存未访问时主动进行预获取 18 | 19 | # 完全禁用IPv6 20 | force-AAAA-SOA yes 21 | 22 | # DNS 服务器组 23 | server 运营商DNS1 -group china -exclude-default-group 24 | server 运营商DNS2 -group china -exclude-default-group 25 | server 223.5.5.5 -group china -exclude-default-group 26 | server 运营商DNS1 -group apple -exclude-default-group 27 | server-tls 1.1.1.1 28 | server-https https://8.8.8.8/dns-query 29 | server-https https://1.1.1.1/dns-query 30 | 31 | # 添加域名列表,格式为一行一个域名 32 | domain-set -name china-domain-list -file /etc/smartdns/cn_domain.conf 33 | # 设置对应域名列表的规则。 34 | domain-rules /domain-set:china-domain-list/ -c none -address #6 -nameserver china 35 | 36 | # 添加域名列表,格式为一行一个域名 37 | domain-set -name apple-domain-list -file /etc/smartdns/apple_cdn.conf 38 | # 设置对应域名列表的规则。 39 | domain-rules /domain-set:apple-domain-list/ -c none -address #6 -nameserver apple -------------------------------------------------------------------------------- /SmartDNS/smartdns_s.conf: -------------------------------------------------------------------------------- 1 | # SmartDNS 监听端口 2 | bind [::]:5353 3 | bind-tcp [::]:5353 4 | 5 | # 缓存配置 6 | cache-size 1024 7 | cache-persist yes 8 | cache-file /etc/smartdns/file 9 | cache-checkpoint-time 86400 # 设置缓存定时保存 10 | 11 | # 缓存预获取 12 | prefetch-domain yes 13 | 14 | # 过期缓存 15 | serve-expired yes 16 | serve-expired-ttl 259200 # 过期缓存多长时间未访问则从缓存中释放 17 | serve-expired-reply-ttl 1 # 缓存中域名TTL超时后返回给客户端的TTL时间 18 | serve-expired-prefetch-time 21600 # 过期缓存未访问时主动进行预获取 19 | 20 | # 完全禁用 IPv6 21 | force-AAAA-SOA yes 22 | 23 | # DNS 服务器组 24 | server 运营商DNS1 -group china -exclude-default-group 25 | server 运营商DNS2 -group china -exclude-default-group 26 | server-tls 223.5.5.5 -group china -exclude-default-group 27 | server-tls 1.1.1.1 28 | server-https https://8.8.8.8/dns-query 29 | server-https https://1.1.1.1/dns-query 30 | 31 | # 添加域名列表,格式为一行一个域名 32 | domain-set -name china-domain-list -file /etc/smartdns/all_domains.conf 33 | # 设置对应域名列表的规则。 34 | domain-rules /domain-set:china-domain-list/ -c none -nameserver china -speed-check-mode ping -response-mode first-ping -address -6 35 | -------------------------------------------------------------------------------- /SmartDash/README.md: -------------------------------------------------------------------------------- 1 | # SmartDash 安装与管理脚本 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 4 | [![Platform: Linux](https://img.shields.io/badge/Platform-Linux-green.svg)](https://www.linux.org/) 5 | 6 | **SmartDash** 是一个基于 **SmartDNS** 的智能 DNS 外部面板程序。本脚本提供自动化安装与管理功能,专为 Linux 系统设计,简化 SmartDash 的部署与维护。 7 | 8 | ## ✨ 功能亮点 9 | 10 | - **依赖检查**:自动检测 Python 环境及必要模块,支持一键安装缺失依赖。 11 | - **服务安装**:将 SmartDash 配置为系统服务,包含程序下载与依赖部署。 12 | - **便捷卸载**:支持卸载 SmartDash 服务,并可选删除相关文件。 13 | - **前置检测**:确保 SmartDNS 已正确安装,作为运行前提。 14 | 15 | ## 📥 安装与使用 16 | 17 | ### 1. 通过以下命令安装: 18 | 19 | ```bash 20 | bash <(curl -sL https://lidao.win/smartdash.sh) 21 | ``` 22 | 23 | ### 2. 交互式菜单 24 | 25 | 运行脚本后,将显示以下交互式菜单供选择: 26 | 27 | - **1**:检查依赖环境 28 | - **2**:安装 SmartDash 为系统服务 29 | - **3**:卸载 SmartDash 服务 30 | - **4**:退出 31 | 32 | ## ⚠️ 注意事项 33 | 34 | - **前置条件**:必须先安装 **SmartDNS**,且配置文件位于 `/etc/smartdns/smartdns.conf`。 35 | - **网络要求**:脚本运行需要联网以下载文件和依赖。 36 | - **权限建议**:建议使用具有 `sudo` 权限的用户运行脚本。 37 | 38 | > **提示**:请确保系统环境干净,避免因依赖冲突导致安装失败。 39 | 40 | ## 📜 许可 41 | 42 | 本项目采用 [MIT 许可证](https://opensource.org/licenses/MIT),欢迎自由使用与修改。 43 | 44 | ## 🔗 相关资源 45 | 46 | - [SmartDNS 官方文档](https://github.com/pymumu/smartdns) 47 | - [SmartDash 项目主页](https://github.com/LidaoNote/OpenCode/tree/main/SmartDash) -------------------------------------------------------------------------------- /SmartDash/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, request, redirect, url_for, flash, jsonify 2 | from flask_bootstrap import Bootstrap 3 | import os 4 | import shutil 5 | import requests 6 | from requests.adapters import HTTPAdapter 7 | from urllib3.util.retry import Retry 8 | import dns.resolver 9 | import time 10 | import subprocess 11 | from datetime import datetime 12 | import threading 13 | import logging 14 | import re 15 | 16 | app = Flask(__name__) 17 | app.config['SECRET_KEY'] = 'your-secret-key' 18 | Bootstrap(app) 19 | 20 | # 配置文件路径 21 | CONFIG_FILE = '/etc/smartdns/smartdns.conf' 22 | CONFIG_BACKUP = '/etc/smartdns/smartdns.conf.bak' 23 | CONFIG_BACKUP_DIR = '/etc/smartdns/backups/' 24 | SERVICE_NAME = 'smartdns' 25 | BASE_CONFIG_PATH = '/etc/smartdns/' 26 | 27 | # 确保备份目录存在 28 | if not os.path.exists(CONFIG_BACKUP_DIR): 29 | os.makedirs(CONFIG_BACKUP_DIR) 30 | 31 | # 设置日志 32 | logging.basicConfig(level=logging.INFO) 33 | logger = logging.getLogger(__name__) 34 | 35 | # 用于记录任务上次执行时间的字典,避免重复执行 36 | last_execution_times = {} 37 | 38 | def validate_domains(content): 39 | """验证内容是否包含有效的域名,支持顶级域名和国际化域名(Punycode)""" 40 | # 支持顶级域名(如 cn、com)、子域名和 Punycode 编码的国际化域名 41 | domain_pattern = re.compile( 42 | r'^(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+)?' 43 | r'(?:[a-zA-Z]{2,}|xn--[a-zA-Z0-9-]{2,})$', 44 | re.IGNORECASE 45 | ) 46 | lines = content.splitlines() 47 | valid_domains = 0 48 | for line in lines: 49 | line = line.strip() 50 | if not line or line.startswith('#'): 51 | continue 52 | if domain_pattern.match(line): 53 | valid_domains += 1 54 | else: 55 | logger.warning(f"无效的域名: {line}") 56 | return False, f"无效的域名: {line}" 57 | if valid_domains == 0: 58 | return False, "没有有效的域名" 59 | return True, "验证通过" 60 | 61 | def generate_domain_filename(friendly_name): 62 | """生成标准化的域名组文件名""" 63 | return f"{BASE_CONFIG_PATH}{friendly_name.lower().replace(' ', '_')}_domains.conf" 64 | 65 | def infer_server_type(address): 66 | """根据服务器地址推断服务器类型""" 67 | address = address.lower().strip() 68 | if address.startswith('https://'): 69 | return 'server-https' 70 | if address.startswith('tls://') or ':853' in address: 71 | return 'server-tls' 72 | return 'server' 73 | 74 | def read_config(): 75 | """读取配置文件并解析""" 76 | config = { 77 | 'bind': {'port': '5353', 'tcp_port': '5353'}, 78 | 'cache': { 79 | 'enabled': 'yes', 80 | 'size': '32768', 81 | 'persist': 'yes', 82 | 'file': '/etc/smartdns/cache.db', 83 | 'checkpoint_time': '600' 84 | }, 85 | 'prefetch': {'enabled': 'yes'}, 86 | 'expired': { 87 | 'enabled': 'yes', 88 | 'ttl': '600', 89 | 'reply_ttl': '1', 90 | 'prefetch_time': '1200' 91 | }, 92 | 'ipv6': {'force_aaaa_soa': 'yes'}, 93 | 'servers': [], 94 | 'domain_sets': [] 95 | } 96 | 97 | try: 98 | if not os.path.exists(CONFIG_FILE): 99 | logger.warning(f"配置文件 {CONFIG_FILE} 不存在") 100 | return config 101 | 102 | with open(CONFIG_FILE, 'r', encoding='utf-8') as f: 103 | lines = f.readlines() 104 | i = 0 105 | raw_servers = [] 106 | while i < len(lines): 107 | line = lines[i].strip() 108 | if not line or line.startswith('#'): 109 | i += 1 110 | continue 111 | 112 | parts = line.split() 113 | if not parts: 114 | i += 1 115 | continue 116 | 117 | if parts[0] == 'bind': 118 | config['bind']['port'] = parts[1].split(':')[-1] if len(parts) > 1 else '5353' 119 | elif parts[0] == 'bind-tcp': 120 | config['bind']['tcp_port'] = parts[1].split(':')[-1] if len(parts) > 1 else '5353' 121 | elif parts[0] == 'cache-size': 122 | config['cache']['size'] = parts[1] if len(parts) > 1 else '32768' 123 | config['cache']['enabled'] = 'yes' 124 | elif parts[0] == 'cache-persist': 125 | config['cache']['persist'] = parts[1] if len(parts) > 1 else 'yes' 126 | elif parts[0] == 'cache-file': 127 | config['cache']['file'] = parts[1] if len(parts) > 1 else '/etc/smartdns/cache.db' 128 | elif parts[0] == 'cache-checkpoint-time': 129 | config['cache']['checkpoint_time'] = parts[1] if len(parts) > 1 else '600' 130 | elif parts[0] == 'prefetch-domain': 131 | config['prefetch']['enabled'] = parts[1] if len(parts) > 1 else 'yes' 132 | elif parts[0] == 'serve-expired': 133 | config['expired']['enabled'] = parts[1] if len(parts) > 1 else 'yes' 134 | elif parts[0] == 'serve-expired-ttl': 135 | config['expired']['ttl'] = parts[1] if len(parts) > 1 else '600' 136 | elif parts[0] == 'serve-expired-reply-ttl': 137 | config['expired']['reply_ttl'] = parts[1] if len(parts) > 1 else '1' 138 | elif parts[0] == 'serve-expired-prefetch-time': 139 | config['expired']['prefetch_time'] = parts[1] if len(parts) > 1 else '1200' 140 | elif parts[0] == 'force-AAAA-SOA': 141 | config['ipv6']['force_aaaa_soa'] = parts[1] if len(parts) > 1 else 'yes' 142 | elif parts[0].startswith('server'): 143 | if len(parts) > 1: 144 | server_info = {'address': parts[1], 'type': parts[0], 'group': '通用'} 145 | if len(parts) > 2: 146 | options = parts[2:] 147 | for j in range(len(options)): 148 | if options[j] == '-group' and j + 1 < len(options): 149 | server_info['group'] = options[j + 1] 150 | break 151 | raw_servers.append(server_info) 152 | elif parts[0] == 'domain-set': 153 | if len(parts) >= 5 and parts[1] == '-name' and parts[3] == '-file': 154 | friendly_name = parts[2].split('-')[0] if '-' in parts[2] else parts[2] 155 | domain_set_info = { 156 | 'name': parts[2], 157 | 'friendly_name': friendly_name, 158 | 'file': parts[4], 159 | 'group': friendly_name, 160 | 'source_url': '', 161 | 'last_updated': '', 162 | 'domain_count': 0, 163 | 'speed_check_mode': 'ping', 164 | 'response_mode': 'fastest', 165 | 'address_ipv6': False, 166 | 'update_schedule': {'frequency': 'none', 'time': '', 'day': ''} # 确保默认值 167 | } 168 | while i + 1 < len(lines): 169 | next_line = lines[i + 1].strip() 170 | if next_line.startswith('# Source ='): 171 | domain_set_info['source_url'] = next_line.replace('# Source =', '').strip() 172 | i += 1 173 | elif next_line.startswith('# Update-Schedule ='): 174 | schedule_info = next_line.replace('# Update-Schedule =', '').strip().split(',') 175 | if len(schedule_info) >= 2: 176 | domain_set_info['update_schedule']['frequency'] = schedule_info[0].strip() 177 | domain_set_info['update_schedule']['time'] = schedule_info[1].strip() 178 | if len(schedule_info) > 2: 179 | domain_set_info['update_schedule']['day'] = schedule_info[2].strip() 180 | i += 1 181 | else: 182 | break 183 | while i + 1 < len(lines): 184 | next_line = lines[i + 1].strip() 185 | if not next_line.startswith('domain-rules'): 186 | break 187 | rule_parts = next_line.split() 188 | for j in range(len(rule_parts)): 189 | if rule_parts[j] == '-speed-check-mode' and j + 1 < len(rule_parts): 190 | domain_set_info['speed_check_mode'] = rule_parts[j + 1] 191 | elif rule_parts[j] == '-response-mode' and j + 1 < len(rule_parts): 192 | domain_set_info['response_mode'] = rule_parts[j + 1] 193 | elif rule_parts[j] == '-address' and j + 1 < len(rule_parts) and rule_parts[j + 1] == '-6': 194 | domain_set_info['address_ipv6'] = True 195 | i += 1 196 | try: 197 | if os.path.exists(domain_set_info['file']): 198 | domain_count = 0 199 | with open(domain_set_info['file'], 'r', encoding='utf-8') as df: 200 | for line in df: 201 | if line.strip() and not line.startswith('#'): 202 | domain_count += 1 203 | domain_set_info['domain_count'] = domain_count 204 | mtime = os.path.getmtime(domain_set_info['file']) 205 | domain_set_info['last_updated'] = datetime.fromtimestamp(mtime).strftime('%Y-%m-%d %H:%M:%S') 206 | else: 207 | domain_set_info['last_updated'] = '文件不存在' 208 | domain_set_info['domain_count'] = 0 209 | except Exception as e: 210 | logger.error(f"读取域名文件 {domain_set_info['file']} 出错: {str(e)}") 211 | domain_set_info['last_updated'] = '读取失败' 212 | domain_set_info['domain_count'] = 0 213 | config['domain_sets'].append(domain_set_info) 214 | i += 1 215 | 216 | server_dict = {} 217 | for server in raw_servers: 218 | key = (server['group'], server['type']) 219 | if key not in server_dict: 220 | server_dict[key] = { 221 | 'type': server['type'], 222 | 'group': server['group'], 223 | 'addresses': [] 224 | } 225 | server_dict[key]['addresses'].append(server['address']) 226 | 227 | config['servers'] = list(server_dict.values()) 228 | except Exception as e: 229 | logger.error(f"读取配置文件出错: {str(e)}") 230 | 231 | return config 232 | 233 | def write_config(config): 234 | """将配置写入文件""" 235 | try: 236 | shutil.copy2(CONFIG_FILE, CONFIG_BACKUP) 237 | 238 | with open(CONFIG_FILE, 'w', encoding='utf-8') as f: 239 | f.write("# SmartDNS 配置文件\n") 240 | f.write(f"bind [::]:{config['bind']['port']}\n") 241 | f.write(f"bind-tcp [::]:{config['bind']['tcp_port']}\n\n") 242 | if config['cache']['enabled'] == 'yes': 243 | f.write("# 缓存设置\n") 244 | f.write(f"cache-size {config['cache']['size']}\n") 245 | f.write(f"cache-persist {config['cache']['persist']}\n") 246 | f.write(f"cache-file {config['cache']['file']}\n") 247 | f.write(f"cache-checkpoint-time {config['cache']['checkpoint_time']}\n\n") 248 | f.write(f"# 缓存预获取\n") 249 | f.write(f"prefetch-domain {config['prefetch']['enabled']}\n") 250 | f.write(f"# 乐观缓存\n") 251 | f.write(f"serve-expired {config['expired']['enabled']}\n") 252 | f.write(f"serve-expired-ttl {config['expired']['ttl']}\n") 253 | f.write(f"serve-expired-reply-ttl {config['expired']['reply_ttl']}\n") 254 | f.write(f"serve-expired-prefetch-time {config['expired']['prefetch_time']}\n\n") 255 | f.write(f"# 全局禁用 IPv6\n") 256 | f.write(f"force-AAAA-SOA {config['ipv6']['force_aaaa_soa']}\n\n") 257 | f.write("# 非通用组服务器\n") 258 | for server in sorted([s for s in config['servers'] if s['group'] != '通用'], key=lambda x: x['group']): 259 | for address in server['addresses']: 260 | f.write(f"# 所属域名组:{server['group']}\n") 261 | f.write(f"{server['type']} {address} -group {server['group']} -exclude-default-group\n") 262 | f.write("\n# 通用组服务器\n") 263 | for server in [s for s in config['servers'] if s['group'] == '通用']: 264 | for address in server['addresses']: 265 | f.write(f"{server['type']} {address}\n") 266 | f.write("\n# 域名组设置\n") 267 | for domain_set in config['domain_sets']: 268 | f.write(f"# {domain_set['group']}域名组文件路径和延迟测试方法 / IPv6是否启用等设置\n") 269 | f.write(f"domain-set -name {domain_set['name']} -file {domain_set['file']}\n") 270 | if domain_set.get('source_url'): 271 | f.write(f"# Source = {domain_set['source_url']}\n") 272 | schedule = domain_set.get('update_schedule', {}) 273 | if schedule.get('frequency') != 'none': 274 | schedule_str = f"{schedule['frequency']},{schedule['time']}" 275 | if schedule['frequency'] == 'weekly' and schedule.get('day'): 276 | schedule_str += f",{schedule['day']}" 277 | f.write(f"# Update-Schedule = {schedule_str}\n") 278 | rule = f"domain-rules /domain-set:{domain_set['name']}/ -c none -nameserver {domain_set['group']}" 279 | if domain_set['speed_check_mode'] != 'none': 280 | rule += f" -speed-check-mode {domain_set['speed_check_mode']}" 281 | if domain_set['response_mode'] != 'none': 282 | rule += f" -response-mode {domain_set['response_mode']}" 283 | if domain_set['address_ipv6']: 284 | rule += f" -address -6" 285 | f.write(f"{rule}\n") 286 | except Exception as e: 287 | logger.error(f"写入配置文件出错: {str(e)}") 288 | raise 289 | 290 | def update_domain_content_by_index(index, url, restart=True): 291 | """更新指定域名组的内容""" 292 | try: 293 | config = read_config() 294 | if 0 <= index < len(config['domain_sets']): 295 | session = requests.Session() 296 | retries = Retry(total=5, backoff_factor=2, status_forcelist=[502, 503, 504, 403, 429]) 297 | session.mount('https://', HTTPAdapter(max_retries=retries)) 298 | domain = url.split('/')[2] if '//' in url else url.split('/')[0] 299 | ip = resolve_domain_with_local_dns(domain) 300 | response = None 301 | if ip: 302 | logger.info(f"解析 {domain} 到 {ip}") 303 | try: 304 | if '//' in url: 305 | parts = url.split('//') 306 | path = parts[1].split('/', 1)[1] if len(parts[1].split('/')) > 1 else '' 307 | new_url = f"{parts[0]}//{ip}/{path}" 308 | else: 309 | path = url.split('/', 1)[1] if len(url.split('/')) > 1 else '' 310 | new_url = f"{ip}/{path}" 311 | headers = {'Host': domain} 312 | response = session.get(new_url, timeout=30, headers=headers, verify=False) 313 | if response.status_code == 200: 314 | logger.info(f"从 IP {ip} 成功获取内容") 315 | else: 316 | logger.warning(f"IP 请求失败,状态码 {response.status_code},回退到域名") 317 | response = None 318 | except Exception as ip_error: 319 | logger.error(f"IP 请求失败: {str(ip_error)},回退到域名") 320 | if not response or response.status_code != 200: 321 | response = session.get(url, timeout=30) 322 | if response.status_code == 200: 323 | content = response.text 324 | is_valid, validation_message = validate_domains(content) 325 | if not is_valid: 326 | logger.error(f"域名验证失败: {validation_message}") 327 | return False, validation_message, False 328 | try: 329 | with open(config['domain_sets'][index]['file'], 'w', encoding='utf-8') as f: 330 | f.write(content) 331 | mtime = os.path.getmtime(config['domain_sets'][index]['file']) 332 | config['domain_sets'][index]['last_updated'] = datetime.fromtimestamp(mtime).strftime('%Y-%m-%d %H:%M:%S') 333 | config['domain_sets'][index]['domain_count'] = len([line for line in content.splitlines() if line.strip() and not line.startswith('#')]) 334 | write_config(config) 335 | restarted = False 336 | restart_message = "" 337 | if restart: 338 | def delayed_restart(): 339 | time.sleep(1) 340 | success, restart_message = restart_service() 341 | if not success: 342 | logger.error(f"服务重启失败: {restart_message}") 343 | threading.Thread(target=delayed_restart, daemon=True).start() 344 | restarted = True 345 | logger.info(f"成功更新域名组 {config['domain_sets'][index]['name']}") 346 | return True, "更新成功", restarted 347 | except Exception as e: 348 | logger.error(f"保存文件内容出错: {str(e)}") 349 | return False, f"保存文件内容出错:{str(e)}", False 350 | else: 351 | logger.error(f"从 URL 获取内容失败,状态码: {response.status_code}") 352 | return False, f"从URL获取内容失败,状态码:{response.status_code}", False 353 | else: 354 | logger.error("无效的域名组索引") 355 | return False, "无效的域名组索引", False 356 | except Exception as e: 357 | logger.error(f"更新内容出错: {str(e)}") 358 | return False, f"更新内容出错: {str(e)}", False 359 | 360 | def matches_current_time(schedule_info): 361 | """检查当前时间是否匹配设置的更新时间""" 362 | current_time = datetime.now() 363 | current_hour_minute = current_time.strftime("%H:%M") 364 | current_day = current_time.strftime("%A").lower() # 如 'monday' 365 | 366 | frequency = schedule_info.get('frequency', 'none') 367 | set_time = schedule_info.get('time', '') 368 | set_day = schedule_info.get('day', '').lower() 369 | 370 | if frequency == 'none' or not set_time: 371 | return False 372 | 373 | # 检查时间是否匹配 374 | if current_hour_minute != set_time: 375 | return False 376 | 377 | # 对于每周频率,额外检查星期几 378 | if frequency == 'weekly' and set_day: 379 | if current_day != set_day: 380 | return False 381 | 382 | return True 383 | 384 | def run_custom_scheduler(): 385 | """自定义调度器,每分钟检查当前时间与设置时间的一致性""" 386 | logger.info("自定义调度器线程启动") 387 | while True: 388 | try: 389 | current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") 390 | config = read_config() 391 | for index, domain_set in enumerate(config['domain_sets']): 392 | schedule_info = domain_set.get('update_schedule', {}) 393 | source_url = domain_set.get('source_url', '') 394 | name = domain_set.get('name', '未知') 395 | 396 | if schedule_info.get('frequency', 'none') == 'none' or not source_url: 397 | continue 398 | 399 | # 检查当前时间是否匹配设置时间 400 | if matches_current_time(schedule_info): 401 | # 检查是否已在本周期内执行过 402 | last_exec_key = f"{name}_{schedule_info['frequency']}_{schedule_info.get('day', '')}" 403 | last_exec_time = last_execution_times.get(last_exec_key) 404 | current_day = datetime.now().strftime("%Y-%m-%d") 405 | if last_exec_time and last_exec_time.startswith(current_day): 406 | logger.info(f"任务 {name} 今天已执行,跳过") 407 | continue 408 | 409 | logger.info(f"时间匹配,执行更新任务 for {name} at {current_time}") 410 | success, message, restarted = update_domain_content_by_index(index, source_url) 411 | if success: 412 | logger.info(f"更新任务 {name} 成功: {message}") 413 | last_execution_times[last_exec_key] = current_time 414 | else: 415 | logger.error(f"更新任务 {name} 失败: {message}") 416 | #else: 417 | # logger.debug(f"时间不匹配 for {name}: 当前 {current_time}, 设置 {schedule_info}") 418 | time.sleep(30) # 每半分钟检查一次 419 | except Exception as e: 420 | logger.error(f"自定义调度器出错: {str(e)}") 421 | time.sleep(30) 422 | 423 | # 启动自定义调度器线程 424 | scheduler_thread = threading.Thread(target=run_custom_scheduler, daemon=True) 425 | scheduler_thread.start() 426 | 427 | def resolve_domain_with_local_dns(domain): 428 | """通过本机DNS解析域名""" 429 | try: 430 | resolver = dns.resolver.Resolver() 431 | resolver.nameservers = ['127.0.0.1'] 432 | resolver.port = 53 433 | logger.info(f"使用 127.0.0.1:53 解析 {domain}") 434 | answers = resolver.resolve(domain, 'A') 435 | if answers: 436 | return answers[0].address 437 | except Exception as e: 438 | logger.error(f"解析域名 {domain} 出错: {str(e)}") 439 | return None 440 | 441 | def restart_service(): 442 | """重启 SmartDNS 服务""" 443 | try: 444 | subprocess.run(['systemctl', 'restart', SERVICE_NAME], check=True) 445 | return True, "服务重启成功" 446 | except Exception as e: 447 | return False, f"服务重启失败: {str(e)}" 448 | 449 | def backup_config(): 450 | """备份配置文件""" 451 | try: 452 | timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') 453 | backup_path = os.path.join(CONFIG_BACKUP_DIR, f'smartdns_{timestamp}.bak') 454 | shutil.copy2(CONFIG_FILE, backup_path) 455 | return True, f"配置已备份到 {backup_path}" 456 | except Exception as e: 457 | return False, f"备份失败: {str(e)}" 458 | 459 | def restore_config(backup_file): 460 | """从备份文件还原配置""" 461 | try: 462 | backup_path = os.path.join(CONFIG_BACKUP_DIR, backup_file) 463 | if os.path.exists(backup_path): 464 | shutil.copy2(backup_path, CONFIG_FILE) 465 | def delayed_restart(): 466 | time.sleep(1) 467 | success, restart_message = restart_service() 468 | if not success: 469 | logger.error(f"服务重启失败: {restart_message}") 470 | threading.Thread(target=delayed_restart, daemon=True).start() 471 | return True, "配置还原成功,服务正在重启" 472 | else: 473 | return False, "备份文件不存在" 474 | except Exception as e: 475 | return False, f"还原失败: {str(e)}" 476 | 477 | def test_dns_resolution(domain="www.google.com"): 478 | """测试 DNS 解析""" 479 | try: 480 | result = subprocess.run(['nslookup', domain, '127.0.0.1'], capture_output=True, text=True, timeout=5) 481 | output = result.stdout 482 | logger.info(f"DNS 测试输出已获取") 483 | ip_addresses = [] 484 | found_answer_section = False 485 | for line in output.splitlines(): 486 | line = line.strip() 487 | if line.startswith("Non-authoritative answer:"): 488 | found_answer_section = True 489 | continue 490 | if found_answer_section and line.startswith("Address:"): 491 | ip = line.split()[-1] 492 | if ip != "127.0.0.1" and "#53" not in ip: 493 | ip_addresses.append(ip) 494 | if ip_addresses: 495 | # 限制最多显示前两个 IP 地址 496 | displayed_ips = ip_addresses[:2] 497 | total_ips = len(ip_addresses) 498 | if total_ips > 2: 499 | return True, f"DNS 解析成功: {domain} to {', '.join(displayed_ips)} (共 {total_ips} 个结果,仅显示前 2 个)" 500 | return True, f"DNS 解析成功: {domain} to {', '.join(displayed_ips)}" 501 | return False, f"DNS 解析失败: 没有有效结果\n{output}" 502 | except subprocess.TimeoutExpired: 503 | return False, "DNS 解析失败: 请求超时" 504 | except Exception as e: 505 | logger.error(f"DNS 测试出错: {str(e)}") 506 | return False, f"DNS 解析失败: {str(e)}" 507 | 508 | 509 | @app.route('/') 510 | def index(): 511 | config = read_config() 512 | return render_template('index.html', config=config) 513 | 514 | @app.route('/get_domain_content/', methods=['GET']) 515 | def get_domain_content(index): 516 | try: 517 | config = read_config() 518 | if 0 <= index < len(config['domain_sets']): 519 | domain_set = config['domain_sets'][index] 520 | if domain_set['domain_count'] > 1000: 521 | return jsonify({'status': 'error', 'message': '域名表过大,无法加载'}) 522 | if os.path.exists(domain_set['file']): 523 | with open(domain_set['file'], 'r', encoding='utf-8') as f: 524 | content = f.read() 525 | return jsonify({'status': 'success', 'content': content}) 526 | else: 527 | return jsonify({'status': 'error', 'message': '文件不存在'}) 528 | else: 529 | return jsonify({'status': 'error', 'message': '无效的域名组索引'}) 530 | except Exception as e: 531 | return jsonify({'status': 'error', 'message': f'加载内容出错: {str(e)}'}) 532 | 533 | @app.route('/update', methods=['POST']) 534 | def update_config(): 535 | try: 536 | config = read_config() 537 | config['bind']['port'] = request.form.get('bind_port', '5353') 538 | config['bind']['tcp_port'] = request.form.get('bind_tcp_port', '5353') 539 | config['cache']['enabled'] = request.form.get('cache_enabled', 'no') 540 | config['cache']['size'] = request.form.get('cache_size', '32768') 541 | config['cache']['persist'] = request.form.get('cache_persist', 'yes') 542 | config['cache']['file'] = request.form.get('cache_file', '/etc/smartdns/cache.db') 543 | config['cache']['checkpoint_time'] = request.form.get('cache_checkpoint_time', '600') 544 | config['prefetch']['enabled'] = request.form.get('prefetch_enabled', 'yes') 545 | config['expired']['enabled'] = request.form.get('expired_enabled', 'yes') 546 | config['expired']['ttl'] = request.form.get('expired_ttl', '600') 547 | config['expired']['reply_ttl'] = request.form.get('expired_reply_ttl', '1') 548 | config['expired']['prefetch_time'] = request.form.get('expired_prefetch_time', '1200') 549 | config['ipv6']['force_aaaa_soa'] = request.form.get('force_aaaa_soa', 'yes') 550 | write_config(config) 551 | def delayed_restart(): 552 | time.sleep(1) 553 | success, restart_message = restart_service() 554 | if not success: 555 | logger.error(f"服务重启失败: {restart_message}") 556 | threading.Thread(target=delayed_restart, daemon=True).start() 557 | flash('配置更新成功,SmartDNS 服务已重启!', 'success') 558 | except Exception as e: 559 | flash(f'更新配置出错:{str(e)}', 'success') 560 | return redirect(url_for('index')) 561 | 562 | @app.route('/add_server', methods=['POST']) 563 | def add_server(): 564 | try: 565 | config = read_config() 566 | server_address = request.form.get('server_address') 567 | server_group = request.form.get('server_group', '通用') 568 | if server_address: 569 | server_type = infer_server_type(server_address) 570 | for server in config['servers']: 571 | if server['group'] == server_group and server['type'] == server_type: 572 | server['addresses'].append(server_address) 573 | break 574 | else: 575 | config['servers'].append({ 576 | 'type': server_type, 577 | 'group': server_group, 578 | 'addresses': [server_address] 579 | }) 580 | write_config(config) 581 | def delayed_restart(): 582 | time.sleep(1) 583 | success, restart_message = restart_service() 584 | if not success: 585 | logger.error(f"服务重启失败: {restart_message}") 586 | threading.Thread(target=delayed_restart, daemon=True).start() 587 | flash('服务器添加成功,SmartDNS 服务已重启!', 'success') 588 | else: 589 | flash('服务器地址不能为空!', 'success') 590 | except Exception as e: 591 | flash(f'添加服务器出错:{str(e)}', 'success') 592 | return redirect(url_for('index')) 593 | 594 | @app.route('/delete_server/') 595 | def delete_server(index): 596 | try: 597 | config = read_config() 598 | if 0 <= index < len(config['servers']): 599 | config['servers'].pop(index) 600 | write_config(config) 601 | def delayed_restart(): 602 | time.sleep(1) 603 | success, restart_message = restart_service() 604 | if not success: 605 | logger.error(f"服务重启失败: {restart_message}") 606 | threading.Thread(target=delayed_restart, daemon=True).start() 607 | flash('服务器删除成功,SmartDNS 服务已重启!', 'success') 608 | else: 609 | flash('无效的服务器索引!', 'success') 610 | except Exception as e: 611 | flash(f'删除服务器出错:{str(e)}', 'success') 612 | return redirect(url_for('index')) 613 | 614 | @app.route('/update_server/', methods=['POST']) 615 | def update_server(index): 616 | try: 617 | config = read_config() 618 | if 0 <= index < len(config['servers']): 619 | addresses = request.form.get('addresses', '').split(',') 620 | addresses = [addr.strip() for addr in addresses if addr.strip()] 621 | group = request.form.get('group', '通用') 622 | if not addresses: 623 | return jsonify({'status': 'error', 'message': '服务器地址不能为空', 'restarted': False}) 624 | server_type = infer_server_type(addresses[0]) 625 | config['servers'][index] = { 626 | 'type': server_type, 627 | 'group': group, 628 | 'addresses': addresses 629 | } 630 | write_config(config) 631 | def delayed_restart(): 632 | time.sleep(1) 633 | success, restart_message = restart_service() 634 | if not success: 635 | logger.error(f"服务重启失败: {restart_message}") 636 | threading.Thread(target=delayed_restart, daemon=True).start() 637 | return jsonify({'status': 'success', 'message': '已保存,服务正在重启', 'restarted': True}) 638 | else: 639 | return jsonify({'status': 'error', 'message': '无效的服务器索引', 'restarted': False}) 640 | except Exception as e: 641 | return jsonify({'status': 'error', 'message': f'更新服务器出错: {str(e)}', 'restarted': False}) 642 | 643 | @app.route('/add_domain_set', methods=['POST']) 644 | def add_domain_set(): 645 | try: 646 | config = read_config() 647 | friendly_name = request.form.get('friendly_name') 648 | source_url = request.form.get('source_url', '') 649 | speed_check_mode = request.form.get('speed_check_mode', 'ping') 650 | response_mode = request.form.get('response_mode', 'fastest') 651 | address_ipv6 = request.form.get('address_ipv6', 'no') == 'yes' 652 | update_frequency = request.form.get('update_frequency', 'none') 653 | update_time = request.form.get('update_time', '') 654 | update_day = request.form.get('update_day', '') 655 | if friendly_name: 656 | name = f"{friendly_name.lower().replace(' ', '-')}-domain-list" 657 | file_path = generate_domain_filename(friendly_name) 658 | domain_set = { 659 | 'name': name, 660 | 'friendly_name': friendly_name, 661 | 'file': file_path, 662 | 'group': friendly_name, 663 | 'source_url': source_url, 664 | 'domain_count': 0, 665 | 'last_updated': '尚未更新', 666 | 'speed_check_mode': speed_check_mode, 667 | 'response_mode': response_mode, 668 | 'address_ipv6': address_ipv6, 669 | 'update_schedule': { 670 | 'frequency': update_frequency, 671 | 'time': update_time, 672 | 'day': update_day if update_frequency == 'weekly' else '' 673 | } 674 | } 675 | config['domain_sets'].append(domain_set) 676 | write_config(config) 677 | def delayed_restart(): 678 | time.sleep(1) 679 | success, restart_message = restart_service() 680 | if not success: 681 | logger.error(f"服务重启失败: {restart_message}") 682 | threading.Thread(target=delayed_restart, daemon=True).start() 683 | flash('域名组添加成功,SmartDNS 服务已重启!', 'success') 684 | else: 685 | flash('域名组名称不能为空!', 'success') 686 | except Exception as e: 687 | flash(f'添加域名组出错:{str(e)}', 'success') 688 | return redirect(url_for('index')) 689 | 690 | @app.route('/delete_domain_set/') 691 | def delete_domain_set(index): 692 | try: 693 | config = read_config() 694 | if 0 <= index < len(config['domain_sets']): 695 | config['domain_sets'].pop(index) 696 | write_config(config) 697 | def delayed_restart(): 698 | time.sleep(1) 699 | success, restart_message = restart_service() 700 | if not success: 701 | logger.error(f"服务重启失败: {restart_message}") 702 | threading.Thread(target=delayed_restart, daemon=True).start() 703 | flash('域名组删除成功,SmartDNS 服务已重启!', 'success') 704 | else: 705 | flash('无效的域名组索引!', 'success') 706 | except Exception as e: 707 | flash(f'删除域名组出错:{str(e)}', 'success') 708 | return redirect(url_for('index')) 709 | 710 | @app.route('/update_domain_set/', methods=['POST']) 711 | def update_domain_set(index): 712 | try: 713 | config = read_config() 714 | if 0 <= index < len(config['domain_sets']): 715 | friendly_name = request.form.get('friendly_name', '') 716 | if not friendly_name: 717 | return jsonify({'status': 'error', 'message': '域名组名称不能为空', 'restarted': False}) 718 | update_frequency = request.form.get('update_frequency', 'none') 719 | update_time = request.form.get('update_time', '') 720 | update_day = request.form.get('update_day', '') 721 | config['domain_sets'][index]['friendly_name'] = friendly_name 722 | config['domain_sets'][index]['name'] = f"{friendly_name.lower().replace(' ', '-')}-domain-list" 723 | config['domain_sets'][index]['file'] = generate_domain_filename(friendly_name) 724 | config['domain_sets'][index]['source_url'] = request.form.get('source_url', '') 725 | config['domain_sets'][index]['group'] = friendly_name 726 | config['domain_sets'][index]['speed_check_mode'] = request.form.get('speed_check_mode', 'ping') 727 | config['domain_sets'][index]['response_mode'] = request.form.get('response_mode', 'fastest') 728 | config['domain_sets'][index]['address_ipv6'] = request.form.get('address_ipv6', 'no') == 'yes' 729 | config['domain_sets'][index]['update_schedule'] = { 730 | 'frequency': update_frequency, 731 | 'time': update_time, 732 | 'day': update_day if update_frequency == 'weekly' else '' 733 | } 734 | content = request.form.get('content', '') 735 | if content and config['domain_sets'][index]['domain_count'] <= 1000: 736 | is_valid, validation_message = validate_domains(content) 737 | if not is_valid: 738 | return jsonify({'status': 'error', 'message': validation_message, 'restarted': False}) 739 | try: 740 | with open(config['domain_sets'][index]['file'], 'w', encoding='utf-8') as f: 741 | f.write(content) 742 | mtime = os.path.getmtime(config['domain_sets'][index]['file']) 743 | config['domain_sets'][index]['last_updated'] = datetime.fromtimestamp(mtime).strftime('%Y-%m-%d %H:%M:%S') 744 | config['domain_sets'][index]['domain_count'] = len([line for line in content.splitlines() if line.strip() and not line.startswith('#')]) 745 | except Exception as e: 746 | return jsonify({'status': 'error', 'message': f'保存文件内容出错:{str(e)}', 'restarted': False}) 747 | write_config(config) 748 | def delayed_restart(): 749 | time.sleep(1) 750 | success, restart_message = restart_service() 751 | if not success: 752 | logger.error(f"服务重启失败: {restart_message}") 753 | threading.Thread(target=delayed_restart, daemon=True).start() 754 | return jsonify({'status': 'success', 'message': '已保存,服务正在重启', 'restarted': True}) 755 | else: 756 | return jsonify({'status': 'error', 'message': '无效的域名组索引', 'restarted': False}) 757 | except Exception as e: 758 | return jsonify({'status': 'error', 'message': f'更新域名组出错: {str(e)}', 'restarted': False}) 759 | 760 | @app.route('/update_domain_content/', methods=['POST']) 761 | def update_domain_content(index): 762 | try: 763 | config = read_config() 764 | if 0 <= index < len(config['domain_sets']): 765 | url = request.form.get('url', '') 766 | if not url: 767 | return jsonify({'status': 'error', 'message': 'URL 不能为空', 'restarted': False}) 768 | success, message, restarted = update_domain_content_by_index(index, url) 769 | if success: 770 | content = '' 771 | if os.path.exists(config['domain_sets'][index]['file']): 772 | with open(config['domain_sets'][index]['file'], 'r', encoding='utf-8') as f: 773 | content = f.read() 774 | return jsonify({'status': 'success', 'message': message + (', 服务正在重启' if restarted else ''), 'content': content, 'restarted': restarted}) 775 | else: 776 | return jsonify({'status': 'error', 'message': message, 'restarted': False}) 777 | else: 778 | return jsonify({'status': 'error', 'message': '无效的域名组索引', 'restarted': False}) 779 | except Exception as e: 780 | return jsonify({'status': 'error', 'message': f'更新内容出错: {str(e)}', 'restarted': False}) 781 | 782 | @app.route('/backup', methods=['POST']) 783 | def backup(): 784 | success, message = backup_config() 785 | flash(message, 'success') 786 | return redirect(url_for('index')) 787 | 788 | @app.route('/restore', methods=['POST']) 789 | def restore(): 790 | backup_file = request.form.get('backup_file', '') 791 | if not backup_file: 792 | flash("未选择备份文件!", 'success') 793 | return redirect(url_for('index')) 794 | success, message = restore_config(backup_file) 795 | flash(message, 'success') 796 | return redirect(url_for('index')) 797 | 798 | @app.route('/restart', methods=['POST']) 799 | def restart(): 800 | success, message = restart_service() 801 | flash(message, 'success') 802 | return redirect(url_for('index')) 803 | 804 | @app.route('/test_dns', methods=['POST']) 805 | def test_dns(): 806 | domain = request.form.get('test_domain', 'www.google.com') 807 | success, message = test_dns_resolution(domain) 808 | flash(message, 'success') 809 | return redirect(url_for('index')) 810 | 811 | @app.route('/backups', methods=['GET']) 812 | def list_backups(): 813 | try: 814 | backups = os.listdir(CONFIG_BACKUP_DIR) 815 | return jsonify({'status': 'success', 'backups': backups}) 816 | except Exception as e: 817 | return jsonify({'status': 'error', 'message': str(e)}) 818 | 819 | if __name__ == '__main__': 820 | logger.info("应用启动,启动自定义调度器") 821 | app.run(host='0.0.0.0', port=8088, debug=True) -------------------------------------------------------------------------------- /SmartDash/install_smartdash.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 安装目录 4 | INSTALL_DIR="/opt/smartdash" 5 | TEMPLATE_DIR="${INSTALL_DIR}/templates" 6 | SERVICE_NAME="smartdash" 7 | SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" 8 | PORT=8088 9 | SMARTDNS_CONF="/etc/smartdns/smartdns.conf" 10 | 11 | # 文件下载地址(使用 GitHub 原始链接) 12 | APP_PY_URL="https://raw.githubusercontent.com/LidaoNote/OpenCode/refs/heads/main/SmartDash/app.py" 13 | INDEX_HTML_URL="https://raw.githubusercontent.com/LidaoNote/OpenCode/refs/heads/main/SmartDash/templates/index.html" 14 | 15 | # 目标文件路径 16 | APP_PY_PATH="${INSTALL_DIR}/app.py" 17 | INDEX_HTML_PATH="${TEMPLATE_DIR}/index.html" 18 | 19 | # 所需的 Python 模块 20 | REQUIRED_MODULES=("flask" "flask-bootstrap" "requests" "dnspython" "schedule") 21 | 22 | # 颜色输出函数 23 | RED='\033[0;31m' 24 | GREEN='\033[0;32m' 25 | YELLOW='\033[1;33m' 26 | NC='\033[0m' # No Color 27 | echo_red() { echo -e "${RED}$1${NC}"; } 28 | echo_green() { echo -e "${GREEN}$1${NC}"; } 29 | echo_yellow() { echo -e "${YELLOW}$1${NC}"; } 30 | 31 | # 检查系统类型以确定包管理器 32 | PACKAGE_MANAGER="" 33 | if command -v apt &> /dev/null; then 34 | PACKAGE_MANAGER="apt" 35 | elif command -v yum &> /dev/null; then 36 | PACKAGE_MANAGER="yum" 37 | elif command -v dnf &> /dev/null; then 38 | PACKAGE_MANAGER="dnf" 39 | else 40 | echo_red "错误:未找到支持的包管理器(apt/yum/dnf),无法自动安装依赖。" 41 | exit 1 42 | fi 43 | 44 | # 检查是否安装了 smartdns 及其配置文件 45 | check_smartdns() { 46 | echo "检查是否安装 SmartDNS..." 47 | if ! command -v smartdns &> /dev/null; then 48 | echo_red "错误:未找到 SmartDNS 服务。请先安装 SmartDNS,然后再安装 SmartDash。" 49 | echo_red "SmartDash 是一个 SmartDNS 的外部面板程序。" 50 | exit 1 51 | else 52 | echo_green "SmartDNS 服务已安装。" 53 | fi 54 | 55 | if [ ! -f "$SMARTDNS_CONF" ]; then 56 | echo_red "错误:未找到 SmartDNS 配置文件 ${SMARTDNS_CONF}。" 57 | echo_red "请确保 SmartDNS 已正确安装并配置,然后再安装 SmartDash。" 58 | exit 1 59 | else 60 | echo_green "SmartDNS 配置文件 ${SMARTDNS_CONF} 已存在。" 61 | fi 62 | } 63 | 64 | # 检查 Python 环境 65 | check_python_env() { 66 | local env_ok=true 67 | echo "检查 Python 环境..." 68 | if ! command -v python3 &> /dev/null; then 69 | echo_yellow "Python3 未安装。" 70 | env_ok=false 71 | else 72 | echo_green "Python3 已安装:$(python3 --version)" 73 | fi 74 | 75 | if ! command -v pip3 &> /dev/null; then 76 | echo_yellow "pip3 未安装。" 77 | env_ok=false 78 | else 79 | echo_green "pip3 已安装:$(pip3 --version)" 80 | fi 81 | 82 | if [ "$env_ok" = false ]; then 83 | echo_yellow "环境不适合运行 SmartDash,是否安装依赖?(y/n)" 84 | read -r response 85 | if [[ ! "$response" =~ ^[Yy]$ ]]; then 86 | echo_red "未安装依赖,退出程序。" 87 | exit 1 88 | fi 89 | install_system_deps 90 | else 91 | echo_green "环境已满足基本要求,检查 Python 模块..." 92 | fi 93 | } 94 | 95 | # 安装系统依赖 96 | install_system_deps() { 97 | echo "安装系统依赖..." 98 | if [ "$PACKAGE_MANAGER" = "apt" ]; then 99 | sudo apt update 100 | sudo apt install -y python3 python3-pip 101 | elif [ "$PACKAGE_MANAGER" = "yum" ] || [ "$PACKAGE_MANAGER" = "dnf" ]; then 102 | sudo $PACKAGE_MANAGER install -y python3 python3-pip 103 | fi 104 | if [ $? -ne 0 ]; then 105 | echo_red "错误:安装系统依赖失败,请手动安装 python3 和 python3-pip。" 106 | exit 1 107 | fi 108 | echo_green "系统依赖安装成功。" 109 | } 110 | 111 | # 检查并安装 Python 模块 112 | check_python_modules() { 113 | local modules_missing=false 114 | echo "检查必要的 Python 模块..." 115 | for module in "${REQUIRED_MODULES[@]}"; do 116 | if ! pip3 show "$module" &> /dev/null; then 117 | echo_yellow "模块 $module 未安装。" 118 | modules_missing=true 119 | else 120 | echo_green "模块 $module 已安装。" 121 | fi 122 | done 123 | 124 | if [ "$modules_missing" = true ]; then 125 | echo_yellow "部分 Python 模块未安装,是否立即安装?(y/n)" 126 | read -r response 127 | if [[ ! "$response" =~ ^[Yy]$ ]]; then 128 | echo_red "未安装缺失的 Python 模块,退出程序。" 129 | exit 1 130 | fi 131 | install_python_modules 132 | else 133 | echo_green "所有必要的 Python 模块已安装。" 134 | fi 135 | } 136 | 137 | # 安装 Python 模块 138 | install_python_modules() { 139 | echo "安装必要的 Python 模块..." 140 | # 检测是否为 Debian 12 系统 141 | USE_BREAK_SYSTEM_PACKAGES="" 142 | if [ -f /etc/os-release ]; then 143 | . /etc/os-release 144 | if [[ "$ID" == "debian" && "$VERSION_ID" == "12" ]]; then 145 | echo_yellow "检测到 Debian 12 系统,将使用 --break-system-packages 参数安装 Python 模块..." 146 | USE_BREAK_SYSTEM_PACKAGES="--break-system-packages" 147 | fi 148 | fi 149 | 150 | for module in "${REQUIRED_MODULES[@]}"; do 151 | if ! pip3 show "$module" &> /dev/null; then 152 | echo_yellow "安装模块 $module..." 153 | # 根据系统版本决定是否使用 --break-system-packages 154 | if [ -n "$USE_BREAK_SYSTEM_PACKAGES" ]; then 155 | pip3 install "$module" --user $USE_BREAK_SYSTEM_PACKAGES 156 | if [ $? -ne 0 ]; then 157 | echo_red "错误:安装 $module 失败,尝试使用 sudo 安装..." 158 | sudo pip3 install "$module" $USE_BREAK_SYSTEM_PACKAGES 159 | if [ $? -ne 0 ]; then 160 | echo_red "错误:安装 $module 失败,请手动安装。" 161 | exit 1 162 | fi 163 | fi 164 | else 165 | pip3 install "$module" --user 166 | if [ $? -ne 0 ]; then 167 | echo_red "错误:安装 $module 失败,尝试使用 sudo 安装..." 168 | sudo pip3 install "$module" 169 | if [ $? -ne 0 ]; then 170 | echo_red "错误:安装 $module 失败,请手动安装。" 171 | exit 1 172 | fi 173 | fi 174 | fi 175 | echo_green "模块 $module 安装成功。" 176 | fi 177 | done 178 | } 179 | 180 | # 检查服务是否存在 181 | check_service() { 182 | if systemctl is-active --quiet "$SERVICE_NAME"; then 183 | echo_yellow "警告:服务 ${SERVICE_NAME} 已存在且正在运行。" 184 | return 1 185 | elif [ -f "$SERVICE_FILE" ]; then 186 | echo_yellow "警告:服务 ${SERVICE_NAME} 已存在但未运行。" 187 | return 1 188 | else 189 | echo_green "服务 ${SERVICE_NAME} 未存在,可以安装。" 190 | return 0 191 | fi 192 | } 193 | 194 | # 检查端口是否被占用 195 | check_port() { 196 | if lsof -i :$PORT &> /dev/null; then 197 | echo_yellow "警告:端口 ${PORT} 已被占用。" 198 | echo_yellow "占用端口的进程信息:" 199 | lsof -i :$PORT 200 | return 1 201 | else 202 | echo_green "端口 ${PORT} 未被占用,可以使用。" 203 | return 0 204 | fi 205 | } 206 | 207 | # 安装 SmartDash 服务 208 | install_service() { 209 | echo "正在安装 ${SERVICE_NAME} 服务..." 210 | if [ ! -f "$APP_PY_PATH" ]; then 211 | echo "应用文件未安装,正在下载和安装 SmartDash 应用文件..." 212 | install_app_files 213 | if [ $? -ne 0 ]; then 214 | echo_red "错误:应用文件安装失败,无法安装服务。" 215 | return 1 216 | fi 217 | fi 218 | 219 | # 创建服务文件 220 | cat > "$SERVICE_FILE" << EOF 221 | [Unit] 222 | Description=SmartDash 223 | After=network.target 224 | 225 | [Service] 226 | Type=simple 227 | User=root 228 | WorkingDirectory=${INSTALL_DIR} 229 | ExecStart=/usr/bin/python3 ${APP_PY_PATH} 230 | Restart=always 231 | RestartSec=10 232 | SyslogIdentifier=SmartDash 233 | Environment=FLASK_ENV=production 234 | 235 | [Install] 236 | WantedBy=multi-user.target 237 | EOF 238 | 239 | if [ $? -ne 0 ]; then 240 | echo_red "错误:创建服务文件失败,请检查权限。" 241 | return 1 242 | fi 243 | 244 | # 重新加载服务配置 245 | sudo systemctl daemon-reload 246 | if [ $? -ne 0 ]; then 247 | echo_red "错误:重新加载服务配置失败。" 248 | return 1 249 | fi 250 | 251 | # 启用服务 252 | sudo systemctl enable "$SERVICE_NAME" 253 | if [ $? -ne 0 ]; then 254 | echo_red "错误:启用服务失败。" 255 | return 1 256 | fi 257 | 258 | # 启动服务 259 | sudo systemctl start "$SERVICE_NAME" 260 | if [ $? -ne 0 ]; then 261 | echo_red "错误:启动服务失败,请查看日志:journalctl -u ${SERVICE_NAME}" 262 | return 1 263 | fi 264 | 265 | echo_green "服务 ${SERVICE_NAME} 安装并启动成功!" 266 | echo_green "访问地址:http://<你的IP>:${PORT}" 267 | return 0 268 | } 269 | 270 | # 卸载 SmartDash 服务 271 | uninstall_service() { 272 | echo "正在卸载 ${SERVICE_NAME} 服务..." 273 | if systemctl is-active --quiet "$SERVICE_NAME"; then 274 | sudo systemctl stop "$SERVICE_NAME" 275 | if [ $? -ne 0 ]; then 276 | echo_red "错误:停止服务失败,请手动停止。" 277 | else 278 | echo_green "服务已停止。" 279 | fi 280 | fi 281 | 282 | if [ -f "$SERVICE_FILE" ]; then 283 | sudo systemctl disable "$SERVICE_NAME" 284 | sudo rm -f "$SERVICE_FILE" 285 | sudo systemctl daemon-reload 286 | if [ $? -ne 0 ]; then 287 | echo_red "错误:卸载服务失败,请手动删除 ${SERVICE_FILE}。" 288 | else 289 | echo_green "服务文件已删除。" 290 | fi 291 | else 292 | echo_yellow "服务文件不存在,无需卸载服务。" 293 | fi 294 | 295 | echo "是否删除应用文件和目录 ${INSTALL_DIR}?(y/n)" 296 | read -r response 297 | if [[ "$response" =~ ^[Yy]$ ]]; then 298 | sudo rm -rf "$INSTALL_DIR" 299 | if [ $? -ne 0 ]; then 300 | echo_red "错误:删除应用目录失败,请手动删除。" 301 | else 302 | echo_green "应用目录已删除。" 303 | fi 304 | fi 305 | echo_green "服务卸载完成!" 306 | } 307 | 308 | # 安装应用文件 309 | install_app_files() { 310 | # 检查安装目录是否存在 311 | if [ -d "$INSTALL_DIR" ]; then 312 | echo_yellow "警告:安装目录 ${INSTALL_DIR} 已存在。" 313 | echo_yellow "继续安装可能导致同名文件被覆盖。是否继续?(y/n)" 314 | read -r response 315 | if [[ ! "$response" =~ ^[Yy]$ ]]; then 316 | echo_red "安装已取消。" 317 | return 1 318 | fi 319 | else 320 | echo "创建安装目录 ${INSTALL_DIR}..." 321 | mkdir -p "$INSTALL_DIR" 322 | if [ $? -ne 0 ]; then 323 | echo_red "错误:无法创建目录 ${INSTALL_DIR},请检查权限。" 324 | return 1 325 | fi 326 | fi 327 | 328 | # 检查模板目录是否存在 329 | if [ ! -d "$TEMPLATE_DIR" ]; then 330 | echo "创建模板目录 ${TEMPLATE_DIR}..." 331 | mkdir -p "$TEMPLATE_DIR" 332 | if [ $? -ne 0 ]; then 333 | echo_red "错误:无法创建目录 ${TEMPLATE_DIR},请检查权限。" 334 | return 1 335 | fi 336 | fi 337 | 338 | # 检查 Python 环境和模块 339 | check_python_env 340 | check_python_modules 341 | 342 | # 检查是否安装了 curl 或 wget 343 | DOWNLOAD_TOOL="" 344 | if command -v curl &> /dev/null; then 345 | DOWNLOAD_TOOL="curl" 346 | elif command -v wget &> /dev/null; then 347 | DOWNLOAD_TOOL="wget" 348 | else 349 | echo_red "错误:未找到 curl 或 wget,无法下载文件。请先安装其中一个工具。" 350 | return 1 351 | fi 352 | 353 | # 下载 app.py 354 | echo "正在下载 app.py 到 ${APP_PY_PATH}..." 355 | if [ "$DOWNLOAD_TOOL" = "curl" ]; then 356 | curl -sS -o "$APP_PY_PATH" "$APP_PY_URL" 357 | elif [ "$DOWNLOAD_TOOL" = "wget" ]; then 358 | wget -q -O "$APP_PY_PATH" "$APP_PY_URL" 359 | fi 360 | if [ $? -ne 0 ]; then 361 | echo_red "错误:下载 app.py 失败,请检查网络连接或 URL 是否有效。" 362 | return 1 363 | fi 364 | echo_green "app.py 下载完成。" 365 | 366 | # 下载 index.html 367 | echo "正在下载 index.html 到 ${INDEX_HTML_PATH}..." 368 | if [ "$DOWNLOAD_TOOL" = "curl" ]; then 369 | curl -sS -o "$INDEX_HTML_PATH" "$INDEX_HTML_URL" 370 | elif [ "$DOWNLOAD_TOOL" = "wget" ]; then 371 | wget -q -O "$INDEX_HTML_PATH" "$INDEX_HTML_URL" 372 | fi 373 | if [ $? -ne 0 ]; then 374 | echo_red "错误:下载 index.html 失败,请检查网络连接或 URL 是否有效。" 375 | return 1 376 | fi 377 | echo_green "index.html 下载完成。" 378 | 379 | # 设置文件权限 380 | echo "设置文件权限..." 381 | chmod 755 "$APP_PY_PATH" 382 | if [ $? -ne 0 ]; then 383 | echo_yellow "警告:设置 app.py 权限失败,请手动检查。" 384 | fi 385 | chmod 644 "$INDEX_HTML_PATH" 386 | if [ $? -ne 0 ]; then 387 | echo_yellow "警告:设置 index.html 权限失败,请手动检查。" 388 | fi 389 | 390 | echo_green "应用文件安装完成!文件已下载到以下位置:" 391 | echo_green " - ${APP_PY_PATH}" 392 | echo_green " - ${INDEX_HTML_PATH}" 393 | return 0 394 | } 395 | 396 | # 主菜单 397 | show_menu() { 398 | echo "" 399 | echo "===== SmartDash 安装与管理工具 =====" 400 | echo "1. 检查依赖环境" 401 | echo "2. 安装 SmartDash 为系统服务" 402 | echo "3. 卸载 SmartDash 服务" 403 | echo "4. 退出" 404 | echo "=====================================" 405 | echo "请选择操作(1-4):" 406 | } 407 | 408 | # 检查 SmartDNS 前提条件 409 | check_smartdns 410 | 411 | # 主程序 412 | while true; do 413 | show_menu 414 | read -r choice 415 | case $choice in 416 | 1) 417 | check_python_env 418 | check_python_modules 419 | ;; 420 | 2) 421 | echo "检查服务和端口..." 422 | check_service 423 | service_exists=$? 424 | check_port 425 | port_free=$? 426 | if [ $service_exists -eq 1 ] || [ $port_free -eq 1 ]; then 427 | echo_yellow "警告:服务或端口存在冲突,是否继续安装服务?(y/n)" 428 | read -r response 429 | if [[ ! "$response" =~ ^[Yy]$ ]]; then 430 | echo_red "服务安装已取消。" 431 | continue 432 | fi 433 | fi 434 | install_service 435 | ;; 436 | 3) 437 | uninstall_service 438 | ;; 439 | 4) 440 | echo_green "退出程序。" 441 | exit 0 442 | ;; 443 | *) 444 | echo_red "无效选项,请选择 1-4。" 445 | ;; 446 | esac 447 | done 448 | -------------------------------------------------------------------------------- /agh_admin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import bcrypt 3 | import yaml 4 | import os 5 | import subprocess 6 | 7 | def generate_bcrypt_password(password: str) -> str: 8 | """生成 bcrypt 加密的密码。""" 9 | salt = bcrypt.gensalt(rounds=10) 10 | hashed_password = bcrypt.hashpw(password.encode('utf-8'), salt) 11 | return hashed_password.decode('utf-8') 12 | 13 | def update_adguard_credentials(yaml_file: str, new_username: str, new_password: str): 14 | """更新 AdGuardHome.yaml 文件中的用户名和密码。""" 15 | with open(yaml_file, 'r') as file: 16 | config = yaml.safe_load(file) 17 | 18 | # 更新用户名和密码 19 | config['users'][0]['name'] = new_username 20 | config['users'][0]['password'] = generate_bcrypt_password(new_password) 21 | 22 | with open(yaml_file, 'w') as file: 23 | yaml.dump(config, file) 24 | 25 | print("管理员账号和密码已更新。") 26 | 27 | def restart_adguard_service(): 28 | """重启 AdGuardHome 服务。""" 29 | try: 30 | subprocess.run(['systemctl', 'restart', 'AdGuardHome'], check=True) 31 | print("AdGuardHome 服务已重启。") 32 | except subprocess.CalledProcessError as e: 33 | print(f"重启 AdGuardHome 服务失败: {e}") 34 | 35 | if __name__ == "__main__": 36 | yaml_file_path = '/opt/AdGuardHome/AdGuardHome.yaml' 37 | 38 | if not os.path.exists(yaml_file_path): 39 | print(f"错误: {yaml_file_path} 文件不存在。") 40 | exit(1) 41 | 42 | new_username = input("请输入新的管理员用户名: ") 43 | new_password = input("请输入新的管理员密码: ") 44 | 45 | update_adguard_credentials(yaml_file_path, new_username, new_password) 46 | restart_adguard_service() -------------------------------------------------------------------------------- /bcrypt10.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import bcrypt 3 | 4 | def generate_bcrypt_password(password: str) -> str: 5 | # 生成盐,设置轮数为10 6 | salt = bcrypt.gensalt(rounds=10) 7 | # 哈希密码 8 | hashed_password = bcrypt.hashpw(password.encode('utf-8'), salt) 9 | return hashed_password.decode('utf-8') 10 | 11 | # 示例用法 12 | if __name__ == "__main__": 13 | plain_password = input("请输入要加密的密码: ") 14 | hashed = generate_bcrypt_password(plain_password) 15 | print(f"加密后的密码: {hashed}") -------------------------------------------------------------------------------- /block.txt: -------------------------------------------------------------------------------- 1 | ||ikuai8.com^ -------------------------------------------------------------------------------- /dns.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 确保脚本以 root 用户身份运行 4 | if [ "$EUID" -ne 0 ]; then 5 | echo "请以 root 用户身份运行此脚本" 6 | exit 1 7 | fi 8 | 9 | # 检测系统架构 10 | ARCH=$(uname -m) 11 | case "$ARCH" in 12 | x86_64) 13 | SMARTDNS_ARCH="x86_64" 14 | INSTALL_METHOD="tar" 15 | ;; 16 | aarch64) 17 | SMARTDNS_ARCH="aarch64" 18 | INSTALL_METHOD="deb" # Using aarch64-debian-all.deb 19 | ;; 20 | arm*) 21 | SMARTDNS_ARCH="arm" 22 | INSTALL_METHOD="deb" 23 | ;; 24 | *) 25 | echo "不支持的架构: $ARCH" 26 | exit 1 27 | ;; 28 | esac 29 | 30 | # 菜单选择 31 | show_menu() { 32 | echo "请选择操作选项:" 33 | echo "1) 安装更新和依赖" 34 | echo "2) 安装 SmartDNS" 35 | echo "3) 安装 SmartDNS 和 AdGuardHome" 36 | echo "4) 卸载 SmartDNS" 37 | echo "5) 卸载 AdGuardHome" 38 | read -p "输入选项 (1/2/3/4/5): " choice 39 | } 40 | 41 | # 安装所需软件 42 | install_dependencies() { 43 | echo "正在安装更新和所需软件..." 44 | apt update 45 | apt install -y wget curl net-tools sed jq dpkg libssl1.1 46 | if [ $? -ne 0 ]; then 47 | echo "依赖安装失败,尝试添加旧版库..." 48 | echo "deb http://deb.debian.org/debian buster main" | tee /etc/apt/sources.list.d/buster.list 49 | apt update 50 | apt install -y libssl1.1 51 | fi 52 | } 53 | 54 | # 检查端口冲突 55 | check_port_conflict() { 56 | if netstat -tulnp | grep -q ":53"; then 57 | echo "端口 53 已被占用,禁用 systemd-resolved..." 58 | systemctl disable --now systemd-resolved 59 | rm -f /etc/resolv.conf 60 | echo "nameserver 8.8.8.8" > /etc/resolv.conf 61 | fi 62 | } 63 | 64 | # 获取最新 SmartDNS 版本 65 | get_latest_smartdns_url() { 66 | echo "正在获取最新 SmartDNS 版本..." 67 | LATEST_RELEASE=$(curl -s https://api.github.com/repos/pymumu/smartdns/releases/latest) 68 | if [ "$INSTALL_METHOD" = "deb" ]; then 69 | DOWNLOAD_URL=$(echo "$LATEST_RELEASE" | jq -r ".assets[] | select(.name | contains(\"${SMARTDNS_ARCH}-debian-all.deb\")) | .browser_download_url") 70 | else 71 | DOWNLOAD_URL=$(echo "$LATEST_RELEASE" | jq -r ".assets[] | select(.name | contains(\"${SMARTDNS_ARCH}-linux-all.tar.gz\")) | .browser_download_url") 72 | fi 73 | if [ -z "$DOWNLOAD_URL" ]; then 74 | echo "无法获取 SmartDNS 下载链接" 75 | exit 1 76 | fi 77 | echo "最新 SmartDNS 下载链接: $DOWNLOAD_URL" 78 | } 79 | 80 | # 安装 SmartDNS 81 | install_smartdns() { 82 | echo "正在安装 SmartDNS..." 83 | install_dependencies 84 | check_port_conflict 85 | get_latest_smartdns_url 86 | if [ "$INSTALL_METHOD" = "deb" ]; then 87 | wget "$DOWNLOAD_URL" -O smartdns.deb 88 | if [ $? -ne 0 ]; then 89 | echo "下载 SmartDNS .deb 包失败" 90 | exit 1 91 | fi 92 | dpkg -i smartdns.deb 93 | if [ $? -ne 0 ]; then 94 | echo "安装 SmartDNS .deb 包失败,尝试修复依赖..." 95 | apt install -f -y 96 | dpkg -i smartdns.deb 97 | if [ $? -ne 0 ]; then 98 | echo "安装 SmartDNS 失败,请检查错误信息" 99 | rm -f smartdns.deb 100 | exit 1 101 | fi 102 | fi 103 | rm -f smartdns.deb 104 | else 105 | wget "$DOWNLOAD_URL" -O smartdns.tar.gz 106 | if [ $? -ne 0 ]; then 107 | echo "下载 SmartDNS .tar.gz 包失败" 108 | exit 1 109 | fi 110 | tar zxf smartdns.tar.gz 111 | cd smartdns 112 | chmod +x ./install 113 | ./install -i 114 | if [ $? -ne 0 ]; then 115 | echo "安装 SmartDNS 失败" 116 | cd .. 117 | rm -rf smartdns smartdns.tar.gz 118 | exit 1 119 | fi 120 | cd .. 121 | rm -rf smartdns smartdns.tar.gz 122 | fi 123 | # 验证二进制 124 | if command -v smartdns >/dev/null 2>&1; then 125 | echo "SmartDNS 安装成功,版本: $(smartdns --version)" 126 | else 127 | echo "SmartDNS 安装失败,未找到 smartdns 命令" 128 | exit 1 129 | fi 130 | # 确保配置目录存在 131 | mkdir -p /etc/smartdns 132 | # 启动服务 133 | systemctl enable smartdns 134 | systemctl restart smartdns 135 | if systemctl is-active --quiet smartdns; then 136 | echo "SmartDNS 服务已成功启动" 137 | else 138 | echo "SmartDNS 服务启动失败,请检查 'systemctl status smartdns.service'" 139 | exit 1 140 | fi 141 | } 142 | 143 | # 安装 AdGuardHome 144 | install_adguardhome() { 145 | echo "正在安装 AdGuardHome..." 146 | curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- 147 | if [ $? -ne 0 ]; then 148 | echo "AdGuardHome 安装失败" 149 | exit 1 150 | fi 151 | } 152 | 153 | # 卸载 SmartDNS 154 | uninstall_smartdns() { 155 | echo "正在卸载 SmartDNS..." 156 | systemctl stop smartdns 157 | systemctl disable smartdns 158 | if [ "$INSTALL_METHOD" = "deb" ]; then 159 | dpkg -r smartdns 160 | else 161 | rm -f /lib/systemd/system/smartdns.service 162 | rm -f /usr/sbin/smartdns 163 | rm -rf /etc/smartdns 164 | fi 165 | systemctl daemon-reload 166 | echo "SmartDNS 卸载完成" 167 | } 168 | 169 | # 卸载 AdGuardHome 170 | uninstall_adguardhome() { 171 | echo "正在卸载 AdGuardHome..." 172 | curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -u 173 | if [ $? -ne 0 ]; then 174 | echo "AdGuardHome 卸载失败" 175 | exit 1 176 | fi 177 | echo "AdGuardHome 卸载完成" 178 | } 179 | 180 | # 下载并配置 SmartDNS 181 | configure_smartdns() { 182 | mkdir -p /etc/smartdns 183 | wget -O /etc/smartdns/smartdns.conf https://raw.githubusercontent.com/LidaoNote/OpenCode/refs/heads/main/SmartDNS/smartdns_s.conf 184 | if [ $? -ne 0 ]; then 185 | echo "下载 SmartDNS 配置文件失败" 186 | exit 1 187 | fi 188 | 189 | echo "设置监听端口..." 190 | if [ "$1" = "adguard" ]; then 191 | # 设置端口为 5353 192 | sed -i 's/bind \[::\]:[0-9]\+/bind \[::\]:5353/g' /etc/smartdns/smartdns.conf 193 | sed -i 's/bind-tcp \[::\]:[0-9]\+/bind-tcp \[::\]:5353/g' /etc/smartdns/smartdns.conf 194 | else 195 | # 设置端口为 53 196 | sed -i 's/bind \[::\]:[0-9]\+/bind \[::\]:53/g' /etc/smartdns/smartdns.conf 197 | sed -i 's/bind-tcp \[::\]:[0-9]\+/bind-tcp \[::\]:53/g' /etc/smartdns/smartdns.conf 198 | fi 199 | 200 | echo "请输入您的运营商 DNS 服务器地址 (按 Enter 使用默认值):" 201 | read -p "DNS1: " dns1 202 | read -p "DNS2: " dns2 203 | 204 | # 如果用户未输入,使用默认值 205 | if [ -z "$dns1" ]; then 206 | dns1="223.6.6.6" 207 | fi 208 | if [ -z "$dns2" ]; then 209 | dns2="119.29.29.29" 210 | fi 211 | 212 | # 修改 DNS 服务器组配置 213 | sed -i "s|server 运营商DNS1 -group china -exclude-default-group|server $dns1 -group china -exclude-default-group|g" /etc/smartdns/smartdns.conf 214 | sed -i "s|server 运营商DNS2 -group china -exclude-default-group|server $dns2 -group china -exclude-default-group|g" /etc/smartdns/smartdns.conf 215 | 216 | wget -O /etc/smartdns/all_domains.conf https://github.com/LidaoNote/OpenCode/raw/refs/heads/main/SmartDNS/all_domains.conf 217 | if [ $? -ne 0 ]; then 218 | echo "下载 SmartDNS 域名列表失败" 219 | exit 1 220 | fi 221 | 222 | echo "重启 SmartDNS 服务..." 223 | systemctl restart smartdns 224 | if [ $? -ne 0 ]; then 225 | echo "SmartDNS 服务重启失败,请检查服务状态" 226 | exit 1 227 | fi 228 | } 229 | 230 | # 下载 AdGuardHome 配置文件并重启服务 231 | download_adguard_config() { 232 | mkdir -p /opt/AdGuardHome 233 | echo "正在下载 AdGuardHome 配置文件..." 234 | wget -O /opt/AdGuardHome/AdGuardHome.yaml https://github.com/LidaoNote/OpenCode/raw/refs/heads/main/AdGuardHome/AdGuardHome.yaml 235 | if [ $? -ne 0 ]; then 236 | echo "AdGuardHome 配置文件下载失败" 237 | exit 1 238 | fi 239 | 240 | echo "配置文件下载成功,保存到 /opt/AdGuardHome/AdGuardHome.yaml" 241 | 242 | # 重启 AdGuardHome 服务 243 | echo "重启 AdGuardHome 服务..." 244 | systemctl restart AdGuardHome 245 | if [ $? -ne 0 ]; then 246 | echo "AdGuardHome 服务重启失败" 247 | exit 1 248 | fi 249 | 250 | # 检查服务状态 251 | if systemctl is-active --quiet AdGuardHome; then 252 | echo "AdGuardHome 服务已成功重启" 253 | else 254 | echo "AdGuardHome 服务未运行" 255 | exit 1 256 | fi 257 | } 258 | 259 | # 执行操作 260 | show_menu 261 | 262 | case $choice in 263 | 1) 264 | install_dependencies 265 | ;; 266 | 2) 267 | install_smartdns 268 | configure_smartdns "" 269 | ;; 270 | 3) 271 | install_smartdns 272 | install_adguardhome 273 | configure_smartdns "adguard" 274 | download_adguard_config 275 | ;; 276 | 4) 277 | uninstall_smartdns 278 | ;; 279 | 5) 280 | uninstall_adguardhome 281 | ;; 282 | *) 283 | echo "无效选项" 284 | exit 1 285 | ;; 286 | esac -------------------------------------------------------------------------------- /doh.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | import requests 4 | import dns.message 5 | import dns.rdatatype 6 | import dns.query 7 | import time 8 | 9 | def test_doh_server(domain, doh_url): 10 | headers = { 11 | 'Content-Type': 'application/dns-message' 12 | } 13 | 14 | # Create a DNS query for the domain 15 | query = dns.message.make_query(domain, dns.rdatatype.A) 16 | query_data = query.to_wire() 17 | 18 | try: 19 | start_time = time.time() # Start timing 20 | response = requests.post(doh_url, headers=headers, data=query_data) 21 | end_time = time.time() # End timing 22 | total_time_ms = (end_time - start_time) * 1000 # Calculate total time in milliseconds 23 | 24 | if response.status_code == 200: 25 | response_data = dns.message.from_wire(response.content) 26 | print(f"DoH server at {doh_url} is available.") 27 | for answer in response_data.answer: 28 | for item in answer.items: 29 | print(f"{domain} resolves to {item}") 30 | print(f"Total query time: {total_time_ms:.2f} ms") 31 | else: 32 | print(f"DoH server at {doh_url} returned status code {response.status_code}.") 33 | except requests.RequestException as e: 34 | print(f"Failed to connect to DoH server at {doh_url}: {e}") 35 | 36 | if __name__ == "__main__": 37 | if len(sys.argv) != 3: 38 | print("Usage: doh.py ") 39 | sys.exit(1) 40 | 41 | domain = sys.argv[1] 42 | doh_url = sys.argv[2] 43 | test_doh_server(domain, doh_url) 44 | -------------------------------------------------------------------------------- /iKuai/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ikuai_url": "http://10.0.0.1", 3 | "username": "admin", 4 | "password": "admin", 5 | "china_ip_url": "https://raw.githubusercontent.com/LidaoNote/OpenCode/refs/heads/main/china_ip.txt", 6 | "last_ip_file": "last_sync_ip.json", 7 | "timeout": 10, 8 | "chunk_size": 10000, 9 | "isp_name": "CN", 10 | "schedule_type": "d", 11 | "schedule_time": "00:00", 12 | "schedule_day": "monday", 13 | "schedule_date": 1 14 | } -------------------------------------------------------------------------------- /iKuai/ikuai-ip-update.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import requests 3 | import hashlib 4 | import time 5 | import json 6 | import os 7 | import logging 8 | import schedule 9 | import threading 10 | import signal 11 | from tenacity import retry, stop_after_attempt, wait_fixed 12 | import ipaddress 13 | 14 | # 配置日志 15 | logging.basicConfig( 16 | level=logging.INFO, 17 | format="%(asctime)s [%(levelname)s] %(message)s", 18 | handlers=[ 19 | logging.FileHandler("ikuai-ip-update.log"), 20 | logging.StreamHandler() 21 | ] 22 | ) 23 | logger = logging.getLogger() 24 | 25 | # 全局标志和配置 26 | RUNNING = True 27 | CONFIG_PATH = os.path.join(os.path.dirname(__file__), "config.json") 28 | CONFIG_CHECK_INTERVAL = 60 29 | LAST_CONFIG_MTIME = 0 30 | CURRENT_CONFIG = None 31 | CURRENT_SCHEDULE = None 32 | 33 | def load_config(): 34 | global LAST_CONFIG_MTIME, CURRENT_CONFIG 35 | try: 36 | stat = os.stat(CONFIG_PATH) 37 | mtime = stat.st_mtime 38 | if mtime != LAST_CONFIG_MTIME: 39 | with open(CONFIG_PATH, "r") as f: 40 | config = json.load(f) 41 | required_configs = [ 42 | "ikuai_url", "username", "password", "china_ip_url", "last_ip_file", 43 | "timeout", "chunk_size", "isp_name", "schedule_type", "schedule_time", 44 | "schedule_day", "schedule_date" 45 | ] 46 | for key in required_configs: 47 | if key not in config: 48 | logger.error(f"缺少必需配置项: {key}") 49 | return None 50 | if config["schedule_type"].lower() not in ["d", "w", "m"]: 51 | logger.error(f"无效 schedule_type: {config['schedule_type']}") 52 | return None 53 | if not (1 <= config["schedule_date"] <= 28): 54 | logger.error(f"无效 schedule_date: {config['schedule_date']}") 55 | return None 56 | if config["schedule_day"].lower() not in ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]: 57 | logger.error(f"无效 schedule_day: {config['schedule_day']}") 58 | return None 59 | try: 60 | time.strptime(config["schedule_time"], "%H:%M") 61 | except ValueError: 62 | logger.error(f"无效 schedule_time: {config['schedule_time']}") 63 | return None 64 | if not isinstance(config["timeout"], (int, float)) or config["timeout"] <= 0: 65 | logger.error(f"无效 timeout: {config['timeout']}") 66 | return None 67 | if not isinstance(config["chunk_size"], int) or config["chunk_size"] <= 0: 68 | logger.error(f"无效 chunk_size: {config['chunk_size']}") 69 | return None 70 | LAST_CONFIG_MTIME = mtime 71 | CURRENT_CONFIG = config 72 | logger.info(f"加载配置文件,修改时间: {time.ctime(mtime)}") 73 | return config 74 | return CURRENT_CONFIG 75 | except FileNotFoundError: 76 | logger.error(f"配置文件 {CONFIG_PATH} 不存在") 77 | return None 78 | except json.JSONDecodeError: 79 | logger.error(f"配置文件 {CONFIG_PATH} 格式错误") 80 | return None 81 | except Exception as e: 82 | logger.error(f"加载配置文件失败: {e}") 83 | return None 84 | 85 | def md5_hash(text): 86 | return hashlib.md5(text.encode('utf-8')).hexdigest() 87 | 88 | @retry(stop=stop_after_attempt(3), wait=wait_fixed(2)) 89 | def login(config): 90 | logger.info(f"登录: {config['ikuai_url']}/Action/login") 91 | payload = { 92 | "username": config["username"], 93 | "passwd": md5_hash(config["password"]), 94 | "pass": str(int(time.time() * 1000)), 95 | "remember_password": "" 96 | } 97 | response = requests.post(f"{config['ikuai_url']}/Action/login", json=payload, timeout=config["timeout"]) 98 | response.raise_for_status() 99 | data = response.json() 100 | if data.get("Result") == 10000: 101 | session = requests.Session() 102 | session.cookies.update(response.cookies) 103 | logger.info("登录成功") 104 | return session 105 | logger.error(f"登录失败: {data.get('ErrMsg')}") 106 | return None 107 | 108 | @retry(stop=stop_after_attempt(3), wait=wait_fixed(2)) 109 | def fetch_china_ip_list(config): 110 | logger.info(f"获取 IP 列表: {config['china_ip_url']}") 111 | response = requests.get(config["china_ip_url"], timeout=config["timeout"]) 112 | response.raise_for_status() 113 | ip_list = [] 114 | for line in response.text.splitlines(): 115 | line = line.strip() 116 | if not line: 117 | continue 118 | try: 119 | ipaddress.ip_network(line) 120 | ip_list.append(line) 121 | except ValueError: 122 | logger.warning(f"无效 IP: {line}") 123 | if not ip_list: 124 | logger.error("IP 列表为空") 125 | return None 126 | logger.info(f"获取 {len(ip_list)} 条 IP") 127 | return ip_list 128 | 129 | def load_last_ip_list(config): 130 | path = os.path.join(os.path.dirname(__file__), config["last_ip_file"]) 131 | if not os.path.exists(path) or os.path.getsize(path) == 0: 132 | logger.warning(f"IP 列表文件 {path} 不存在或为空") 133 | return [] 134 | try: 135 | with open(path, 'r') as f: 136 | return json.load(f) 137 | except (json.JSONDecodeError, Exception) as e: 138 | logger.error(f"读取 IP 列表失败: {e}") 139 | return [] 140 | 141 | def save_last_ip_list(config, ip_list): 142 | path = os.path.join(os.path.dirname(__file__), config["last_ip_file"]) 143 | try: 144 | with open(path, 'w') as f: 145 | json.dump(ip_list, f) 146 | logger.info("保存 IP 列表成功") 147 | except Exception as e: 148 | logger.error(f"保存 IP 列表失败: {e}") 149 | 150 | @retry(stop=stop_after_attempt(3), wait=wait_fixed(2)) 151 | def get_isp_info(session, isp_name, config): 152 | logger.info(f"查询 {isp_name} 运营商信息") 153 | payload = { 154 | "func_name": "custom_isp", 155 | "action": "show", 156 | "param": {"TYPE": "data,total", "limit": "0,1000"} 157 | } 158 | response = session.post(f"{config['ikuai_url']}/Action/call", json=payload, headers={"Content-Type": "application/json"}, timeout=config["timeout"]) 159 | response.raise_for_status() 160 | data = response.json() 161 | if data.get("Result") != 30000: 162 | logger.error(f"获取运营商失败: {data.get('ErrMsg')}") 163 | return None, 0 164 | for isp in data.get("Data", {}).get("data", []): 165 | if isinstance(isp, dict) and isp.get("name") == isp_name: 166 | ipgroup = isp.get("ipgroup", "") 167 | ip_count = len(ipgroup.split(",")) if ipgroup else 0 168 | logger.info(f"找到 {isp_name} ID: {isp.get('id')},IP 条数: {ip_count}") 169 | return isp.get("id"), ip_count 170 | logger.info(f"未找到 {isp_name}") 171 | return None, 0 172 | 173 | def update_custom_isp(session, ip_list, isp_name, config): 174 | logger.info(f"更新 {isp_name},{len(ip_list)} 条 IP") 175 | isp_id, isp_ip_count = get_isp_info(session, isp_name, config) 176 | ipgroup_str = ",".join(ip_list) 177 | payload = { 178 | "func_name": "custom_isp", 179 | "action": "edit" if isp_id else "add", 180 | "param": { 181 | "id": isp_id, 182 | "name": isp_name, 183 | "ipgroup": ipgroup_str 184 | } if isp_id else { 185 | "name": isp_name, 186 | "ipgroup": ipgroup_str 187 | } 188 | } 189 | response = session.post(f"{config['ikuai_url']}/Action/call", json=payload, headers={"Content-Type": "application/json"}, timeout=config["timeout"]) 190 | response.raise_for_status() 191 | result = response.json() 192 | if result.get("Result") not in [10000, 30000]: 193 | logger.error(f"更新失败: {result.get('ErrMsg')} (Result: {result.get('Result')})") 194 | return False 195 | expected_count = len(ip_list) 196 | if isp_id and isp_ip_count == expected_count: 197 | logger.info(f"验证成功: {isp_name} IP 条数 {isp_ip_count} 匹配预期 {expected_count}") 198 | return True 199 | logger.error(f"验证失败: {isp_name} IP 条数 {isp_ip_count} 不匹配预期 {expected_count}") 200 | return False 201 | 202 | def update_job(config): 203 | logger.info("开始更新任务") 204 | if not config: 205 | logger.error("无有效配置,跳过任务") 206 | return 207 | china_ip_list = fetch_china_ip_list(config) 208 | if not china_ip_list: 209 | logger.error("无 IP 列表,跳过更新") 210 | return 211 | last_ip_list = load_last_ip_list(config) 212 | if sorted(china_ip_list) == sorted(last_ip_list): 213 | logger.info("远程 IP 列表无变化,跳过更新") 214 | return 215 | logger.info(f"远程 IP 列表变更(新: {len(china_ip_list)} 条,旧: {len(last_ip_list)} 条),开始更新") 216 | session = login(config) 217 | if session: 218 | try: 219 | if update_custom_isp(session, china_ip_list, config["isp_name"], config): 220 | save_last_ip_list(config, china_ip_list) 221 | else: 222 | logger.error("更新未成功,不保存新 IP 列表") 223 | except Exception as e: 224 | logger.error(f"任务失败: {e}") 225 | finally: 226 | session.close() 227 | else: 228 | logger.error("登录失败,跳过任务") 229 | logger.info("更新任务结束") 230 | 231 | def schedule_jobs(config): 232 | logger.info(f"设置调度: {config['schedule_type']} 周期,时间 {config['schedule_time']}") 233 | try: 234 | schedule.clear() 235 | if config["schedule_type"] == "d": 236 | schedule.every().day.at(config["schedule_time"]).do(update_job, config) 237 | elif config["schedule_type"] == "w": 238 | getattr(schedule.every(), config["schedule_day"].lower()).at(config["schedule_time"]).do(update_job, config) 239 | elif config["schedule_type"] == "m": 240 | schedule.every(1).months.at(f"{config['schedule_date']:02d} {config['schedule_time']}").do(update_job, config) 241 | else: 242 | logger.error(f"无效调度类型: {config['schedule_type']}") 243 | return False 244 | logger.info(f"调度任务已设置: {config['schedule_type']} {config['schedule_time']}") 245 | return True 246 | except schedule.ScheduleValueError as e: 247 | logger.error(f"调度错误: {e}") 248 | return False 249 | 250 | def run_scheduler(): 251 | config = load_config() 252 | if not config or not schedule_jobs(config): 253 | logger.error("初始配置或调度失败,退出") 254 | return 255 | global CURRENT_SCHEDULE, CURRENT_CONFIG 256 | CURRENT_SCHEDULE = { 257 | "schedule_type": config["schedule_type"], 258 | "schedule_time": config["schedule_time"], 259 | "schedule_day": config["schedule_day"], 260 | "schedule_date": config["schedule_date"] 261 | } 262 | CURRENT_CONFIG = config 263 | last_check = 0 264 | while RUNNING: 265 | if time.time() - last_check >= CONFIG_CHECK_INTERVAL: 266 | new_config = load_config() 267 | if new_config: 268 | new_schedule = { 269 | "schedule_type": new_config["schedule_type"], 270 | "schedule_time": new_config["schedule_time"], 271 | "schedule_day": new_config["schedule_day"], 272 | "schedule_date": new_config["schedule_date"] 273 | } 274 | if not CURRENT_SCHEDULE or new_schedule != CURRENT_SCHEDULE: 275 | logger.info("调度配置变更,重新设置") 276 | if schedule_jobs(new_config): 277 | CURRENT_SCHEDULE = new_schedule 278 | CURRENT_CONFIG = new_config 279 | else: 280 | logger.error("重新设置调度失败,使用旧配置") 281 | last_check = time.time() 282 | schedule.run_pending() 283 | time.sleep(5) 284 | 285 | def signal_handler(sig, frame): 286 | global RUNNING 287 | logger.info("收到终止信号,停止服务...") 288 | RUNNING = False 289 | 290 | if __name__ == "__main__": 291 | logger.info("iKuai IP 更新服务启动") 292 | signal.signal(signal.SIGINT, signal_handler) 293 | signal.signal(signal.SIGTERM, signal_handler) 294 | scheduler_thread = threading.Thread(target=run_scheduler, daemon=True) 295 | scheduler_thread.start() 296 | try: 297 | while RUNNING: 298 | time.sleep(1) 299 | except KeyboardInterrupt: 300 | logger.info("键盘中断,停止服务") 301 | RUNNING = False 302 | scheduler_thread.join() 303 | logger.info("iKuai IP 更新服务停止") -------------------------------------------------------------------------------- /iKuai/install-iksip.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # iKuai IP 更新服务一键安装脚本 4 | # 适用于基于 systemd 的 Linux 系统(如 Ubuntu、CentOS 等) 5 | # 支持通过键盘交互选择安装、卸载或退出 6 | # 安装:生成 config.json 和 config.json.example,配置服务,以 root 用户运行 7 | # 卸载:删除服务和配置文件 8 | # 如果缺少 config.json,将交互式生成标准 JSON 配置文件,仅要求用户输入必要字段 9 | # username 和 isp_name 支持默认值,其他字段使用默认值,配置说明在 config.json.example 中 10 | 11 | # 目标安装目录 12 | INSTALL_DIR="/opt/iksip" 13 | SERVICE_NAME="iksip" 14 | SYSTEMD_SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" 15 | SCRIPT_URL="https://raw.githubusercontent.com/LidaoNote/OpenCode/refs/heads/main/iKuai/ikuai-ip-update.py" 16 | SCRIPT_NAME="ikuai-ip-update.py" 17 | DEFAULT_CHINA_IP_URL="https://raw.githubusercontent.com/LidaoNote/OpenCode/refs/heads/main/china_ip.txt" 18 | DEFAULT_LAST_IP_FILE="last_sync_ip.json" 19 | DEFAULT_TIMEOUT=30 20 | DEFAULT_CHUNK_SIZE=10000 21 | DEFAULT_USERNAME="admin" 22 | DEFAULT_ISP_NAME="CN" 23 | DEFAULT_SCHEDULE_TYPE="d" 24 | DEFAULT_SCHEDULE_TIME="05:00" 25 | DEFAULT_SCHEDULE_DAY="monday" 26 | DEFAULT_SCHEDULE_DATE=1 27 | 28 | # 颜色输出 29 | RED='\033[0;31m' 30 | GREEN='\033[0;32m' 31 | YELLOW='\033[1;33m' 32 | NC='\033[0m' # 无颜色 33 | 34 | # 日志函数 35 | log_info() { 36 | echo -e "${GREEN}[INFO]${NC} $1" 37 | } 38 | 39 | log_error() { 40 | echo -e "${RED}[ERROR]${NC} $1" 41 | exit 1 42 | } 43 | 44 | log_warning() { 45 | echo -e "${YELLOW}[WARNING]${NC} $1" 46 | } 47 | 48 | # 交互式菜单 49 | show_menu() { 50 | echo "请选择操作:" 51 | echo "1. 安装 iKuai IP 更新服务" 52 | echo "2. 卸载 iKuai IP 更新服务" 53 | echo "3. 退出" 54 | read -p "输入选项 (1-3): " choice 55 | case "$choice" in 56 | 1) 57 | install_service 58 | ;; 59 | 2) 60 | uninstall_service 61 | ;; 62 | 3) 63 | log_info "退出脚本" 64 | exit 0 65 | ;; 66 | *) 67 | log_warning "无效选项,请输入 1、2 或 3" 68 | show_menu 69 | ;; 70 | esac 71 | } 72 | 73 | # 卸载服务 74 | uninstall_service() { 75 | log_info "开始卸载 $SERVICE_NAME 服务" 76 | 77 | # 停止服务 78 | if systemctl is-active --quiet "$SERVICE_NAME"; then 79 | log_info "停止 $SERVICE_NAME 服务" 80 | systemctl stop "$SERVICE_NAME" || log_warning "停止 $SERVICE_NAME 服务失败" 81 | fi 82 | 83 | # 禁用服务 84 | if systemctl is-enabled --quiet "$SERVICE_NAME"; then 85 | log_info "禁用 $SERVICE_NAME 服务" 86 | systemctl disable "$SERVICE_NAME" || log_warning "禁用 $SERVICE_NAME 服务失败" 87 | fi 88 | 89 | # 删除服务文件 90 | if [ -f "$SYSTEMD_SERVICE_FILE" ]; then 91 | log_info "删除服务文件: $SYSTEMD_SERVICE_FILE" 92 | rm -f "$SYSTEMD_SERVICE_FILE" || log_warning "删除服务文件失败" 93 | systemctl daemon-reload || log_warning "重新加载 systemd 配置失败" 94 | fi 95 | 96 | # 删除安装目录 97 | if [ -d "$INSTALL_DIR" ]; then 98 | log_warning "即将删除安装目录: $INSTALL_DIR" 99 | read -p "确认删除所有配置文件和日志?(y/N): " confirm 100 | if [[ "$confirm" =~ ^[Yy]$ ]]; then 101 | rm -rf "$INSTALL_DIR" || log_warning "删除安装目录失败" 102 | log_info "已删除安装目录: $INSTALL_DIR" 103 | else 104 | log_info "取消删除安装目录" 105 | fi 106 | fi 107 | 108 | log_info "$SERVICE_NAME 服务卸载完成" 109 | exit 0 110 | } 111 | 112 | # 安装服务 113 | install_service() { 114 | # 检查是否为 root 用户 115 | if [ "$(id -u)" -ne 0 ]; then 116 | log_error "请以 root 用户或使用 sudo 运行此脚本" 117 | fi 118 | 119 | # 检查系统是否支持 systemd 120 | if ! command -v systemctl >/dev/null 2>&1; then 121 | log_error "此脚本仅支持基于 systemd 的系统(如 Ubuntu、CentOS)" 122 | fi 123 | 124 | # 检查 Python 3 是否安装 125 | if ! command -v python3 >/dev/null 2>&1; then 126 | log_error "Python 3 未安装,请先安装 Python 3" 127 | fi 128 | 129 | # 检查 pip 是否安装 130 | if ! command -v pip3 >/dev/null 2>&1; then 131 | log_warning "pip3 未安装,尝试安装..." 132 | if command -v apt-get >/dev/null 2>&1; then 133 | apt-get update && apt-get install -y python3-pip || log_error "安装 pip3 失败" 134 | elif command -v yum >/dev/null 2>&1; then 135 | yum install -y python3-pip || log_error "安装 pip3 失败" 136 | else 137 | log_error "无法自动安装 pip3,请手动安装" 138 | fi 139 | fi 140 | 141 | # 检查并获取 ikuai-ip-update.py 142 | if [ -f "$SCRIPT_NAME" ]; then 143 | log_info "找到本地 $SCRIPT_NAME 文件,将使用本地文件" 144 | else 145 | log_info "未找到本地 $SCRIPT_NAME 文件,尝试从 $SCRIPT_URL 下载" 146 | if command -v curl >/dev/null 2>&1; then 147 | curl -o "$SCRIPT_NAME" "$SCRIPT_URL" || log_error "下载 $SCRIPT_NAME 失败" 148 | elif command -v wget >/dev/null 2>&1; then 149 | wget -O "$SCRIPT_NAME" "$SCRIPT_URL" || log_error "下载 $SCRIPT_NAME 失败" 150 | else 151 | log_error "未安装 curl 或 wget,请安装后重试" 152 | fi 153 | log_info "成功下载 $SCRIPT_NAME" 154 | fi 155 | 156 | # 验证 ikuai-ip-update.py 是否可执行 157 | if ! head -n 1 "$SCRIPT_NAME" | grep -q "^#!/usr/bin/env python3"; then 158 | log_error "$SCRIPT_NAME 文件格式错误,缺少 Python shebang 行" 159 | fi 160 | 161 | # 交互式生成 config.json(如果不存在) 162 | if [ ! -f "config.json" ]; then 163 | log_warning "未找到 config.json,将通过交互式输入生成配置文件" 164 | 165 | # 收集用户输入 166 | read -p "请输入 iKuai 路由器地址 (例如 http://10.0.0.1): " ikuai_url 167 | [ -z "$ikuai_url" ] && log_error "iKuai 路由器地址不能为空" 168 | 169 | read -p "请输入 iKuai 用户名 (按 Enter 使用默认 $DEFAULT_USERNAME): " username 170 | username=${username:-$DEFAULT_USERNAME} 171 | 172 | read -p "请输入 iKuai 密码: " password 173 | [ -z "$password" ] && log_error "密码不能为空" 174 | 175 | read -p "请输入运营商名称 (按 Enter 使用默认 $DEFAULT_ISP_NAME): " isp_name 176 | isp_name=${isp_name:-$DEFAULT_ISP_NAME} 177 | 178 | # 使用 Python 生成 config.json,确保正确处理特殊字符 179 | python3 - << EOF || log_error "生成 config.json 失败,请检查输入值(可能包含特殊字符,如引号或换行符)" 180 | import json 181 | config = { 182 | "ikuai_url": "$ikuai_url", 183 | "username": "$username", 184 | "password": "$password", 185 | "china_ip_url": "$DEFAULT_CHINA_IP_URL", 186 | "last_ip_file": "$DEFAULT_LAST_IP_FILE", 187 | "timeout": $DEFAULT_TIMEOUT, 188 | "chunk_size": $DEFAULT_CHUNK_SIZE, 189 | "isp_name": "$isp_name", 190 | "schedule_type": "$DEFAULT_SCHEDULE_TYPE", 191 | "schedule_time": "$DEFAULT_SCHEDULE_TIME", 192 | "schedule_day": "$DEFAULT_SCHEDULE_DAY", 193 | "schedule_date": $DEFAULT_SCHEDULE_DATE 194 | } 195 | with open("config.json", "w") as f: 196 | json.dump(config, f, indent=4) 197 | EOF 198 | log_info "已生成 config.json" 199 | 200 | # 生成 config.json.example,包含注释 201 | python3 - << EOF || log_error "生成 config.json.example 失败" 202 | import json 203 | config = { 204 | "ikuai_url": "$ikuai_url", 205 | "username": "$username", 206 | "password": "$password", 207 | "china_ip_url": "$DEFAULT_CHINA_IP_URL", 208 | "last_ip_file": "$DEFAULT_LAST_IP_FILE", 209 | "timeout": $DEFAULT_TIMEOUT, 210 | "chunk_size": $DEFAULT_CHUNK_SIZE, 211 | "isp_name": "$isp_name", 212 | "schedule_type": "$DEFAULT_SCHEDULE_TYPE", 213 | "schedule_time": "$DEFAULT_SCHEDULE_TIME", 214 | "schedule_day": "$DEFAULT_SCHEDULE_DAY", 215 | "schedule_date": $DEFAULT_SCHEDULE_DATE 216 | } 217 | comments = { 218 | "ikuai_url": "iKuai 路由器地址,例如 http://10.0.0.1,必须是有效的 URL", 219 | "username": "iKuai 管理员用户名,不能为空,默认为 admin", 220 | "password": "iKuai 管理员密码,不能为空", 221 | "china_ip_url": "IP 列表的 URL,默认为中国 IP 列表,可改为其他 IP 列表的 URL(如 https://example.com/other_ip.txt)", 222 | "last_ip_file": "本地保存的 IP 列表文件名,首次运行时生成,可改为其他文件名(如 my_ip_list.json)", 223 | "timeout": "API 请求超时时间(秒),正数,可根据网络情况调整(如 10, 60),默认为 30", 224 | "chunk_size": "分块大小(当前 API 无需分块),正整数,可根据需要调整(如 5000, 20000),默认为 10000", 225 | "isp_name": "运营商名称,用于 iKuai 路由器,不能为空,默认为 CN", 226 | "schedule_type": "调度周期:d=每天,w=每周,m=每月,默认为 d(每天)", 227 | "schedule_time": "调度时间,格式 HH:MM(如 05:00),表示每天/每周/每月的运行时间,默认为 05:00(凌晨 5 点)", 228 | "schedule_day": "每周调度星期,仅在 schedule_type=w 时有效,可为 monday, tuesday, wednesday, thursday, friday, saturday, sunday,默认为 monday", 229 | "schedule_date": "每月调度日期,仅在 schedule_type=m 时有效,范围 1-28,默认为 1(每月 1 号)" 230 | } 231 | output = [] 232 | for key, value in config.items(): 233 | output.append({"// {}".format(key): comments[key]}) 234 | output.append({key: value}) 235 | with open("config.json.example", "w") as f: 236 | json.dump(output, f, indent=4, ensure_ascii=False) 237 | EOF 238 | log_info "已生成 config.json.example,包含配置项说明" 239 | fi 240 | 241 | # 验证 config.json 格式 242 | if ! python3 -c "import json; json.load(open('config.json'))" >/dev/null 2>&1; then 243 | log_error "config.json 格式错误,请检查 JSON 语法" 244 | fi 245 | 246 | # 检查必要配置项 247 | required_configs=("ikuai_url" "username" "password" "china_ip_url" "last_ip_file" "timeout" "chunk_size" "isp_name" "schedule_type" "schedule_time" "schedule_day" "schedule_date") 248 | for key in "${required_configs[@]}"; do 249 | if ! python3 -c "import json; data=json.load(open('config.json')); assert '$key' in data" >/dev/null 2>&1; then 250 | log_error "config.json 缺少必需配置项: $key" 251 | fi 252 | done 253 | 254 | # 验证 schedule_type 255 | schedule_type=$(python3 -c "import json; print(json.load(open('config.json'))['schedule_type'].lower())") 256 | if [[ ! "$schedule_type" =~ ^(d|w|m)$ ]]; then 257 | log_error "config.json 中的 schedule_type 必须为 d(每天), w(每周)或 m(每月)" 258 | fi 259 | 260 | # 创建安装目录 261 | log_info "创建安装目录: $INSTALL_DIR" 262 | mkdir -p "$INSTALL_DIR" || log_error "创建目录 $INSTALL_DIR 失败" 263 | 264 | # 复制脚本和配置文件 265 | log_info "复制 $SCRIPT_NAME 和 config.json 到 $INSTALL_DIR" 266 | cp "$SCRIPT_NAME" "$INSTALL_DIR/" || log_error "复制 $SCRIPT_NAME 失败" 267 | cp "config.json" "$INSTALL_DIR/" || log_error "复制 config.json 失败" 268 | cp "config.json.example" "$INSTALL_DIR/" 2>/dev/null || log_info "未找到 config.json.example,跳过复制" 269 | 270 | # 预创建 last_sync_ip.json 271 | touch "$INSTALL_DIR/$DEFAULT_LAST_IP_FILE" || log_warning "创建 $DEFAULT_LAST_IP_FILE 失败" 272 | 273 | # 设置文件权限 274 | log_info "设置文件权限" 275 | chmod 755 "$INSTALL_DIR/$SCRIPT_NAME" || log_error "设置 $SCRIPT_NAME 权限失败" 276 | chmod 600 "$INSTALL_DIR/config.json" || log_error "设置 config.json 权限失败" 277 | chmod 644 "$INSTALL_DIR/config.json.example" 2>/dev/null || true 278 | chmod 664 "$INSTALL_DIR/$DEFAULT_LAST_IP_FILE" || log_warning "设置 $DEFAULT_LAST_IP_FILE 权限失败" 279 | touch "$INSTALL_DIR/ikuai-ip-update.log" || log_warning "创建日志文件失败" 280 | chmod 664 "$INSTALL_DIR/ikuai-ip-update.log" || log_warning "设置日志文件权限失败" 281 | chown root:root "$INSTALL_DIR/$SCRIPT_NAME" "$INSTALL_DIR/config.json" "$INSTALL_DIR/ikuai-ip-update.log" "$INSTALL_DIR/$DEFAULT_LAST_IP_FILE" || log_error "设置文件所有者失败" 282 | chown root:root "$INSTALL_DIR/config.json.example" 2>/dev/null || true 283 | chown root:root "$INSTALL_DIR" || log_error "设置目录所有者失败" 284 | 285 | # 安装 Python 依赖 286 | log_info "安装 Python 依赖" 287 | pip3 install requests tenacity schedule || log_error "安装 Python 依赖失败" 288 | 289 | # 创建 systemd 服务文件 290 | log_info "创建 systemd 服务文件: $SYSTEMD_SERVICE_FILE" 291 | cat > "$SYSTEMD_SERVICE_FILE" << EOF 292 | [Unit] 293 | Description=iKuai IP Update Service 294 | After=network.target 295 | 296 | [Service] 297 | Type=simple 298 | ExecStart=/usr/bin/python3 $INSTALL_DIR/$SCRIPT_NAME 299 | WorkingDirectory=$INSTALL_DIR 300 | Restart=always 301 | RestartSec=10 302 | TimeoutStopSec=15 303 | StartLimitInterval=60 304 | StartLimitBurst=5 305 | 306 | [Install] 307 | WantedBy=multi-user.target 308 | EOF 309 | 310 | if [ $? -ne 0 ]; then 311 | log_error "创建 systemd 服务文件失败" 312 | fi 313 | 314 | # 重新加载 systemd 配置 315 | log_info "重新加载 systemd 配置" 316 | systemctl daemon-reload || log_error "重新加载 systemd 配置失败" 317 | 318 | # 启用并启动服务 319 | log_info "启用并启动 $SERVICE_NAME 服务" 320 | systemctl enable "$SERVICE_NAME" || log_error "启用 $SERVICE_NAME 服务失败" 321 | systemctl start "$SERVICE_NAME" || log_error "启动 $SERVICE_NAME 服务失败" 322 | 323 | # 检查服务状态 324 | log_info "检查 $SERVICE_NAME 服务状态" 325 | if systemctl is-active --quiet "$SERVICE_NAME"; then 326 | log_info "$SERVICE_NAME 服务已成功启动" 327 | else 328 | log_error "$SERVICE_NAME 服务启动失败,请检查日志: journalctl -u $SERVICE_NAME" 329 | fi 330 | 331 | log_info "安装完成!" 332 | log_info "服务以 root 用户运行" 333 | log_info "服务日志位于: $INSTALL_DIR/ikuai-ip-update.log" 334 | log_info "配置文件位于: $INSTALL_DIR/config.json" 335 | log_info "配置说明位于: $INSTALL_DIR/config.json.example" 336 | log_info "您可以编辑 $INSTALL_DIR/config.json 修改以下默认配置:" 337 | log_info " - IP 列表 URL: $DEFAULT_CHINA_IP_URL" 338 | log_info " - 本地 IP 列表文件名: $DEFAULT_LAST_IP_FILE" 339 | log_info " - API 请求超时时间: $DEFAULT_TIMEOUT 秒" 340 | log_info " - 分块大小: $DEFAULT_CHUNK_SIZE(当前 API 无需分块)" 341 | log_info " - 调度周期: $DEFAULT_SCHEDULE_TYPE(每天)" 342 | log_info " - 调度时间: $DEFAULT_SCHEDULE_TIME(凌晨 5 点)" 343 | log_info " - 每周调度星期: $DEFAULT_SCHEDULE_DAY(仅每周有效)" 344 | log_info " - 每月调度日期: $DEFAULT_SCHEDULE_DATE(仅每月有效)" 345 | log_info "管理服务命令:" 346 | log_info " - 查看状态: systemctl status $SERVICE_NAME" 347 | log_info " - 停止服务: systemctl stop $SERVICE_NAME" 348 | log_info " - 重启服务: systemctl restart $SERVICE_NAME" 349 | log_info "卸载服务:重新运行脚本并选择选项 2" 350 | exit 0 351 | } 352 | 353 | # 显示交互菜单 354 | show_menu -------------------------------------------------------------------------------- /iKuai/readme.md: -------------------------------------------------------------------------------- 1 | # iKuai IP 同步服务说明文档 2 | 3 | ## 概述 4 | `ikuai-ip-update.py` 是一个 Python 脚本,用于自动更新 iKuai 路由器上的运营商 IP 列表(默认“CN”)。它从指定 URL 获取最新 IP 列表,与本地缓存比较,若有变化或运营商条目不存在,则更新路由器设置。脚本以服务形式运行,支持定时更新(每天、每周、每月)。 5 | 6 | 配套脚本 `install-iksip.sh` 提供一键安装,支持交互式配置和卸载,部署为 `systemd` 服务(`iksip`)。 7 | 8 | ## 前提条件 9 | - **操作系统**:基于 `systemd` 的 Linux(如 Ubuntu 18.04+、CentOS 7+、Debian 12)。 10 | - **Python 3**:确保已安装(Debian 12 默认 Python 3.11)。 11 | - **网络**:可访问 GitHub 和 iKuai 路由器。 12 | - **权限**:需 `root` 或 `sudo` 权限。 13 | - **iKuai 路由器**:需提供 Web 界面地址和管理员凭据。 14 | 15 | ## 安装 16 | 1. **下载安装脚本**: 17 | ```bash 18 | wget -O install-iksip.sh https://raw.githubusercontent.com/LidaoNote/OpenCode/refs/heads/main/iKuai/install-iksip.sh 19 | ``` 20 | 21 | 2. **赋予执行权限**: 22 | ```bash 23 | chmod +x install-iksip.sh 24 | ``` 25 | 26 | 3. **运行脚本**: 27 | ```bash 28 | sudo ./install-iksip.sh 29 | ``` 30 | - 选择操作: 31 | ``` 32 | 1. 安装 iKuai IP 更新服务 33 | 2. 卸载 iKuai IP 更新服务 34 | 3. 退出 35 | ``` 36 | - 安装(选项 1): 37 | - 输入路由器地址(例如 `http://10.0.0.1`)、用户名(默认 `admin`)、密码、运营商名称(默认 `CN`)。 38 | - 脚本下载 `ikuai-ip-update.py`,生成 `config.json` 和 `config.json.example`,安装依赖,配置服务。 39 | - 卸载(选项 2): 40 | - 停止并删除服务,删除 `/opt/iksip/`(需确认)。 41 | - 退出(选项 3):退出脚本。 42 | 43 | 4. **验证安装**: 44 | ```bash 45 | systemctl status iksip 46 | cat /opt/iksip/ikuai-ip-update.log 47 | ``` 48 | 49 | ## 配置 50 | 配置文件位于 `/opt/iksip/config.json`,主要字段: 51 | - `ikuai_url`:路由器地址(如 `http://10.0.0.1`)。 52 | - `username`, `password`:管理员凭据。 53 | - `china_ip_url`:IP 列表 URL(默认:`https://raw.githubusercontent.com/LidaoNote/OpenCode/refs/heads/main/china_ip.txt`)。 54 | - `last_ip_file`:本地缓存文件(默认:`last_sync_ip.json`)。 55 | - `isp_name`:运营商名称(默认:`CN`)。 56 | - `schedule_type`:调度周期(`d`=每天,`w`=每周,`m`=每月)。 57 | - `schedule_time`:调度时间(`HH:MM`,如 `05:00`)。 58 | - `schedule_day`:每周星期(`monday` 等,仅每周有效)。 59 | - `schedule_date`:每月日期(1-28,仅每月有效)。 60 | 61 | **修改配置**: 62 | ```bash 63 | sudo nano /opt/iksip/config.json 64 | sudo systemctl restart iksip 65 | ``` 66 | 67 | ## 工作原理 68 | - **加载配置**:读取 `/opt/iksip/config.json`。 69 | - **获取 IP 列表**:从 `china_ip_url` 下载 IP 列表。 70 | - **比较 IP 列表**:与 `last_ip_file` 比较。 71 | - **更新路由器**:若列表变化或运营商不存在,登录 iKuai,更新运营商 IP 列表。 72 | - **验证更新**:检查 iKuai 条数是否匹配。 73 | - **保存缓存**:更新 `last_ip_file`。 74 | - **定时任务**:按 `schedule_type` 和 `schedule_time` 运行。 75 | - **日志**:记录操作到 `/opt/iksip/ikuai-ip-update.log`。 76 | 77 | ## 使用方法 78 | 服务以 `root` 用户运行,自动启动。管理命令: 79 | - 查看状态:`systemctl status iksip` 80 | - 停止服务:`sudo systemctl stop iksip` 81 | - 重启服务:`sudo systemctl restart iksip` 82 | - 查看日志:`cat /opt/iksip/ikuai-ip-update.log` 83 | 84 | ## 错误处理 85 | - **配置错误**:无效 `config.json`,记录错误并退出。 86 | - **登录失败**:无法登录路由器,跳过更新。 87 | - **IP 列表失败**:无法下载 IP 列表,跳过更新。 88 | - **网络问题**:重试 3 次(间隔 2 秒)。 89 | 90 | ## 定时任务 91 | 内置调度(Python `schedule` 库): 92 | - **每天**:在 `schedule_time` 运行。 93 | - **每周**:在 `schedule_day` 的 `schedule_time` 运行。 94 | - **每月**:在 `schedule_date` 的 `schedule_time` 运行。 95 | 96 | ## 注意事项 97 | - **网络**:确保路由器和 IP 列表 URL 可访问。 98 | - **安全**:服务以 `root` 用户运行,定期检查脚本安全性。 99 | - **日志管理**:定期清理日志: 100 | ```bash 101 | sudo truncate -s 0 /opt/iksip/ikuai-ip-update.log 102 | ``` 103 | - **配置备份**:修改 `config.json` 前备份。 -------------------------------------------------------------------------------- /miniChat/README.md: -------------------------------------------------------------------------------- 1 | ## miniChat 2 | 3 | **miniChat** 是一个轻量、临时的私密聊天室,支持电脑和手机浏览器使用。 4 | - **核心特点**: 5 | - 服务器仅作为消息中转,不存储任何数据。 6 | - 新加入用户无法查看历史聊天记录。 7 | - 刷新浏览器即清空本地聊天记录。 8 | - 强调隐私,无监管、无敏感词过滤。 9 | - **使用场景**:适合需要快速、安全、临时沟通的场景。 10 | 11 | ### 安装流程 12 | 13 | 要将 miniChat 安装为系统服务,请按照以下步骤操作: 14 | 15 | 1. **准备工作**: 16 | - 确保您使用的是基于 Debian/Ubuntu 的 Linux 系统(如 Ubuntu 20.04 或更高版本)。 17 | - 确保您有 root 权限以执行安装脚本。 18 | - 将以下文件放置在同一目录下:`server.py`、`index.html` 和 `install_minichat_service.sh`。 19 | 20 | 2. **运行安装脚本**: 21 | - 打开终端,进入包含上述文件的目录。 22 | - 赋予安装脚本执行权限: 23 | ```bash 24 | chmod +x install_minichat_service.sh 25 | ``` 26 | - 以 root 权限运行脚本: 27 | ```bash 28 | sudo ./install_minichat_service.sh 29 | ``` 30 | - 脚本将自动完成以下操作: 31 | - 安装必要的依赖(如 Python3、pip、nginx 等)。 32 | - 创建专用用户 `minichat` 用于运行服务。 33 | - 在 `/opt/miniChat` 目录下创建安装目录并复制文件。 34 | - 设置 Python 虚拟环境并安装所需 Python 包(`aiohttp`、`aiohttp-jinja2`、`jinja2`)。 35 | - 创建并启用 systemd 服务 `minichat.service`。 36 | - 启动 miniChat 服务。 37 | 38 | 3. **验证安装**: 39 | - 检查服务状态: 40 | ```bash 41 | systemctl status minichat 42 | ``` 43 | - 查看服务日志: 44 | ```bash 45 | journalctl -u minichat.service 46 | ``` 47 | - 如果服务正常运行,您可以通过浏览器访问 `http://<您的服务器IP>:8080` 来使用聊天室。 48 | 49 | 4. **配置 Nginx 反向代理**: 50 | - 按照下方的 “Nginx 反向代理配置” 部分配置 Nginx,以支持 HTTPS 和 WebSocket。 51 | - 配置完成后,重启 Nginx: 52 | ```bash 53 | systemctl restart nginx 54 | ``` 55 | 56 | 5. **故障排查**: 57 | - 如果服务未能启动,检查日志以获取错误信息: 58 | ```bash 59 | journalctl -u minichat.service 60 | ``` 61 | - 确保端口 8080 未被占用。 62 | - 验证 Nginx 配置是否正确: 63 | ```bash 64 | nginx -t 65 | ``` 66 | 67 | ### 依赖安装 68 | 运行 miniChat 需要以下 Python 扩展(安装脚本会自动处理): 69 | ```bash 70 | pip install aiohttp aiohttp-jinja2 jinja2 71 | ``` 72 | 73 | ### Nginx 反向代理配置 74 | 以下是推荐的 Nginx 配置,用于支持 HTTPS 和 WebSocket: 75 | ```nginx 76 | server { 77 | listen 60000 ssl; # 对外监听端口 78 | server_name example.com; # 替换为您的域名 79 | ssl_certificate /path/to/example.com.pem; # 证书路径 80 | ssl_certificate_key /path/to/example.com.key; # 证书密钥路径 81 | ssl_protocols TLSv1.2 TLSv1.3; 82 | ssl_ciphers HIGH:!aNULL:!MD5; 83 | 84 | # 静态页面 85 | location / { 86 | proxy_pass http://<服务器IP>:8080; 87 | proxy_set_header Host $host; 88 | proxy_set_header X-Real-IP $remote_addr; 89 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 90 | proxy_read_timeout 86400; 91 | } 92 | 93 | # WebSocket 94 | location /ws { 95 | proxy_pass http://<服务器IP>:8080/ws; 96 | proxy_http_version 1.1; 97 | proxy_set_header Upgrade $http_upgrade; 98 | proxy_set_header Connection "upgrade"; 99 | proxy_set_header Host $host; 100 | proxy_set_header X-Real-IP $remote_addr; 101 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 102 | proxy_read_timeout 86400; 103 | } 104 | } 105 | ``` 106 | **注意**:请将 `<服务器IP>` 替换为您的实际服务器 IP 地址,`example.com` 替换为您的域名,并确保证书路径正确。 107 | 108 | ### 联系方式 109 | - **GitHub Issues**:提交问题或咨询。 110 | - **Telegram**:@FreeQQ -------------------------------------------------------------------------------- /miniChat/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 聊天室 7 | 327 | 328 | 329 |
330 |
331 | 332 | 333 |
334 |
335 |
336 | 343 | 491 | 492 | -------------------------------------------------------------------------------- /miniChat/install_minichat_service.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 遇到任何错误时退出 4 | set -e 5 | 6 | # 定义变量 7 | SERVICE_NAME="minichat" 8 | INSTALL_DIR="/opt/miniChat" 9 | PYTHON_VERSION="python3" 10 | MIN_PYTHON_VERSION="3.8" 11 | USER="minichat" 12 | SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" 13 | SERVER_PY_URL="https://raw.githubusercontent.com/LidaoNote/OpenCode/refs/heads/main/miniChat/server.py" 14 | INDEX_HTML_URL="https://raw.githubusercontent.com/LidaoNote/OpenCode/refs/heads/main/miniChat/index.html" 15 | 16 | # 输出颜色设置 17 | RED='\033[0;31m' 18 | GREEN='\033[0;32m' 19 | NC='\033[0m' # 无颜色 20 | 21 | echo "开始安装 miniChat..." 22 | 23 | # 检查是否以 root 权限运行 24 | if [[ $EUID -ne 0 ]]; then 25 | echo -e "${RED}此脚本必须以 root 权限运行${NC}" 26 | exit 1 27 | fi 28 | 29 | # 运行环境检测 30 | echo "正在检测运行环境..." 31 | 32 | # 检查是否为基于 Debian/Ubuntu 的系统 33 | if ! command -v apt-get >/dev/null 2>&1; then 34 | echo -e "${RED}此脚本仅支持基于 Debian/Ubuntu 的系统,请在 Ubuntu 或 Debian 上运行${NC}" 35 | exit 1 36 | fi 37 | 38 | # 检查系统发行版 39 | if ! grep -qi "debian\|ubuntu" /etc/os-release; then 40 | echo -e "${RED}警告:未检测到 Debian 或 Ubuntu 系统,可能不兼容${NC}" 41 | read -p "是否继续安装?(y/N): " confirm 42 | if [[ ! "$confirm" =~ ^[Yy]$ ]]; then 43 | echo "安装已取消" 44 | exit 1 45 | fi 46 | fi 47 | 48 | # 检查 Python 版本 49 | if ! command -v $PYTHON_VERSION >/dev/null 2>&1; then 50 | echo -e "${RED}未找到 Python3,请先安装 Python3${NC}" 51 | exit 1 52 | fi 53 | 54 | # 提取 Python 版本并确保格式干净 55 | PYTHON_VERSION_CHECK=$($PYTHON_VERSION --version 2>&1 | grep -oP '\d+\.\d+' | head -n 1) 56 | if [ -z "$PYTHON_VERSION_CHECK" ]; then 57 | echo -e "${RED}无法获取 Python 版本,请检查 Python 安装${NC}" 58 | exit 1 59 | fi 60 | 61 | # 使用 awk 比较版本号 62 | if ! echo "$PYTHON_VERSION_CHECK $MIN_PYTHON_VERSION" | awk '{exit ($1 >= $2)}'; then 63 | echo -e "${RED}Python 版本过低,要求至少 ${MIN_PYTHON_VERSION},当前版本为 ${PYTHON_VERSION_CHECK}${NC}" 64 | exit 1 65 | fi 66 | 67 | # 检查网络连接 68 | echo "正在检查网络连接..." 69 | if ! curl -Is $SERVER_PY_URL >/dev/null 2>&1; then 70 | echo -e "${RED}无法连接到 GitHub,请检查网络或 URL 是否正确${NC}" 71 | exit 1 72 | fi 73 | 74 | # 前置安装环境与依赖 75 | echo "正在安装前置环境与依赖..." 76 | apt-get update 77 | apt-get install -y python3 python3-pip python3-venv curl 78 | 79 | # 检查 pip 是否可用 80 | if ! $PYTHON_VERSION -m pip --version >/dev/null 2>&1; then 81 | echo -e "${RED}pip 未正确安装,请检查 Python 环境${NC}" 82 | exit 1 83 | fi 84 | 85 | # 创建运行服务的用户 86 | if ! id -u $USER >/dev/null 2>&1; then 87 | echo "正在创建用户 ${USER}..." 88 | useradd -m -s /bin/false $USER 89 | fi 90 | 91 | # 创建安装目录 92 | echo "正在创建安装目录 ${INSTALL_DIR}..." 93 | mkdir -p $INSTALL_DIR 94 | chown $USER:$USER $INSTALL_DIR 95 | 96 | # 下载应用程序文件 97 | echo "正在下载应用程序文件..." 98 | curl -o $INSTALL_DIR/server.py $SERVER_PY_URL 99 | curl -o $INSTALL_DIR/index.html $INDEX_HTML_URL 100 | chown -R $USER:$USER $INSTALL_DIR 101 | 102 | # 验证下载的文件 103 | if [ ! -f "$INSTALL_DIR/server.py" ] || [ ! -f "$INSTALL_DIR/index.html" ]; then 104 | echo -e "${RED}无法下载所需文件,请检查 URL 和网络连接。${NC}" 105 | exit 1 106 | fi 107 | 108 | # 创建虚拟环境并安装依赖 109 | echo "正在设置 Python 虚拟环境..." 110 | su - $USER -s /bin/bash -c " 111 | cd $INSTALL_DIR 112 | $PYTHON_VERSION -m venv venv 113 | source venv/bin/activate 114 | pip install --upgrade pip 115 | pip install aiohttp aiohttp-jinja2 jinja2 116 | " 117 | 118 | # 验证 Python 依赖是否安装成功 119 | echo "正在验证 Python 依赖..." 120 | if ! su - $USER -s /bin/bash -c "source $INSTALL_DIR/venv/bin/activate && python3 -c 'import aiohttp, aiohttp_jinja2, jinja2'" >/dev/null 2>&1; then 121 | echo -e "${RED}Python 依赖安装失败,请检查 pip 和网络连接${NC}" 122 | exit 1 123 | fi 124 | 125 | # 创建 systemd 服务文件 126 | echo "正在创建 systemd 服务文件..." 127 | cat > $SERVICE_FILE << EOF 128 | [Unit] 129 | Description=miniChat WebSocket 聊天服务 130 | After=network.target 131 | 132 | [Service] 133 | User=$USER 134 | Group=$USER 135 | WorkingDirectory=$INSTALL_DIR 136 | ExecStart=$INSTALL_DIR/venv/bin/python3 $INSTALL_DIR/server.py 137 | Restart=always 138 | RestartSec=10 139 | 140 | [Install] 141 | WantedBy=multi-user.target 142 | EOF 143 | 144 | # 设置服务文件权限 145 | chmod 644 $SERVICE_FILE 146 | 147 | # 重新加载 systemd 并启用服务 148 | echo "正在配置 systemd 服务..." 149 | systemctl daemon-reload 150 | systemctl enable $SERVICE_NAME 151 | systemctl start $SERVICE_NAME 152 | 153 | # 检查服务状态 154 | echo "正在检查服务状态..." 155 | if systemctl is-active --quiet $SERVICE_NAME; then 156 | echo -e "${GREEN}miniChat 服务已成功安装并运行!${NC}" 157 | else 158 | echo -e "${RED}无法启动 miniChat 服务,请使用以下命令查看日志:journalctl -u ${SERVICE_NAME}.service${NC}" 159 | exit 1 160 | fi 161 | 162 | echo "安装完成!" 163 | echo "后续步骤:" 164 | echo "1. 配置 Nginx 反向代理(参考 README.md 中的配置)" 165 | echo "2. 通过 http://<您的服务器IP>:8080 访问聊天室" 166 | echo "3. 使用以下命令监控服务状态:systemctl status ${SERVICE_NAME}" 167 | echo "4. 使用以下命令查看日志:journalctl -u ${SERVICE_NAME}.service" -------------------------------------------------------------------------------- /miniChat/server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from aiohttp import web, WSMsgType 4 | import aiohttp_jinja2 5 | import jinja2 6 | import json 7 | import asyncio 8 | import logging 9 | import time 10 | import os 11 | 12 | logging.basicConfig(level=logging.DEBUG) 13 | logger = logging.getLogger(__name__) 14 | 15 | users = {} # 存储 {username: {'ws': ws, 'fingerprint': fingerprint, 'last_active': timestamp}} 16 | connections = {} # WebSocket 连接与用户名的映射 17 | 18 | async def websocket_handler(request): 19 | ws = web.WebSocketResponse(heartbeat=30) # 启用内置心跳 20 | await ws.prepare(request) 21 | logger.debug(f"New WebSocket connection from {request.remote}") 22 | 23 | try: 24 | async for msg in ws: 25 | if msg.type == WSMsgType.TEXT: 26 | data = json.loads(msg.data) 27 | event = data.get('event') 28 | 29 | if event == 'join': 30 | username = data['name'] 31 | fingerprint = data.get('fingerprint') 32 | if not fingerprint: 33 | await ws.send_json({'event': 'error', 'error': '缺少指纹信息'}) 34 | return ws 35 | if username in users and users[username]['fingerprint'] != fingerprint: 36 | await ws.send_json({'event': 'name_taken', 'error': '用户名已存在'}) 37 | return ws 38 | # 如果是重连,检查指纹是否匹配 39 | if username in users and users[username]['fingerprint'] == fingerprint: 40 | old_ws = users[username]['ws'] 41 | if old_ws in connections: 42 | del connections[old_ws] # 移除旧连接 43 | logger.debug(f"{username} reconnected with fingerprint {fingerprint}") 44 | else: 45 | users[username] = {'ws': ws, 'fingerprint': fingerprint, 'last_active': time.time()} 46 | logger.debug(f"{username} joined with fingerprint {fingerprint}") 47 | await broadcast({'name': '系统', 'msg': f'{username} 加入了聊天室'}) 48 | connections[ws] = username 49 | users[username]['ws'] = ws 50 | users[username]['last_active'] = time.time() # 更新最后活动时间 51 | await ws.send_json({'event': 'join_success', 'name': username, 'fingerprint': fingerprint}) 52 | 53 | elif event == 'message' and ws in connections: 54 | username = connections[ws] 55 | users[username]['last_active'] = time.time() # 更新最后活动时间 56 | await broadcast({'name': username, 'msg': data['msg']}) 57 | 58 | elif event == 'ping' and ws in connections: 59 | username = connections[ws] 60 | users[username]['last_active'] = time.time() # 更新最后活动时间 61 | await ws.send_json({'event': 'pong'}) 62 | 63 | except Exception as e: 64 | logger.error(f"Error in WebSocket handler: {e}") 65 | finally: 66 | if ws in connections: 67 | username = connections.pop(ws) 68 | if username in users and users[username]['ws'] == ws: 69 | logger.debug(f"{username} disconnected, awaiting potential reconnect") 70 | 71 | return ws 72 | 73 | async def broadcast(data, exclude=None): 74 | if connections: 75 | message = {'event': 'message', 'name': data['name'], 'msg': data['msg']} if 'msg' in data else data 76 | await asyncio.gather( 77 | *[ws.send_json(message) for ws in connections if ws != exclude], 78 | return_exceptions=True 79 | ) 80 | 81 | async def check_offline_users(): 82 | while True: 83 | await asyncio.sleep(5) # 每 5 秒检查一次 84 | current_time = time.time() 85 | for username in list(users.keys()): 86 | user_info = users[username] 87 | if (current_time - user_info['last_active'] > 10 and 88 | (user_info['ws'].closed or user_info['ws'] not in connections)): 89 | # 用户超过 10 秒未活动且连接已关闭,判定为离线 90 | del users[username] 91 | if user_info['ws'] in connections: 92 | del connections[user_info['ws']] 93 | await broadcast({'name': '系统', 'msg': f'{username} 已离线'}) 94 | logger.debug(f"{username} marked as offline") 95 | 96 | async def on_startup(app): 97 | # 在应用启动时注册离线检查任务 98 | asyncio.create_task(check_offline_users()) 99 | logger.info("Offline check task started") 100 | 101 | async def index(request): 102 | return aiohttp_jinja2.render_template('index.html', request, {}) 103 | 104 | app = web.Application() 105 | 106 | # 获取当前脚本文件的目录 107 | current_dir = os.path.dirname(os.path.abspath(__file__)) 108 | aiohttp_jinja2.setup(app, loader=jinja2.FileSystemLoader(current_dir)) 109 | 110 | app.router.add_get('/', index) 111 | app.router.add_get('/ws', websocket_handler) 112 | app.on_startup.append(on_startup) # 注册启动时的回调函数 113 | 114 | if __name__ == '__main__': 115 | logger.info("Server starting on port 8080") 116 | web.run_app(app, host='0.0.0.0', port=8080) 117 | -------------------------------------------------------------------------------- /unique_ip.txt: -------------------------------------------------------------------------------- 1 | 103.200.30.143 2 | 103.200.30.245 3 | 103.200.31.172 4 | 103.214.168.106 5 | 103.223.122.178 6 | 103.226.246.99 7 | 103.228.130.27 8 | 103.228.130.61 9 | 103.230.123.190 10 | 103.240.180.117 11 | 103.240.182.55 12 | 103.246.246.144 13 | 103.252.114.101 14 | 103.252.114.11 15 | 103.252.114.61 16 | 103.252.115.153 17 | 103.252.115.157 18 | 103.252.115.165 19 | 103.252.115.169 20 | 103.252.115.221 21 | 103.252.115.49 22 | 103.252.115.53 23 | 103.252.115.59 24 | 103.39.76.66 25 | 103.42.176.244 26 | 103.56.16.112 27 | 103.73.161.52 28 | 103.97.176.73 29 | 103.97.3.19 30 | 104.16.251.55 31 | 104.16.252.55 32 | 104.23.124.189 33 | 104.23.125.189 34 | 104.244.42.1 35 | 104.244.42.193 36 | 104.244.43.104 37 | 104.244.43.128 38 | 104.244.43.136 39 | 104.244.43.167 40 | 104.244.43.182 41 | 104.244.43.208 42 | 104.244.43.228 43 | 104.244.43.229 44 | 104.244.43.231 45 | 104.244.43.234 46 | 104.244.43.248 47 | 104.244.43.35 48 | 104.244.43.52 49 | 104.244.43.57 50 | 104.244.43.6 51 | 104.244.43.99 52 | 104.244.45.246 53 | 104.244.46.165 54 | 104.244.46.17 55 | 104.244.46.172 56 | 104.244.46.185 57 | 104.244.46.186 58 | 104.244.46.208 59 | 104.244.46.21 60 | 104.244.46.211 61 | 104.244.46.244 62 | 104.244.46.246 63 | 104.244.46.5 64 | 104.244.46.52 65 | 104.244.46.57 66 | 104.244.46.63 67 | 104.244.46.71 68 | 104.244.46.85 69 | 104.244.46.9 70 | 104.244.46.93 71 | 104.31.142.88 72 | 104.31.143.88 73 | 107.181.166.244 74 | 108.160.161.20 75 | 108.160.161.83 76 | 108.160.162.102 77 | 108.160.162.104 78 | 108.160.162.109 79 | 108.160.162.115 80 | 108.160.162.31 81 | 108.160.162.76 82 | 108.160.162.98 83 | 108.160.163.102 84 | 108.160.163.106 85 | 108.160.163.108 86 | 108.160.163.112 87 | 108.160.163.116 88 | 108.160.163.117 89 | 108.160.165.11 90 | 108.160.165.139 91 | 108.160.165.141 92 | 108.160.165.147 93 | 108.160.165.173 94 | 108.160.165.189 95 | 108.160.165.211 96 | 108.160.165.212 97 | 108.160.165.48 98 | 108.160.165.53 99 | 108.160.165.55 100 | 108.160.165.62 101 | 108.160.165.8 102 | 108.160.165.9 103 | 108.160.166.137 104 | 108.160.166.142 105 | 108.160.166.148 106 | 108.160.166.253 107 | 108.160.166.42 108 | 108.160.166.49 109 | 108.160.166.57 110 | 108.160.166.61 111 | 108.160.166.62 112 | 108.160.166.9 113 | 108.160.167.147 114 | 108.160.167.148 115 | 108.160.167.156 116 | 108.160.167.158 117 | 108.160.167.159 118 | 108.160.167.165 119 | 108.160.167.167 120 | 108.160.167.174 121 | 108.160.167.30 122 | 108.160.169.171 123 | 108.160.169.174 124 | 108.160.169.175 125 | 108.160.169.178 126 | 108.160.169.179 127 | 108.160.169.181 128 | 108.160.169.185 129 | 108.160.169.186 130 | 108.160.169.37 131 | 108.160.169.46 132 | 108.160.169.54 133 | 108.160.169.55 134 | 108.160.170.26 135 | 108.160.170.33 136 | 108.160.170.39 137 | 108.160.170.41 138 | 108.160.170.43 139 | 108.160.170.44 140 | 108.160.170.45 141 | 108.160.170.51 142 | 108.160.170.52 143 | 108.160.172.1 144 | 108.160.172.200 145 | 108.160.172.204 146 | 108.160.172.208 147 | 108.160.172.232 148 | 108.160.173.207 149 | 11.45.1.4 150 | 111.243.214.169 151 | 114.43.24.59 152 | 115.126.100.160 153 | 116.89.243.8 154 | 118.107.180.216 155 | 118.184.26.113 156 | 118.184.78.78 157 | 118.193.202.219 158 | 118.193.240.37 159 | 118.193.240.41 160 | 119.28.87.227 161 | 122.10.85.4 162 | 122.248.226.57 163 | 124.11.210.175 164 | 128.121.146.101 165 | 128.121.146.109 166 | 128.121.146.228 167 | 128.121.146.235 168 | 128.121.243.106 169 | 128.121.243.107 170 | 128.121.243.228 171 | 128.121.243.235 172 | 128.121.243.75 173 | 128.121.243.76 174 | 128.121.243.77 175 | 128.242.240.117 176 | 128.242.240.125 177 | 128.242.240.149 178 | 128.242.240.155 179 | 128.242.240.157 180 | 128.242.240.180 181 | 128.242.240.189 182 | 128.242.240.20 183 | 128.242.240.212 184 | 128.242.240.218 185 | 128.242.240.221 186 | 128.242.240.244 187 | 128.242.240.253 188 | 128.242.240.29 189 | 128.242.240.59 190 | 128.242.240.61 191 | 128.242.240.85 192 | 128.242.240.91 193 | 128.242.240.93 194 | 128.242.245.125 195 | 128.242.245.157 196 | 128.242.245.180 197 | 128.242.245.189 198 | 128.242.245.212 199 | 128.242.245.221 200 | 128.242.245.244 201 | 128.242.245.253 202 | 128.242.245.29 203 | 128.242.245.43 204 | 128.242.245.93 205 | 128.242.250.148 206 | 128.242.250.155 207 | 128.242.250.157 208 | 130.211.15.150 209 | 147.75.95.72 210 | 148.163.48.215 211 | 150.107.3.176 212 | 151.101.77.164 213 | 154.83.14.134 214 | 154.83.15.20 215 | 154.83.15.45 216 | 154.85.102.30 217 | 154.85.102.32 218 | 154.92.16.97 219 | 156.233.67.243 220 | 157.240.0.18 221 | 157.240.0.35 222 | 157.240.1.33 223 | 157.240.1.50 224 | 157.240.1.9 225 | 157.240.10.32 226 | 157.240.10.36 227 | 157.240.10.41 228 | 157.240.11.18 229 | 157.240.11.40 230 | 157.240.12.35 231 | 157.240.12.36 232 | 157.240.12.5 233 | 157.240.12.50 234 | 157.240.13.8 235 | 157.240.15.8 236 | 157.240.16.50 237 | 157.240.17.14 238 | 157.240.17.35 239 | 157.240.17.36 240 | 157.240.17.41 241 | 157.240.17.50 242 | 157.240.18.18 243 | 157.240.2.14 244 | 157.240.2.36 245 | 157.240.2.50 246 | 157.240.20.18 247 | 157.240.20.8 248 | 157.240.21.9 249 | 157.240.3.20 250 | 157.240.3.50 251 | 157.240.3.8 252 | 157.240.6.18 253 | 157.240.6.35 254 | 157.240.7.34 255 | 157.240.7.5 256 | 157.240.7.8 257 | 157.240.8.36 258 | 157.240.8.41 259 | 157.240.8.50 260 | 157.240.9.18 261 | 157.240.9.36 262 | 159.106.121.75 263 | 159.138.20.20 264 | 159.65.107.38 265 | 162.125.1.8 266 | 162.125.17.131 267 | 162.125.18.129 268 | 162.125.18.133 269 | 162.125.2.3 270 | 162.125.2.5 271 | 162.125.2.6 272 | 162.125.32.10 273 | 162.125.32.12 274 | 162.125.32.13 275 | 162.125.32.15 276 | 162.125.32.2 277 | 162.125.32.5 278 | 162.125.32.6 279 | 162.125.32.9 280 | 162.125.34.133 281 | 162.125.6.1 282 | 162.125.7.1 283 | 162.125.8.1 284 | 162.125.80.3 285 | 162.125.80.5 286 | 162.125.80.6 287 | 162.125.82.7 288 | 162.125.83.1 289 | 162.220.12.226 290 | 168.143.162.42 291 | 168.143.162.58 292 | 168.143.171.154 293 | 168.143.171.186 294 | 168.143.171.189 295 | 168.143.171.93 296 | 173.208.182.68 297 | 173.231.12.107 298 | 173.234.53.168 299 | 173.236.182.137 300 | 173.236.212.42 301 | 173.244.209.150 302 | 173.244.217.42 303 | 173.252.100.21 304 | 173.252.100.32 305 | 173.252.102.16 306 | 173.252.102.241 307 | 173.252.103.64 308 | 173.252.105.21 309 | 173.252.108.21 310 | 173.252.108.3 311 | 173.252.110.21 312 | 173.252.248.244 313 | 173.252.73.48 314 | 173.252.88.133 315 | 173.252.88.67 316 | 173.255.209.47 317 | 173.255.213.90 318 | 174.36.196.242 319 | 174.36.228.136 320 | 174.37.154.236 321 | 174.37.175.229 322 | 174.37.243.85 323 | 174.37.54.20 324 | 179.60.193.16 325 | 179.60.193.9 326 | 182.50.139.56 327 | 184.173.136.86 328 | 184.72.1.148 329 | 185.45.6.103 330 | 185.45.6.57 331 | 185.45.7.165 332 | 185.45.7.185 333 | 185.45.7.189 334 | 185.45.7.97 335 | 185.60.216.11 336 | 185.60.216.169 337 | 185.60.216.36 338 | 185.60.216.50 339 | 185.60.218.50 340 | 185.60.219.36 341 | 185.60.219.41 342 | 192.133.77.133 343 | 192.133.77.145 344 | 192.133.77.189 345 | 192.133.77.191 346 | 192.133.77.197 347 | 192.133.77.59 348 | 198.27.124.186 349 | 198.44.185.131 350 | 199.16.156.103 351 | 199.16.156.11 352 | 199.16.156.38 353 | 199.16.156.39 354 | 199.16.156.40 355 | 199.16.156.7 356 | 199.16.156.71 357 | 199.16.156.75 358 | 199.16.158.104 359 | 199.16.158.12 360 | 199.16.158.182 361 | 199.16.158.190 362 | 199.16.158.8 363 | 199.16.158.9 364 | 199.193.116.105 365 | 199.59.148.102 366 | 199.59.148.106 367 | 199.59.148.147 368 | 199.59.148.15 369 | 199.59.148.20 370 | 199.59.148.201 371 | 199.59.148.202 372 | 199.59.148.206 373 | 199.59.148.209 374 | 199.59.148.222 375 | 199.59.148.229 376 | 199.59.148.246 377 | 199.59.148.247 378 | 199.59.148.6 379 | 199.59.148.7 380 | 199.59.148.8 381 | 199.59.148.89 382 | 199.59.148.9 383 | 199.59.148.96 384 | 199.59.148.97 385 | 199.59.149.136 386 | 199.59.149.201 387 | 199.59.149.202 388 | 199.59.149.203 389 | 199.59.149.204 390 | 199.59.149.205 391 | 199.59.149.206 392 | 199.59.149.207 393 | 199.59.149.208 394 | 199.59.149.210 395 | 199.59.149.230 396 | 199.59.149.231 397 | 199.59.149.232 398 | 199.59.149.234 399 | 199.59.149.235 400 | 199.59.149.236 401 | 199.59.149.237 402 | 199.59.149.238 403 | 199.59.149.239 404 | 199.59.149.244 405 | 199.59.150.11 406 | 199.59.150.12 407 | 199.59.150.13 408 | 199.59.150.39 409 | 199.59.150.40 410 | 199.59.150.43 411 | 199.59.150.44 412 | 199.59.150.45 413 | 199.59.150.49 414 | 199.96.58.105 415 | 199.96.58.15 416 | 199.96.58.157 417 | 199.96.58.177 418 | 199.96.58.85 419 | 199.96.59.19 420 | 199.96.59.61 421 | 199.96.59.95 422 | 199.96.61.1 423 | 199.96.62.17 424 | 199.96.62.21 425 | 199.96.62.41 426 | 199.96.62.75 427 | 199.96.63.163 428 | 199.96.63.177 429 | 199.96.63.53 430 | 199.96.63.75 431 | 202.160.128.14 432 | 202.160.128.16 433 | 202.160.128.195 434 | 202.160.128.203 435 | 202.160.128.205 436 | 202.160.128.210 437 | 202.160.128.238 438 | 202.160.128.40 439 | 202.160.128.96 440 | 202.160.129.164 441 | 202.160.129.36 442 | 202.160.129.37 443 | 202.160.129.6 444 | 202.160.130.117 445 | 202.160.130.118 446 | 202.160.130.145 447 | 202.160.130.52 448 | 202.160.130.66 449 | 202.160.130.7 450 | 202.182.98.125 451 | 202.53.137.209 452 | 203.111.254.117 453 | 204.79.197.217 454 | 205.186.152.122 455 | 208.101.21.43 456 | 208.101.60.87 457 | 208.31.254.33 458 | 208.43.170.231 459 | 208.43.237.140 460 | 208.77.47.172 461 | 209.95.56.60 462 | 210.209.84.142 463 | 210.56.51.192 464 | 210.56.51.193 465 | 211.104.160.39 466 | 23.101.24.70 467 | 23.225.141.210 468 | 23.234.30.58 469 | 243.185.187.39 470 | 31.13.106.4 471 | 31.13.112.4 472 | 31.13.112.9 473 | 31.13.64.1 474 | 31.13.64.33 475 | 31.13.64.49 476 | 31.13.64.7 477 | 31.13.65.1 478 | 31.13.65.17 479 | 31.13.65.18 480 | 31.13.66.1 481 | 31.13.66.23 482 | 31.13.66.6 483 | 31.13.67.19 484 | 31.13.67.20 485 | 31.13.67.33 486 | 31.13.67.41 487 | 31.13.68.1 488 | 31.13.68.169 489 | 31.13.68.22 490 | 31.13.69.129 491 | 31.13.69.160 492 | 31.13.69.169 493 | 31.13.69.245 494 | 31.13.69.33 495 | 31.13.69.86 496 | 31.13.70.1 497 | 31.13.70.13 498 | 31.13.70.20 499 | 31.13.70.33 500 | 31.13.70.9 501 | 31.13.71.19 502 | 31.13.71.23 503 | 31.13.71.7 504 | 31.13.72.1 505 | 31.13.72.17 506 | 31.13.72.23 507 | 31.13.72.34 508 | 31.13.73.1 509 | 31.13.73.169 510 | 31.13.73.17 511 | 31.13.73.23 512 | 31.13.73.9 513 | 31.13.74.1 514 | 31.13.74.17 515 | 31.13.75.12 516 | 31.13.75.17 517 | 31.13.75.18 518 | 31.13.75.5 519 | 31.13.76.16 520 | 31.13.76.65 521 | 31.13.76.8 522 | 31.13.76.99 523 | 31.13.77.33 524 | 31.13.77.55 525 | 31.13.78.65 526 | 31.13.78.66 527 | 31.13.79.1 528 | 31.13.79.17 529 | 31.13.80.1 530 | 31.13.80.12 531 | 31.13.80.169 532 | 31.13.80.17 533 | 31.13.80.37 534 | 31.13.80.54 535 | 31.13.81.1 536 | 31.13.81.13 537 | 31.13.81.17 538 | 31.13.81.4 539 | 31.13.82.1 540 | 31.13.82.169 541 | 31.13.82.17 542 | 31.13.82.23 543 | 31.13.82.33 544 | 31.13.82.52 545 | 31.13.83.1 546 | 31.13.83.16 547 | 31.13.83.2 548 | 31.13.83.34 549 | 31.13.83.4 550 | 31.13.83.8 551 | 31.13.84.1 552 | 31.13.84.16 553 | 31.13.84.2 554 | 31.13.84.34 555 | 31.13.84.8 556 | 31.13.85.16 557 | 31.13.85.169 558 | 31.13.85.2 559 | 31.13.85.34 560 | 31.13.85.4 561 | 31.13.85.53 562 | 31.13.85.8 563 | 31.13.86.1 564 | 31.13.86.16 565 | 31.13.86.21 566 | 31.13.86.8 567 | 31.13.87.19 568 | 31.13.87.33 569 | 31.13.87.34 570 | 31.13.87.9 571 | 31.13.88.169 572 | 31.13.88.26 573 | 31.13.90.19 574 | 31.13.90.33 575 | 31.13.91.33 576 | 31.13.91.6 577 | 31.13.92.35 578 | 31.13.92.5 579 | 31.13.93.19 580 | 31.13.93.26 581 | 31.13.94.10 582 | 31.13.94.23 583 | 31.13.94.36 584 | 31.13.94.37 585 | 31.13.94.41 586 | 31.13.94.49 587 | 31.13.94.7 588 | 31.13.95.169 589 | 31.13.95.17 590 | 31.13.95.18 591 | 31.13.95.33 592 | 31.13.95.34 593 | 31.13.95.35 594 | 31.13.95.37 595 | 31.13.95.38 596 | 31.13.95.48 597 | 31.13.96.192 598 | 31.13.96.193 599 | 31.13.96.194 600 | 31.13.96.195 601 | 31.13.96.208 602 | 31.13.97.245 603 | 31.13.97.248 604 | 38.121.72.166 605 | 39.109.122.128 606 | 4.78.139.50 607 | 4.78.139.54 608 | 43.226.16.8 609 | 45.114.11.238 610 | 45.114.11.25 611 | 45.77.186.255 612 | 46.82.174.68 613 | 46.82.174.69 614 | 47.88.58.234 615 | 50.117.117.42 616 | 50.23.209.199 617 | 50.87.93.246 618 | 52.175.9.80 619 | 52.58.1.161 620 | 54.234.18.200 621 | 54.245.22.188 622 | 54.89.135.129 623 | 59.188.250.54 624 | 59.24.3.173 625 | 59.24.3.174 626 | 64.13.192.74 627 | 64.13.192.76 628 | 64.13.232.149 629 | 65.49.26.97 630 | 65.49.26.98 631 | 65.49.26.99 632 | 65.49.68.152 633 | 66.220.146.94 634 | 66.220.147.11 635 | 66.220.147.47 636 | 66.220.148.145 637 | 66.220.149.18 638 | 66.220.149.32 639 | 66.220.149.99 640 | 66.220.151.20 641 | 66.220.152.17 642 | 66.220.155.12 643 | 66.220.155.14 644 | 66.220.158.32 645 | 67.15.100.252 646 | 67.15.129.210 647 | 67.228.102.32 648 | 67.228.235.91 649 | 67.228.235.93 650 | 67.230.169.182 651 | 69.162.134.178 652 | 69.171.224.12 653 | 69.171.224.36 654 | 69.171.224.40 655 | 69.171.225.13 656 | 69.171.227.37 657 | 69.171.228.20 658 | 69.171.228.74 659 | 69.171.229.11 660 | 69.171.229.28 661 | 69.171.229.73 662 | 69.171.230.18 663 | 69.171.232.21 664 | 69.171.233.24 665 | 69.171.233.33 666 | 69.171.233.37 667 | 69.171.234.18 668 | 69.171.234.29 669 | 69.171.234.48 670 | 69.171.235.101 671 | 69.171.235.16 672 | 69.171.235.64 673 | 69.171.237.16 674 | 69.171.237.26 675 | 69.171.239.11 676 | 69.171.242.11 677 | 69.171.244.11 678 | 69.171.244.12 679 | 69.171.244.15 680 | 69.171.245.49 681 | 69.171.245.84 682 | 69.171.246.9 683 | 69.171.247.20 684 | 69.171.247.32 685 | 69.171.247.71 686 | 69.171.248.112 687 | 69.171.248.128 688 | 69.171.248.65 689 | 69.197.153.180 690 | 69.30.25.21 691 | 69.50.221.20 692 | 69.63.176.143 693 | 69.63.176.15 694 | 69.63.176.59 695 | 69.63.178.13 696 | 69.63.180.173 697 | 69.63.181.11 698 | 69.63.181.12 699 | 69.63.184.14 700 | 69.63.184.142 701 | 69.63.184.30 702 | 69.63.186.30 703 | 69.63.186.31 704 | 69.63.187.12 705 | 69.63.189.16 706 | 69.63.190.26 707 | 74.86.118.24 708 | 74.86.12.172 709 | 74.86.12.173 710 | 74.86.142.55 711 | 74.86.151.162 712 | 74.86.151.167 713 | 74.86.17.48 714 | 74.86.226.234 715 | 74.86.228.110 716 | 74.86.3.208 717 | 75.126.115.192 718 | 75.126.124.162 719 | 75.126.135.131 720 | 75.126.150.210 721 | 75.126.164.178 722 | 75.126.2.43 723 | 75.126.215.88 724 | 75.126.33.156 725 | 78.16.49.15 726 | 8.7.198.45 727 | 8.7.198.46 728 | 80.87.199.46 729 | 88.191.249.182 730 | 88.191.249.183 731 | 88.191.253.157 732 | 93.179.102.140 733 | 93.46.8.89 734 | 93.46.8.90 735 | 96.44.137.28 736 | 98.159.108.57 737 | 98.159.108.58 738 | 98.159.108.61 739 | 98.159.108.71 740 | --------------------------------------------------------------------------------