├── Config-Templates ├── client.json └── config.json ├── Docs ├── README-EN.md ├── Sing-Box-Android-iOS-en.md ├── Sing-Box-Android-iOS-ru.md ├── Sing-Box-Windows-en.md ├── Sing-Box-Windows-ru.md ├── cf-scan-ip-en.md ├── cf-scan-ip-ru.md ├── cf-settings-en.md └── cf-settings-ru.md ├── LICENSE ├── README.md ├── Scripts ├── install-server.sh ├── ruleset-update.sh ├── sb-manager-en.sh ├── sb-manager-ru.sh ├── sb-pc-linux-en.sh ├── sb-pc-linux-ru.sh └── update-server.sh └── Subscription-Page ├── background.jpg ├── sub-en-hapr.html ├── sub-en.html ├── sub-ru-hapr.html └── sub-ru.html /Config-Templates/client.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "level": "fatal", 4 | "timestamp": true 5 | }, 6 | "dns": { 7 | "servers": [ 8 | { 9 | "tag": "dns-remote", 10 | "address": "tls://1.1.1.1", 11 | "client_subnet": "SERVER-IP", 12 | "detour": "proxy" 13 | }, 14 | { 15 | "tag": "dns-local", 16 | "address": "195.208.4.1", 17 | "detour": "direct" 18 | }, 19 | { 20 | "tag": "dns-block", 21 | "address": "rcode://success" 22 | } 23 | ], 24 | "rules": [ 25 | { 26 | "rule_set": [ 27 | "category-ads-all" 28 | ], 29 | "server": "dns-block", 30 | "disable_cache": true 31 | }, 32 | { 33 | "domain_suffix": [ 34 | "habr.com", 35 | "kemono.su", 36 | "jut.su", 37 | "kara.su", 38 | "theins.ru", 39 | "tvrain.ru", 40 | "echo.msk.ru", 41 | "the-village.ru", 42 | "snob.ru", 43 | "novayagazeta.ru", 44 | "moscowtimes.ru" 45 | ], 46 | "domain_keyword": [ 47 | "animego", 48 | "yummyanime", 49 | "yummy-anime", 50 | "animeportal", 51 | "anime-portal", 52 | "animedub", 53 | "anidub", 54 | "animelib", 55 | "ikianime", 56 | "anilibria" 57 | ], 58 | "rule_set": [ 59 | "telegram", 60 | "google" 61 | ], 62 | "server": "dns-remote" 63 | }, 64 | { 65 | "domain_suffix": [ 66 | ".ru", 67 | ".su", 68 | ".ru.com", 69 | ".ru.net", 70 | "DOMAIN", 71 | "wikipedia.org", 72 | "kudago.com", 73 | "kinescope.io", 74 | "redheadsound.studio", 75 | "plplayer.online", 76 | "lomont.site", 77 | "remanga.org", 78 | "shopstory.live" 79 | ], 80 | "domain_keyword": [ 81 | "xn--", 82 | "miradres", 83 | "premier", 84 | "shutterstock", 85 | "2gis", 86 | "diginetica", 87 | "kinescopecdn", 88 | "researchgate", 89 | "springer", 90 | "nextcloud", 91 | "wiki", 92 | "kaspersky", 93 | "stepik", 94 | "likee", 95 | "snapchat", 96 | "yappy", 97 | "pikabu", 98 | "okko", 99 | "wink", 100 | "kion", 101 | "roblox", 102 | "ozon", 103 | "wildberries", 104 | "aliexpress" 105 | ], 106 | "rule_set": [ 107 | "gov-ru", 108 | "yandex", 109 | "vk", 110 | "mailru", 111 | "zoom", 112 | "reddit", 113 | "twitch", 114 | "tumblr", 115 | "pinterest", 116 | "deviantart", 117 | "duckduckgo", 118 | "yahoo", 119 | "mozilla", 120 | "samsung", 121 | "huawei", 122 | "apple", 123 | "nvidia", 124 | "xiaomi", 125 | "hp", 126 | "asus", 127 | "lenovo", 128 | "lg", 129 | "oracle", 130 | "adobe", 131 | "blender", 132 | "drweb", 133 | "gitlab", 134 | "debian", 135 | "canonical", 136 | "python", 137 | "doi", 138 | "elsevier", 139 | "sciencedirect", 140 | "clarivate", 141 | "sci-hub", 142 | "duolingo", 143 | "aljazeera", 144 | "torrent-clients" 145 | ], 146 | "server": "dns-local" 147 | }, 148 | { 149 | "inbound": [ 150 | "tun-in" 151 | ], 152 | "server": "dns-remote" 153 | } 154 | ], 155 | "final": "dns-local" 156 | }, 157 | "inbounds": [ 158 | { 159 | "type": "tun", 160 | "tag": "tun-in", 161 | "interface_name": "tun0", 162 | "stack": "system", 163 | "address": "172.19.0.1/28", 164 | "auto_route": true, 165 | "strict_route": true, 166 | "sniff_override_destination": true 167 | } 168 | ], 169 | "outbounds": [ 170 | { 171 | "type": "direct", 172 | "tag": "direct" 173 | }, 174 | { 175 | "type": "trojan", 176 | "tag": "proxy", 177 | "server": "DOMAIN", 178 | "server_port": 443, 179 | "password": "TROJAN-PASSWORD", 180 | "tls": { 181 | "enabled": true, 182 | "server_name": "DOMAIN", 183 | "utls": { 184 | "enabled": true, 185 | "fingerprint": "randomized" 186 | } 187 | }, 188 | "multiplex": { 189 | "enabled": true, 190 | "padding": true 191 | }, 192 | "transport": { 193 | "type": "ws", 194 | "path": "/TROJAN-PATH" 195 | } 196 | } 197 | ], 198 | "route": { 199 | "rules": [ 200 | { 201 | "action": "sniff" 202 | }, 203 | { 204 | "protocol": "dns", 205 | "action": "hijack-dns" 206 | }, 207 | { 208 | "ip_is_private": true, 209 | "outbound": "direct" 210 | }, 211 | { 212 | "rule_set": [ 213 | "category-ads-all" 214 | ], 215 | "action": "reject", 216 | "method": "drop" 217 | }, 218 | { 219 | "domain_suffix": [ 220 | "habr.com", 221 | "kemono.su", 222 | "jut.su", 223 | "kara.su", 224 | "theins.ru", 225 | "tvrain.ru", 226 | "echo.msk.ru", 227 | "the-village.ru", 228 | "snob.ru", 229 | "novayagazeta.ru", 230 | "moscowtimes.ru" 231 | ], 232 | "domain_keyword": [ 233 | "animego", 234 | "yummyanime", 235 | "yummy-anime", 236 | "animeportal", 237 | "anime-portal", 238 | "animedub", 239 | "anidub", 240 | "animelib", 241 | "ikianime", 242 | "anilibria" 243 | ], 244 | "rule_set": [ 245 | "telegram", 246 | "google" 247 | ], 248 | "outbound": "proxy" 249 | }, 250 | { 251 | "domain_suffix": [ 252 | ".ru", 253 | ".su", 254 | ".ru.com", 255 | ".ru.net", 256 | "DOMAIN", 257 | "wikipedia.org", 258 | "kudago.com", 259 | "kinescope.io", 260 | "redheadsound.studio", 261 | "plplayer.online", 262 | "lomont.site", 263 | "remanga.org", 264 | "shopstory.live" 265 | ], 266 | "domain_keyword": [ 267 | "xn--", 268 | "miradres", 269 | "premier", 270 | "shutterstock", 271 | "2gis", 272 | "diginetica", 273 | "kinescopecdn", 274 | "researchgate", 275 | "springer", 276 | "nextcloud", 277 | "wiki", 278 | "kaspersky", 279 | "stepik", 280 | "likee", 281 | "snapchat", 282 | "yappy", 283 | "pikabu", 284 | "okko", 285 | "wink", 286 | "kion", 287 | "roblox", 288 | "ozon", 289 | "wildberries", 290 | "aliexpress" 291 | ], 292 | "ip_cidr": [ 293 | "SERVER-IP" 294 | ], 295 | "rule_set": [ 296 | "gov-ru", 297 | "yandex", 298 | "vk", 299 | "mailru", 300 | "zoom", 301 | "reddit", 302 | "twitch", 303 | "tumblr", 304 | "pinterest", 305 | "deviantart", 306 | "duckduckgo", 307 | "yahoo", 308 | "mozilla", 309 | "samsung", 310 | "huawei", 311 | "apple", 312 | "nvidia", 313 | "xiaomi", 314 | "hp", 315 | "asus", 316 | "lenovo", 317 | "lg", 318 | "oracle", 319 | "adobe", 320 | "blender", 321 | "drweb", 322 | "gitlab", 323 | "debian", 324 | "canonical", 325 | "python", 326 | "doi", 327 | "elsevier", 328 | "sciencedirect", 329 | "clarivate", 330 | "sci-hub", 331 | "duolingo", 332 | "aljazeera", 333 | "torrent-clients" 334 | ], 335 | "outbound": "direct" 336 | }, 337 | { 338 | "action": "resolve", 339 | "strategy": "prefer_ipv4" 340 | }, 341 | { 342 | "rule_set": [ 343 | "geoip-ru" 344 | ], 345 | "outbound": "direct" 346 | }, 347 | { 348 | "inbound": [ 349 | "tun-in" 350 | ], 351 | "outbound": "proxy" 352 | } 353 | ], 354 | "rule_set": [ 355 | { 356 | "tag": "torrent-clients", 357 | "type": "remote", 358 | "format": "source", 359 | "url": "https://DOMAIN/RULESETPATH/torrent-clients.json" 360 | }, 361 | { 362 | "tag": "geoip-ru", 363 | "type": "remote", 364 | "format": "binary", 365 | "url": "https://DOMAIN/RULESETPATH/geoip-ru.srs" 366 | }, 367 | { 368 | "tag": "gov-ru", 369 | "type": "remote", 370 | "format": "binary", 371 | "url": "https://DOMAIN/RULESETPATH/geosite-category-gov-ru.srs" 372 | }, 373 | { 374 | "tag": "yandex", 375 | "type": "remote", 376 | "format": "binary", 377 | "url": "https://DOMAIN/RULESETPATH/geosite-yandex.srs" 378 | }, 379 | { 380 | "tag": "google", 381 | "type": "remote", 382 | "format": "binary", 383 | "url": "https://DOMAIN/RULESETPATH/geosite-google.srs" 384 | }, 385 | { 386 | "tag": "telegram", 387 | "type": "remote", 388 | "format": "binary", 389 | "url": "https://DOMAIN/RULESETPATH/geosite-telegram.srs" 390 | }, 391 | { 392 | "tag": "vk", 393 | "type": "remote", 394 | "format": "binary", 395 | "url": "https://DOMAIN/RULESETPATH/geosite-vk.srs" 396 | }, 397 | { 398 | "tag": "mailru", 399 | "type": "remote", 400 | "format": "binary", 401 | "url": "https://DOMAIN/RULESETPATH/geosite-mailru.srs" 402 | }, 403 | { 404 | "tag": "zoom", 405 | "type": "remote", 406 | "format": "binary", 407 | "url": "https://DOMAIN/RULESETPATH/geosite-zoom.srs" 408 | }, 409 | { 410 | "tag": "reddit", 411 | "type": "remote", 412 | "format": "binary", 413 | "url": "https://DOMAIN/RULESETPATH/geosite-reddit.srs" 414 | }, 415 | { 416 | "tag": "twitch", 417 | "type": "remote", 418 | "format": "binary", 419 | "url": "https://DOMAIN/RULESETPATH/geosite-twitch.srs" 420 | }, 421 | { 422 | "tag": "tumblr", 423 | "type": "remote", 424 | "format": "binary", 425 | "url": "https://DOMAIN/RULESETPATH/geosite-tumblr.srs" 426 | }, 427 | { 428 | "tag": "4chan", 429 | "type": "remote", 430 | "format": "binary", 431 | "url": "https://DOMAIN/RULESETPATH/geosite-4chan.srs" 432 | }, 433 | { 434 | "tag": "pinterest", 435 | "type": "remote", 436 | "format": "binary", 437 | "url": "https://DOMAIN/RULESETPATH/geosite-pinterest.srs" 438 | }, 439 | { 440 | "tag": "deviantart", 441 | "type": "remote", 442 | "format": "binary", 443 | "url": "https://DOMAIN/RULESETPATH/geosite-deviantart.srs" 444 | }, 445 | { 446 | "tag": "duckduckgo", 447 | "type": "remote", 448 | "format": "binary", 449 | "url": "https://DOMAIN/RULESETPATH/geosite-duckduckgo.srs" 450 | }, 451 | { 452 | "tag": "yahoo", 453 | "type": "remote", 454 | "format": "binary", 455 | "url": "https://DOMAIN/RULESETPATH/geosite-yahoo.srs" 456 | }, 457 | { 458 | "tag": "mozilla", 459 | "type": "remote", 460 | "format": "binary", 461 | "url": "https://DOMAIN/RULESETPATH/geosite-mozilla.srs" 462 | }, 463 | { 464 | "tag": "samsung", 465 | "type": "remote", 466 | "format": "binary", 467 | "url": "https://DOMAIN/RULESETPATH/geosite-samsung.srs" 468 | }, 469 | { 470 | "tag": "huawei", 471 | "type": "remote", 472 | "format": "binary", 473 | "url": "https://DOMAIN/RULESETPATH/geosite-huawei.srs" 474 | }, 475 | { 476 | "tag": "apple", 477 | "type": "remote", 478 | "format": "binary", 479 | "url": "https://DOMAIN/RULESETPATH/geosite-apple.srs" 480 | }, 481 | { 482 | "tag": "nvidia", 483 | "type": "remote", 484 | "format": "binary", 485 | "url": "https://DOMAIN/RULESETPATH/geosite-nvidia.srs" 486 | }, 487 | { 488 | "tag": "xiaomi", 489 | "type": "remote", 490 | "format": "binary", 491 | "url": "https://DOMAIN/RULESETPATH/geosite-xiaomi.srs" 492 | }, 493 | { 494 | "tag": "hp", 495 | "type": "remote", 496 | "format": "binary", 497 | "url": "https://DOMAIN/RULESETPATH/geosite-hp.srs" 498 | }, 499 | { 500 | "tag": "asus", 501 | "type": "remote", 502 | "format": "binary", 503 | "url": "https://DOMAIN/RULESETPATH/geosite-asus.srs" 504 | }, 505 | { 506 | "tag": "lenovo", 507 | "type": "remote", 508 | "format": "binary", 509 | "url": "https://DOMAIN/RULESETPATH/geosite-lenovo.srs" 510 | }, 511 | { 512 | "tag": "lg", 513 | "type": "remote", 514 | "format": "binary", 515 | "url": "https://DOMAIN/RULESETPATH/geosite-lg.srs" 516 | }, 517 | { 518 | "tag": "oracle", 519 | "type": "remote", 520 | "format": "binary", 521 | "url": "https://DOMAIN/RULESETPATH/geosite-oracle.srs" 522 | }, 523 | { 524 | "tag": "adobe", 525 | "type": "remote", 526 | "format": "binary", 527 | "url": "https://DOMAIN/RULESETPATH/geosite-adobe.srs" 528 | }, 529 | { 530 | "tag": "blender", 531 | "type": "remote", 532 | "format": "binary", 533 | "url": "https://DOMAIN/RULESETPATH/geosite-blender.srs" 534 | }, 535 | { 536 | "tag": "drweb", 537 | "type": "remote", 538 | "format": "binary", 539 | "url": "https://DOMAIN/RULESETPATH/geosite-drweb.srs" 540 | }, 541 | { 542 | "tag": "gitlab", 543 | "type": "remote", 544 | "format": "binary", 545 | "url": "https://DOMAIN/RULESETPATH/geosite-gitlab.srs" 546 | }, 547 | { 548 | "tag": "debian", 549 | "type": "remote", 550 | "format": "binary", 551 | "url": "https://DOMAIN/RULESETPATH/geosite-debian.srs" 552 | }, 553 | { 554 | "tag": "canonical", 555 | "type": "remote", 556 | "format": "binary", 557 | "url": "https://DOMAIN/RULESETPATH/geosite-canonical.srs" 558 | }, 559 | { 560 | "tag": "python", 561 | "type": "remote", 562 | "format": "binary", 563 | "url": "https://DOMAIN/RULESETPATH/geosite-python.srs" 564 | }, 565 | { 566 | "tag": "doi", 567 | "type": "remote", 568 | "format": "binary", 569 | "url": "https://DOMAIN/RULESETPATH/geosite-doi.srs" 570 | }, 571 | { 572 | "tag": "elsevier", 573 | "type": "remote", 574 | "format": "binary", 575 | "url": "https://DOMAIN/RULESETPATH/geosite-elsevier.srs" 576 | }, 577 | { 578 | "tag": "sciencedirect", 579 | "type": "remote", 580 | "format": "binary", 581 | "url": "https://DOMAIN/RULESETPATH/geosite-sciencedirect.srs" 582 | }, 583 | { 584 | "tag": "clarivate", 585 | "type": "remote", 586 | "format": "binary", 587 | "url": "https://DOMAIN/RULESETPATH/geosite-clarivate.srs" 588 | }, 589 | { 590 | "tag": "sci-hub", 591 | "type": "remote", 592 | "format": "binary", 593 | "url": "https://DOMAIN/RULESETPATH/geosite-sci-hub.srs" 594 | }, 595 | { 596 | "tag": "duolingo", 597 | "type": "remote", 598 | "format": "binary", 599 | "url": "https://DOMAIN/RULESETPATH/geosite-duolingo.srs" 600 | }, 601 | { 602 | "tag": "aljazeera", 603 | "type": "remote", 604 | "format": "binary", 605 | "url": "https://DOMAIN/RULESETPATH/geosite-aljazeera.srs" 606 | }, 607 | { 608 | "tag": "category-ads-all", 609 | "type": "remote", 610 | "format": "binary", 611 | "url": "https://DOMAIN/RULESETPATH/geosite-category-ads-all.srs" 612 | } 613 | ], 614 | "auto_detect_interface": true, 615 | "override_android_vpn": true 616 | }, 617 | "experimental": { 618 | "cache_file": { 619 | "enabled": true 620 | } 621 | } 622 | } -------------------------------------------------------------------------------- /Config-Templates/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "level": "fatal", 4 | "output": "box.log", 5 | "timestamp": true 6 | }, 7 | "dns": { 8 | "servers": [ 9 | { 10 | "tag": "dns-remote", 11 | "address": "tls://1.1.1.1" 12 | }, 13 | { 14 | "tag": "dns-block", 15 | "address": "rcode://success" 16 | } 17 | ], 18 | "rules": [ 19 | { 20 | "rule_set": [ 21 | "category-ads-all" 22 | ], 23 | "server": "dns-block", 24 | "disable_cache": true 25 | }, 26 | { 27 | "outbound": "any", 28 | "server": "dns-remote" 29 | } 30 | ] 31 | }, 32 | "inbounds": [ 33 | { 34 | "type": "trojan", 35 | "tag": "trojan-in", 36 | "listen": "127.0.0.1", 37 | "listen_port": 10443, 38 | "users": [ 39 | { 40 | "name": "user", 41 | "password": "TROJAN-PASSWORD" 42 | } 43 | ], 44 | "transport": { 45 | "type": "ws", 46 | "path": "/TROJAN-PATH" 47 | }, 48 | "multiplex": { 49 | "enabled": true, 50 | "padding": true 51 | } 52 | }, 53 | { 54 | "type": "vless", 55 | "tag": "vless-in", 56 | "listen": "127.0.0.1", 57 | "listen_port": 11443, 58 | "users": [ 59 | { 60 | "name": "user", 61 | "uuid": "VLESS-UUID" 62 | } 63 | ], 64 | "transport": { 65 | "type": "ws", 66 | "path": "/VLESS-PATH" 67 | }, 68 | "multiplex": { 69 | "enabled": true, 70 | "padding": true 71 | } 72 | } 73 | ], 74 | "outbounds": [ 75 | { 76 | "type": "direct", 77 | "tag": "direct" 78 | }, 79 | { 80 | "type": "direct", 81 | "tag": "IPv4", 82 | "domain_strategy": "ipv4_only" 83 | }, 84 | { 85 | "type": "socks", 86 | "tag": "warp", 87 | "server": "127.0.0.1", 88 | "server_port": 40000 89 | } 90 | ], 91 | "route": { 92 | "rules": [ 93 | { 94 | "action": "sniff" 95 | }, 96 | { 97 | "protocol": "dns", 98 | "action": "hijack-dns" 99 | }, 100 | { 101 | "rule_set": [ 102 | "category-ads-all" 103 | ], 104 | "action": "reject", 105 | "method": "drop" 106 | }, 107 | { 108 | "rule_set": [ 109 | "geoip-ru", 110 | "gov-ru", 111 | "openai", 112 | "telegram" 113 | ], 114 | "domain_suffix": [ 115 | ".ru", 116 | ".su", 117 | ".ru.com", 118 | ".ru.net", 119 | "rutracker.org", 120 | "rutracker.cc", 121 | "habr.com", 122 | "ntc.party", 123 | "gemini.google.com", 124 | "bard.google.com", 125 | "aistudio.google.com", 126 | "makersuite.google.com", 127 | "alkalimakersuite-pa.clients6.google.com", 128 | "alkalicore-pa.clients6.google.com", 129 | "aida.googleapis.com", 130 | "generativelanguage.googleapis.com", 131 | "proactivebackend-pa.googleapis.com", 132 | "geller-pa.googleapis.com", 133 | "deepmind.com", 134 | "deepmind.google", 135 | "generativeai.google", 136 | "ai.google.dev", 137 | "canva.com" 138 | ], 139 | "domain_keyword": [ 140 | "xn--", 141 | "generativelanguage", 142 | "generativeai" 143 | ], 144 | "outbound": "warp" 145 | }, 146 | { 147 | "rule_set": [ 148 | "google" 149 | ], 150 | "outbound": "IPv4" 151 | } 152 | ], 153 | "rule_set": [ 154 | { 155 | "tag": "geoip-ru", 156 | "type": "local", 157 | "format": "binary", 158 | "path": "/var/www/RULESETPATH/geoip-ru.srs" 159 | }, 160 | { 161 | "tag": "gov-ru", 162 | "type": "local", 163 | "format": "binary", 164 | "path": "/var/www/RULESETPATH/geosite-category-gov-ru.srs" 165 | }, 166 | { 167 | "tag": "google", 168 | "type": "local", 169 | "format": "binary", 170 | "path": "/var/www/RULESETPATH/geosite-google.srs" 171 | }, 172 | { 173 | "tag": "openai", 174 | "type": "local", 175 | "format": "binary", 176 | "path": "/var/www/RULESETPATH/geosite-openai.srs" 177 | }, 178 | { 179 | "tag": "telegram", 180 | "type": "local", 181 | "format": "binary", 182 | "path": "/var/www/RULESETPATH/geosite-telegram.srs" 183 | }, 184 | { 185 | "tag": "category-ads-all", 186 | "type": "local", 187 | "format": "binary", 188 | "path": "/var/www/RULESETPATH/geosite-category-ads-all.srs" 189 | } 190 | ] 191 | }, 192 | "experimental": { 193 | "cache_file": { 194 | "enabled": true 195 | } 196 | } 197 | } -------------------------------------------------------------------------------- /Docs/README-EN.md: -------------------------------------------------------------------------------- 1 | # Secret Sing-Box 2 | 3 |  4 | 5 | [**Russian version**](https://github.com/A-Zuro/Secret-Sing-Box/blob/main/README.md) 6 | 7 | ### Easy setup of Trojan and VLESS proxy with TLS termination on NGINX or HAProxy 8 | This script is designed to fully and quickly configure a secure proxy server with [Sing-Box](https://sing-box.sagernet.org) core and [NGINX](https://nginx.org/en/) or [HAProxy](https://www.haproxy.org) camouflage. Two setup methods: 9 | 10 | - All requests to the proxy are received by NGINX, the requests are passed to Sing-Box only if they contain the correct path (WebSocket or HTTPUpgrade transport) 11 | 12 |  13 | 14 | - All requests to the proxy are received by HAProxy, then Trojan passwords are read from the first 56 bytes of the request by using a Lua script, the requests are passed to Sing-Box only if they contain the correct Trojan password (TCP transport) — [FPPweb3](https://github.com/FPPweb3) method 15 | 16 |  17 | 18 | Both setup methods make it impossible to detect Sing-Box from the outside, which improves security. 19 | 20 | > [!IMPORTANT] 21 | > Recommended OS for the server: Debian 11/12 or Ubuntu 22.04/24.04. Just 512 MB of RAM, 5 GB of disk space and 1 processor core are sufficient. You will also need an IPv4 on the server and your own domain ([How to set it up?](https://github.com/A-Zuro/Secret-Sing-Box/blob/main/Docs/cf-settings-en.md)). Run as root on a newly installed system. It's recommended to update and reboot the system before running this script. 22 | 23 | > [!NOTE] 24 | > With routing rules for Russia. Open ports on the server: 443 and SSH. 25 | > 26 | > This project is created for educational and demonstration purposes. Please make sure that your actions are legal before using it. 27 | 28 | ### Includes: 29 | 1) Sing-Box server setup 30 | 2) NGINX or HAProxy reverse proxy and website setup on port 443 31 | 3) TLS certificates with auto renewal 32 | 4) Security setup (optional) 33 | 5) Multiplexing to optimise connections and to solve TLS in TLS problem 34 | 6) Enable BBR 35 | 7) WARP setup 36 | 8) Optional setup of proxy chains of two or more servers 37 | 9) An option to setup connection to custom Cloudflare IP on the client 38 | 10) Client Sing-Box configs with routing rules for Russia 39 | 11) Automated management of user config files 40 | 12) Page for convenient distribution of subscriptions 41 | 42 | ### Server setup: 43 | 44 | To setup the server, run this command on it: 45 | 46 | ``` 47 | bash <(curl -Ls https://raw.githubusercontent.com/A-Zuro/Secret-Sing-Box/master/Scripts/install-server.sh); source /etc/bash.bashrc 48 | ``` 49 | 50 | Then just enter the necessary information: 51 | 52 |  53 | 54 | > [!CAUTION] 55 | > Passwords, UUIDs, paths and other data in the image above are for example purposes only. Do not use them on your server. 56 | 57 | In the end, the script will show your links to client configs and to subscription page, it's recommended to save them. 58 | 59 | ----- 60 | 61 | To display the settings menu, run this command: 62 | 63 | ``` 64 | ssb 65 | ``` 66 | 67 | Then follow the instructions: 68 | 69 |  70 | 71 | Option 5 synchronizes the settings in client configs of all users, which eliminates the need to edit the config of each user separately. If new rule sets are added to the configs by using option 5.2, they will be automatically downloaded on the server if they are from [SagerNet](https://github.com/SagerNet/sing-geosite/tree/rule-set). 72 | 73 | ### WARP+ keys: 74 | 75 | To activate a WARP+ key, enter this command (replace the key with yours): 76 | 77 | ``` 78 | warp-cli registration license CMD5m479-Y5hS6y79-U06c5mq9 79 | ``` 80 | 81 | ### Client setup: 82 | > [!IMPORTANT] 83 | > On some devices, "stack": "system" in tun interface settings in client configs might not work. In such cases, it is recommended to replace it with "gvisor" by using option 4 in the settings menu (see above). 84 | 85 | [Android and iOS](https://github.com/A-Zuro/Secret-Sing-Box/blob/main/Docs/Sing-Box-Android-iOS-en.md). The guide is given for Android, the app interface is different on iOS, but it has similar settings. 86 | 87 | [Windows](https://github.com/A-Zuro/Secret-Sing-Box/blob/main/Docs/Sing-Box-Windows-en.md). This method is recommended due to more complete routing settings, but you can also import the link to [Hiddify](https://github.com/hiddify/hiddify-app/releases/latest) client app. If some apps are not proxied when using Hiddify, change the config options > service mode > VPN. 88 | 89 | [Linux](https://github.com/A-Zuro/Secret-Sing-Box/blob/main/Docs/README-EN.md#client-setup). Run the command below and follow the instructions or use [Hiddify](https://github.com/hiddify/hiddify-app/releases/latest) client app. 90 | ``` 91 | bash <(curl -Ls https://raw.githubusercontent.com/A-Zuro/Secret-Sing-Box/master/Scripts/sb-pc-linux-en.sh) 92 | ``` 93 | 94 | ### Stargazers over time: 95 | [](https://starchart.cc/A-Zuro/Secret-Sing-Box) 96 | -------------------------------------------------------------------------------- /Docs/Sing-Box-Android-iOS-en.md: -------------------------------------------------------------------------------- 1 | # Setting Up Sing-Box Client on Android and iOS 2 | 3 | ### 1) Install Sing-Box: 4 | Android: https://play.google.com/store/apps/details?id=io.nekohasekai.sfa 5 | 6 | iOS: https://apps.apple.com/us/app/sing-box-vt/id6673731168 7 | 8 | All OS: https://github.com/SagerNet/sing-box/releases/latest 9 | 10 | After installing the app, you can use the subscription page for easier setup or import the config link manually (see below). 11 | 12 | ----- 13 | 14 | ### 2) Open Sing-Box app and import the link: 15 |  16 | 17 |  18 | 19 |  20 | 21 | Paste the link after step 6 22 | 23 | ----- 24 | 25 | ### 3) Check the connection: 26 |  27 | 28 | The proxy should work now 29 | 30 | But the setup is not complete yet 31 | 32 | ----- 33 | 34 | ### 4) It’s recommended to use proxy only for the necessary apps: 35 |  36 | 37 | Then click on 3 dots in the upper right corner, select «Proxy Mode», and then select «Include» 38 | 39 | After that select the apps 40 | 41 | Done! 42 | -------------------------------------------------------------------------------- /Docs/Sing-Box-Android-iOS-ru.md: -------------------------------------------------------------------------------- 1 | # Настройка клиента Sing-Box на Android и iOS 2 | 3 | ### 1) Устанавливаем Sing-Box: 4 | Android: https://play.google.com/store/apps/details?id=io.nekohasekai.sfa 5 | 6 | iOS: https://apps.apple.com/us/app/sing-box-vt/id6673731168 7 | 8 | Все ОС: https://github.com/SagerNet/sing-box/releases/latest 9 | 10 | После установки приложения можно использовать страницу выдачи подписок для более простой настройки или импортировать ссылку на конфиг вручную (см. далее). 11 | 12 | ----- 13 | 14 | ### 2) Открываем Sing-Box и импортируем полученную ссылку: 15 |  16 | 17 |  18 | 19 |  20 | 21 | В пункте 6 вставляем ссылку 22 | 23 | ----- 24 | 25 | ### 3) Проверяем подключение: 26 |  27 | 28 | После этого прокси должен работать 29 | 30 | Но настройка пока не окончена 31 | 32 | ----- 33 | 34 | ### 4) Рекомендуется использовать прокси только для нужных приложений: 35 |  36 | 37 | Далее жмём на 3 точки в правом верхнем углу, выбираем Proxy Mode, после этого выбираем Include 38 | 39 | Потом выбираем нужные приложения 40 | 41 | Готово! 42 | -------------------------------------------------------------------------------- /Docs/Sing-Box-Windows-en.md: -------------------------------------------------------------------------------- 1 | # Setting Up Sing-Box Client on Windows 2 | 3 | ### 1.1) Install Sing-Box (for Windows 10 and 11) 4 | 5 | Press Win + X and open the terminal with admin rights: 6 | 7 |  8 | 9 | Then enter the command: 10 | 11 | ``` 12 | winget install sing-box 13 | ``` 14 | 15 | You can close the terminal after the installation is complete (Sing-Box can also be updated with the same command). 16 | 17 | If you are getting an error telling that winget is absent, then follow the instructions below. 18 | 19 | ----- 20 | 21 | ### 1.2) Install Sing-Box (for Windows versions without winget) 22 | 23 | Download Sing-Box for Windows from the official repository: 24 | 25 | https://github.com/SagerNet/sing-box/releases/latest 26 | 27 | Then extract sing-box.exe from the archive. 28 | 29 | ----- 30 | 31 | ### 2) Create a .cmd or .bat file with such content: 32 | 33 | ``` 34 | @echo off 35 | echo Started sing-box 36 | echo. 37 | echo Do not close this window while sing-box is running 38 | echo. 39 | echo Press Ctrl + C to disconnect 40 | echo. 41 | if not exist "C:\1-sbconfig\" mkdir C:\1-sbconfig 42 | curl --silent -o C:\1-sbconfig\client.json https://domain.com/secret175subscr1pt10n/1-me-VLESS-CLIENT.json 43 | sing-box run -c C:\1-sbconfig\client.json 44 | ``` 45 | 46 | Change the link in the 9th line to yours. 47 | 48 | For Windows versions without winget replace the last line like this and replace the path to sing-box.exe to your actual path: 49 | 50 | ``` 51 | C:\actual\path\to\sing-box.exe run -c C:\1-sbconfig\client.json 52 | ``` 53 | 54 | ----- 55 | 56 | ### 3) Create a shortcut for this .cmd or .bat file 57 | 58 | Then change the settings of the shortcut to run it as admin. 59 | 60 |  61 | 62 |  63 | 64 |  65 | 66 | Then press OK. 67 | 68 | ----- 69 | 70 | ### 4) Click on the shortcut to connect to the server 71 | 72 | Do not close the terminal window while connected to proxy. 73 | 74 | To disconnect, click on the terminal window and then press Ctrl + C. 75 | -------------------------------------------------------------------------------- /Docs/Sing-Box-Windows-ru.md: -------------------------------------------------------------------------------- 1 | # Настройка клиента Sing-Box на Windows 2 | 3 | ### 1.1) Устанавливаем Sing-Box (Windows 10 и 11) 4 | 5 | Жмём Win + X, после этого открываем командную строку с правами аминистратора: 6 | 7 |  8 | 9 | Далее вводим команду: 10 | 11 | ``` 12 | winget install sing-box 13 | ``` 14 | 15 | После окончания установки командную строку можно закрыть (в дальнейшем можно обновлять Sing-Box той же командой). 16 | 17 | Если на этом этапе возникла ошибка, предупреждающая об отсутствии winget, то следуйте инструкциям ниже. 18 | 19 | ----- 20 | 21 | ### 1.2) Устанавливаем Sing-Box (для версий Windows без winget) 22 | 23 | Скачиваем Sing-Box для Windows из официального репозитория: 24 | 25 | https://github.com/SagerNet/sing-box/releases/latest 26 | 27 | Далее извлекаем sing-box.exe из архива. 28 | 29 | ----- 30 | 31 | ### 2) Создаём .cmd или .bat файл с таким содержимым: 32 | 33 | ``` 34 | @echo off 35 | echo Started sing-box 36 | echo. 37 | echo Do not close this window while sing-box is running 38 | echo. 39 | echo Press Ctrl + C to disconnect 40 | echo. 41 | if not exist "C:\1-sbconfig\" mkdir C:\1-sbconfig 42 | curl --silent -o C:\1-sbconfig\client.json https://domain.com/secret175subscr1pt10n/1-me-VLESS-CLIENT.json 43 | sing-box run -c C:\1-sbconfig\client.json 44 | ``` 45 | 46 | Ссылку в предпоследней строчке меняем на свою. 47 | 48 | Для версий Windows, где нет winget, заменяем последнюю строчку таким образом и меняем путь к sing-box.exe на свой: 49 | 50 | ``` 51 | C:\actual\path\to\sing-box.exe run -c C:\1-sbconfig\client.json 52 | ``` 53 | 54 | ----- 55 | 56 | ### 3) Создаём ярлык для этого .cmd или .bat файла 57 | 58 | Далее настраиваем ярлык, чтобы запускать его с правами администратора. 59 | 60 |  61 | 62 |  63 | 64 |  65 | 66 | Везде жмём OK. 67 | 68 | ----- 69 | 70 | ### 4) Для подключения к прокси просто жмём на ярлык 71 | 72 | Не нужно закрывать появившееся окно, пока ПК подключён к прокси. 73 | 74 | Чтобы отключиться, жмём на окно командной строки, а далее Ctrl + C. 75 | -------------------------------------------------------------------------------- /Docs/cf-scan-ip-en.md: -------------------------------------------------------------------------------- 1 | # How to Select a Cloudflare Subnet? 2 | 3 | We will use the **CloudflareScanner** tool to test latency and download speed for Cloudflare's CDN, identifying the fastest IP addresses (IPv4 and IPv6). Run the scanner without VPN/proxy. 4 | 5 | ### Main Features 6 | 7 | - Latency and download speed testing for Cloudflare IP addresses. 8 | - Can be used with custom parameters for more accurate testing. 9 | - Supports testing IP addresses of other CDNs and websites. 10 | 11 | ### Usage on Windows 12 | 13 | 1. Download the executable file from the [release page](https://github.com/Ptechgithub/CloudflareScanner/releases/latest) and extract it. 14 | 2. Run the `CloudflareScanner.exe` file and wait for the test to complete. 15 | 3. After completion, the 10 fastest IP addresses with latency and download speed details will be displayed. 16 | 17 | ### Usage on Linux 18 | 19 | Use the commands below to: 20 | - Identify your device's architecture. 21 | - Download the latest release for your architecture. 22 | - Extract the archive, make the file executable, and run CloudflareScanner. 23 | 24 | ```bash 25 | ARCH=$(uname -m); case $ARCH in x86_64) FILE="CloudflareScanner_linux-amd64.zip";; aarch64|arm64) FILE="CloudflareScanner_linux-arm64.zip";; armv7l) FILE="CloudflareScanner_linux-arm7.zip";; mips64) FILE="CloudflareScanner_linux-mips64.zip";; mips64le) FILE="CloudflareScanner_linux-mips64le.zip";; riscv64) FILE="CloudflareScanner_linux-riscv64.zip";; *) echo "Unsupported architecture: $ARCH"; exit 1;; esac 26 | wget "https://github.com/Ptechgithub/CloudflareScanner/releases/latest/download/$FILE" 27 | unzip "$FILE" -d CloudflareScanner && cd CloudflareScanner 28 | chmod +x CloudflareScanner 29 | ./CloudflareScanner 30 | ``` 31 | 32 | For subsequent runs, use: 33 | 34 | ```bash 35 | cd CloudflareScanner && ./CloudflareScanner 36 | ``` 37 | 38 | ### Usage on macOS 39 | 40 | Follow these steps to run the tool on macOS: 41 | 42 | 1. Download the file corresponding to your CPU: 43 | - **`CloudflareScanner_darwin-arm64.zip`** — for Macs with Apple Silicon (M1, M2, and newer). 44 | - **`CloudflareScanner_darwin-amd64.zip`** — for Intel-based Macs. 45 | 46 | 2. Extract the downloaded archive to a convenient location, such as the `Downloads` folder. 47 | 48 | 3. Open the terminal and execute these commands: 49 | 50 | ```bash 51 | cd ~/Downloads/CloudflareScanner_darwin-arm64 52 | chmod +x CloudflareScanner 53 | ./CloudflareScanner 54 | ``` 55 | 56 | For subsequent runs, use: 57 | 58 | ```bash 59 | cd ~/Downloads/CloudflareScanner_darwin-arm64 && ./CloudflareScanner 60 | ``` 61 | 62 | ### Usage on Android 63 | 64 | First, install [Termux](https://play.google.com/store/apps/details?id=com.termux) on your device. 65 | 66 | Then open the app and use the commands below to: 67 | - Download the latest release for Android. 68 | - Extract the archive, make the file executable, and run CloudflareScanner. 69 | 70 | ```bash 71 | pkg install wget -y 72 | wget "https://github.com/Ptechgithub/CloudflareScanner/releases/latest/download/CloudflareScanner_android-arm64.zip" 73 | unzip "CloudflareScanner_android-arm64.zip" -d CloudflareScanner && cd CloudflareScanner 74 | chmod +x CloudflareScanner 75 | ./CloudflareScanner 76 | ``` 77 | 78 | For subsequent runs, use: 79 | 80 | ```bash 81 | cd CloudflareScanner && ./CloudflareScanner 82 | ``` 83 | 84 | ### Example Output 85 | 86 | | IP Address | Sent | Received | Loss Rate | Average Delay (ms) | Download Speed (MB/s) | 87 | | ------------- | ---- | -------- | --------- | ------------------ | --------------------- | 88 | | 104.27.200.69 | 4 | 4 | 0.00 | 146.23 | 28.64 | 89 | | 172.67.60.78 | 4 | 4 | 0.00 | 139.82 | 15.02 | 90 | | ... | ... | ... | ... | ... | ... | 91 | 92 | Full results are saved to `result.csv` in the current directory. 93 | 94 | ### Internet Provider 95 | 96 | When choosing an optimal subnet, consider your internet connection type: 97 | 98 | - For mobile internet, scan using the connection shared from the phone or modem of the ISP you are going to use later. 99 | - For wired internet connections, scan using the specific internet provider and channel intended for later use. 100 | 101 | Test results can vary significantly based on the telecom or ISP, so it's crucial to perform the scan from the actual network you are going to use. 102 | 103 | ### Additional Parameters 104 | 105 | - `-n`: Number of threads for latency testing (default 200, max 1000). 106 | - `-t`: Number of latency tests per IP (default 4). 107 | - `-dn`: Number of IPs for download speed testing after sorting by latency (default 10). 108 | - `-dt`: Duration of download speed testing per IP in seconds (default 10). 109 | - `-tp`: Port for testing (default 443). 110 | - `-url`: URL for latency (HTTPing) and download speed testing. 111 | - `-httping`: Switch latency test mode to HTTP. 112 | - `-httping-code`: Allowed HTTP status codes for HTTPing latency testing (default 200, 301, 302). 113 | - `-cfcolo`: Match specified locations; locations named by three-letter airport codes, comma-separated, case-insensitive, supported by Cloudflare, AWS CloudFront, only available in HTTPing mode (default all locations). 114 | - `-tl`: Maximum average latency threshold; only display IPs below this value (default 9999 ms). 115 | - `-tll`: Minimum average latency threshold; only display IPs above this value (default 0 ms). 116 | - `-tlr`: Maximum packet loss threshold; only display IPs with loss below this ratio (default 1.00). 117 | - `-sl`: Minimum download speed threshold; only display IPs above this speed (default 0.00 MB/s). 118 | - `-p`: Number of results to display directly after testing (default 10). 119 | - `-f`: IP range data file; if the path contains spaces, wrap it in quotes; supports IP ranges of other CDNs (default ip.txt). 120 | - `-ip`: IP range data directly specified via parameters, separated by commas (default empty). 121 | - `-o`: Write result to file; if path contains spaces, wrap it in quotes; if set to empty [-o ""] do not write to file (default is result.csv). 122 | - `-dd`: Disable download speed test; when disabled, test results will be sorted by latency (by default sorted by download speed) (enabled by default). 123 | - `-allip`: Test all IPs within each range (IPv4 only); defaults to randomly testing one IP per /24 subnet. 124 | - `-v`: Print program version and check for updates. 125 | - `-h`: Show help information and exit. 126 | 127 | For more information and the latest updates, visit [CloudflareScanner GitHub repository](https://github.com/Ptechgithub/CloudflareScanner). 128 | -------------------------------------------------------------------------------- /Docs/cf-scan-ip-ru.md: -------------------------------------------------------------------------------- 1 | # Как выбрать подсеть Cloudflare? 2 | 3 | Для этого воспользуемся инструментом **CloudflareScanner** для тестирования задержки и скорости CDN Cloudflare с целью определения самых быстрых IP-адресов (IPv4 и IPv6). Запускать сканер нужно без VPN/прокси. 4 | 5 | ### Основные возможности 6 | 7 | - Тестирование задержки (latency) и скорости загрузки IP-адресов Cloudflare. 8 | - Возможность указания пользовательских параметров для более точного тестирования. 9 | - Поддержка тестирования IP-адресов других CDN и веб-сайтов. 10 | 11 | ### Использование на Windows 12 | 13 | 1. Скачайте исполняемый файл со [страницы релизов](https://github.com/Ptechgithub/CloudflareScanner/releases/latest) и распакуйте его. 14 | 2. Запустите файл `CloudflareScanner.exe` и дождитесь завершения тестирования. 15 | 3. После завершения теста будут отображены 10 самых быстрых IP-адресов с информацией о задержке и скорости загрузки. 16 | 17 | ### Использование на Linux 18 | 19 | Воспользуемся командами ниже, чтобы: 20 | - Определить архитектуру вашего устройства. 21 | - Загрузить последний релиз для вашей архитектуры. 22 | - Распаковать архив, сделать файл исполняемым и запустить CloudflareScanner. 23 | 24 | ```bash 25 | ARCH=$(uname -m); case $ARCH in x86_64) FILE="CloudflareScanner_linux-amd64.zip";; aarch64|arm64) FILE="CloudflareScanner_linux-arm64.zip";; armv7l) FILE="CloudflareScanner_linux-arm7.zip";; mips64) FILE="CloudflareScanner_linux-mips64.zip";; mips64le) FILE="CloudflareScanner_linux-mips64le.zip";; riscv64) FILE="CloudflareScanner_linux-riscv64.zip";; *) echo "Архитектура не поддерживается: $ARCH"; exit 1;; esac 26 | wget "https://github.com/Ptechgithub/CloudflareScanner/releases/latest/download/$FILE" 27 | unzip "$FILE" -d CloudflareScanner && cd CloudflareScanner 28 | chmod +x CloudflareScanner 29 | ./CloudflareScanner 30 | ``` 31 | 32 | В дальнейшем используйте эту команду для запуска: 33 | 34 | ```bash 35 | cd CloudflareScanner && ./CloudflareScanner 36 | ``` 37 | 38 | ### Использование на macOS 39 | 40 | Для запуска инструмента на macOS выполните следующие шаги: 41 | 42 | 1. Скачайте файл, соответствующий вашему процессору: 43 | - **`CloudflareScanner_darwin-arm64.zip`** — для Mac на Apple Silicon (M1, M2 и новее). 44 | - **`CloudflareScanner_darwin-amd64.zip`** — для Mac на Intel. 45 | 46 | 2. Распакуйте скачанный архив в удобную папку, например, `Downloads`. 47 | 48 | 3. Откройте терминал и выполните команды: 49 | 50 | ```bash 51 | cd ~/Downloads/CloudflareScanner_darwin-arm64 52 | chmod +x CloudflareScanner 53 | ./CloudflareScanner 54 | ``` 55 | 56 | В дальнейшем используйте эту команду для запуска: 57 | 58 | ```bash 59 | cd ~/Downloads/CloudflareScanner_darwin-arm64 && ./CloudflareScanner 60 | ``` 61 | 62 | ### Использование на Android 63 | 64 | Для начала нужно установить [Termux](https://play.google.com/store/apps/details?id=com.termux) на ваше устройство. 65 | 66 | Далее откройте приложение и используйте команды ниже, чтобы: 67 | - Загрузить последний релиз для Android. 68 | - Распаковать архив, сделать файл исполняемым и запустить CloudflareScanner. 69 | 70 | ```bash 71 | pkg install wget -y 72 | wget "https://github.com/Ptechgithub/CloudflareScanner/releases/latest/download/CloudflareScanner_android-arm64.zip" 73 | unzip "CloudflareScanner_android-arm64.zip" -d CloudflareScanner && cd CloudflareScanner 74 | chmod +x CloudflareScanner 75 | ./CloudflareScanner 76 | ``` 77 | 78 | В дальнейшем используйте эту команду для запуска: 79 | 80 | ```bash 81 | cd CloudflareScanner && ./CloudflareScanner 82 | ``` 83 | 84 | ### Пример результата 85 | 86 | | IP-адрес | Отправлено | Получено | Потери | Средняя задержка (мс) | Скорость загрузки (МБ/с) | 87 | |----------------|------------|----------|--------|-----------------------|--------------------------| 88 | | 104.27.200.69 | 4 | 4 | 0.00 | 146.23 | 28.64 | 89 | | 172.67.60.78 | 4 | 4 | 0.00 | 139.82 | 15.02 | 90 | | ... | ... | ... | ... | ... | ... | 91 | 92 | Полные результаты сохраняются в файл `result.csv` в текущей директории. 93 | 94 | ### Интернет-провайдер 95 | 96 | При выборе оптимальной подсети учитывайте тип вашего интернет-подключения: 97 | 98 | - Если вы планируете использовать подсеть с мобильным интернетом, выполните сканирование через подключение, раздаваемое с вашего телефона или модема того оператора, который будете использовать в дальнейшем. 99 | - Для проводного подключения (ШПД) рекомендуется проводить сканирование непосредственно с того интернет-провайдера и канала, который будет использоваться в итоге. 100 | 101 | Результаты тестирования могут значительно различаться в зависимости от используемого оператора связи или провайдера интернет-услуг, поэтому важно сканировать именно из той сети, из которой будет происходить подключение. 102 | 103 | ### Дополнительные параметры 104 | 105 | - `-n`: Количество потоков для тестирования задержки (по умолчанию 200, максимум 1000). 106 | - `-t`: Количество тестов задержки для каждого IP (по умолчанию 4). 107 | - `-dn`: Количество IP для тестирования скорости загрузки после сортировки по задержке (по умолчанию 10). 108 | - `-dt`: Время тестирования скорости загрузки для каждого IP в секундах (по умолчанию 10 секунд). 109 | - `-tp`: Порт для тестирования (по умолчанию 443). 110 | - `-url`: URL для тестирования задержки (HTTPing) и скорости загрузки. 111 | - `-httping`: Переключение режима тестирования задержки на HTTP-протокол. 112 | - `-httping-code`: Допустимые HTTP-коды состояния при тестировании задержки с помощью HTTPing (по умолчанию 200, 301, 302). 113 | - `-cfcolo`: Соответствие указанным локациям; названия локаций — трёхбуквенные коды аэропортов, разделённые запятыми, регистронезависимые, поддерживаются Cloudflare, AWS CloudFront, доступны только в режиме HTTPing (по умолчанию все локации). 114 | - `-tl`: Верхний предел средней задержки; выводить только IP с задержкой ниже указанного значения, верхние и нижние пределы могут использоваться вместе (по умолчанию 9999 мс). 115 | - `-tll`: Нижний предел средней задержки; выводить только IP с задержкой выше указанного значения (по умолчанию 0 мс). 116 | - `-tlr`: Верхний предел коэффициента потерь; выводить только IP с коэффициентом потерь ниже или равным указанному значению, диапазон 0.00~1.00, 0 отфильтровывает любые IP с потерями (по умолчанию 1.00). 117 | - `-sl`: Нижний предел скорости загрузки; выводить только IP с скоростью загрузки выше указанного значения, тестирование будет остановлено при достижении указанного числа [-dn] (по умолчанию 0.00 МБ/с). 118 | - `-p`: Количество результатов для отображения; напрямую отображать указанное количество результатов после тестирования, при установке значения 0 результаты не будут отображаться, и программа завершится (по умолчанию 10). 119 | - `-f`: Файл данных диапазона IP; если путь содержит пробелы, поместите его в кавычки; поддерживает диапазоны IP других CDN (по умолчанию ip.txt). 120 | - `-ip`: Указание диапазона IP; напрямую указывает данные диапазона IP для тестирования через параметры, разделённые запятыми (по умолчанию пусто). 121 | - `-o`: Запись результата в файл; если путь содержит пробелы, поместите его в кавычки; при установке пустого значения [-o ""] результаты не будут записываться в файл (по умолчанию result.csv). 122 | - `-dd`: Отключить тест скорости загрузки; после отключения результаты теста будут отсортированы по задержке (по умолчанию сортируются по скорости загрузки) (по умолчанию включено). 123 | - `-allip`: Тестировать все IP; тестировать каждый IP в диапазоне (только IPv4) (по умолчанию случайным образом тестировать один IP из каждого диапазона /24). 124 | - `-v`: Вывод версии программы и проверка обновлений. 125 | - `-h`: Вывод справочной информации. 126 | 127 | Для получения дополнительной информации и последних обновлений посетите [репозиторий CloudflareScanner на GitHub](https://github.com/Ptechgithub/CloudflareScanner). 128 | -------------------------------------------------------------------------------- /Docs/cf-settings-en.md: -------------------------------------------------------------------------------- 1 | # Setting Up a Domain 2 | 3 | > [!NOTE] 4 | > This guideline shows how to set up a domain using Cloudflare as an example, but you can also use other services. 5 | 6 | ### General settings 7 | First, you need to add your domain to your Cloudflare account and specify Cloudflare NS servers at your domain registrar. 8 | 9 | Next, change the following settings in your Cloudflare account: 10 | 1) SSL/TLS > Overview > Configure > Full 11 | 2) SSL/TLS > Edge Certificates > Minimum TLS Version > TLS 1.2 12 | 3) SSL/TLS > Edge Certificates > TLS 1.3 > Enable 13 | 14 | ### DNS Settings 15 | Example of DNS records: 16 | 17 |  18 | 19 | For the setup option with TLS termination on NGINX and WebSocket or HTTPUpgrade transport, you can enable proxying of both records (this may require to disable ECH). 20 | 21 | > [!IMPORTANT] 22 | > If you already have an A record for this domain created for other purposes, then also create an A record for subdomain: 23 | > 24 | > A | sub | 98.76.54.32 25 | > 26 | > Instead of «sub», specify your subdomain and enter it in the script instead of your domain (e. g. sub.example.com). 27 | 28 | ### Getting Cloudflare API token 29 | Overview > Get your API token > Create Token > Edit zone DNS (Use template) 30 | 31 | Then specify the following settings: 32 | 33 |  34 | 35 | Other settings can be left as is. 36 | 37 | After receiving the token, you need to copy and save it, because it will only be shown once. 38 | 39 | You can use an API key instead of a token, but this is less secure and not recommended. 40 | -------------------------------------------------------------------------------- /Docs/cf-settings-ru.md: -------------------------------------------------------------------------------- 1 | # Настройка домена 2 | 3 | > [!NOTE] 4 | > Здесь показана настройка домена на примере Cloudflare, но можно также использовать другие сервисы. 5 | 6 | ### Общие настройки 7 | Для начала нужно добавить домен в аккаунт Cloudflare и указать NS-сервера Cloudflare у вашего регистратора доменов. 8 | 9 | Далее выполнить следующие настройки в аккаунте Cloudflare: 10 | 1) SSL/TLS > Overview > Configure > Full 11 | 2) SSL/TLS > Edge Certificates > Minimum TLS Version > TLS 1.2 12 | 3) SSL/TLS > Edge Certificates > TLS 1.3 > Включить 13 | 14 | ### Настройки DNS 15 | Пример DNS записей: 16 | 17 |  18 | 19 | Для варианта настройки с терминированием TLS на NGINX и транспортом WebSocket или HTTPUpgrade можно включить проксирование обеих записей (требует [отключения ECH](https://habr.com/ru/articles/856602/)). 20 | 21 | > [!IMPORTANT] 22 | > Если уже есть А запись на этот домен, созданная для других целей, то нужно создать А запись на поддомен: 23 | > 24 | > A | sub | 98.76.54.32 25 | > 26 | > Вместо «sub» придумайте свой поддомен, и в скрипте введите его вместо домена (например, sub.example.com). 27 | 28 | ### Получение API токена Cloudflare 29 | Overview > Get your API token > Create Token > Edit zone DNS (Use template) 30 | 31 | Далее нужно указать следующие настройки: 32 | 33 |  34 | 35 | Остальные настройки можно оставить, как есть. 36 | 37 | После получения токен нужно скопировать и сохранить, потому что его покажут только 1 раз. 38 | 39 | Вместо токена можно использовать API ключ, но это менее безопасно и не рекомендуется. 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 A-Zuro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Secret Sing-Box 2 | 3 |  4 | 5 | [**English version**](https://github.com/A-Zuro/Secret-Sing-Box/blob/main/Docs/README-EN.md) 6 | 7 | ### Простая настройка прокси с использованием протоколов Trojan и VLESS и терминированием TLS на NGINX или HAProxy 8 | Данный скрипт предназначен для полной и быстрой настройки защищённого прокси-сервера с ядром [Sing-Box](https://sing-box.sagernet.org) и маскировкой при помощи [NGINX](https://nginx.org/ru/) или [HAProxy](https://www.haproxy.org). Два варианта настройки на выбор: 9 | 10 | - Все запросы к прокси принимает NGINX, запросы передаются на Sing-Box только при наличии в них правильного пути (транспорт WebSocket или HTTPUpgrade) 11 | 12 |  13 | 14 | - Все запросы к прокси принимает HAProxy, пароли Trojan считываются из первых 56 байт запроса с помощью скрипта на Lua, запросы передаются на Sing-Box только при наличии в них правильного пароля Trojan (транспорт TCP) — метод [FPPweb3](https://github.com/FPPweb3) 15 | 16 |  17 | 18 | Оба варианта настройки делают невозможным обнаружение Sing-Box снаружи, что повышает уровень безопасности. 19 | 20 | > [!IMPORTANT] 21 | > Рекомендуемая ОС для сервера: Debian 11/12 или Ubuntu 22.04/24.04. Достаточно 512 Мб оперативной памяти, 5 Гб на диске и 1 ядра процессора. Для настройки понадобится IPv4 на сервере и свой домен ([Как настроить?](https://github.com/A-Zuro/Secret-Sing-Box/blob/main/Docs/cf-settings-ru.md)). Запускайте от имени root на чистой системе. Рекомендуется обновить систему и перезагрузить сервер перед запуском скрипта. 22 | 23 | > [!NOTE] 24 | > С правилами маршрутизации для России. Открытые порты на сервере: 443 и SSH. 25 | > 26 | > Данный проект создан в образовательных и демонстрационных целях. Пожалуйста, убедитесь в законности ваших действий перед использованием. 27 | 28 | ### Включает: 29 | 1) Настройку сервера Sing-Box 30 | 2) Настройку обратного прокси на NGINX или HAProxy на 443 порту, а также сайта-заглушки 31 | 3) TLS сертификаты с автоматическим обновлением 32 | 4) Настройку безопасности (опционально) 33 | 5) Мультиплексирование для оптимизации соединений и решения проблемы TLS внутри TLS 34 | 6) Включение BBR 35 | 7) Настройку WARP 36 | 8) Возможность настройки цепочек из двух и более серверов 37 | 9) Возможность настраивать на клиенте подключение к выбранному IP Cloudflare 38 | 10) Клиентские конфиги Sing-Box с правилами маршрутизации для России 39 | 11) Автоматизированное управление конфигами пользователей 40 | 12) Страницу для удобной выдачи подписок 41 | 42 | ### Настройка сервера: 43 | 44 | Для настройки сервера введите на нём эту команду: 45 | 46 | ``` 47 | bash <(curl -Ls https://raw.githubusercontent.com/A-Zuro/Secret-Sing-Box/master/Scripts/install-server.sh); source /etc/bash.bashrc 48 | ``` 49 | 50 | Затем просто введите необходимую информацию: 51 | 52 |  53 | 54 | > [!CAUTION] 55 | > Пароли, UUID, пути и другие данные на изображении выше даны для примера. Не используйте их на своём сервере. 56 | 57 | В конце скрипт покажет ссылки на клиентские конфиги и страницу выдачи подписок, рекомендуется их сохранить. 58 | 59 | ----- 60 | 61 | Чтобы вывести меню настроек, введите команду: 62 | 63 | ``` 64 | ssb 65 | ``` 66 | 67 | Далее следуйте инструкциям: 68 | 69 |  70 | 71 | Пункт 5 синхронизирует настройки в клиентских конфигах всех пользователей, что позволяет не редактировать конфиг каждого пользователя отдельно. При добавлении в конфиги новых наборов правил (rule sets) с помощью пункта 5.2, они будут автоматически загружены на сервер, если это наборы правил от [SagerNet](https://github.com/SagerNet/sing-geosite/tree/rule-set). 72 | 73 | ### Ключи WARP+: 74 | 75 | Чтобы активировать ключ WARP+, введите эту команду, заменив ключ на свой: 76 | 77 | ``` 78 | warp-cli registration license CMD5m479-Y5hS6y79-U06c5mq9 79 | ``` 80 | 81 | ### Настройка клиентов: 82 | > [!IMPORTANT] 83 | > На некоторых устройствах может не работать "stack": "system" в настройках tun-интерфейса в клиентских конфигах. В таких случаях рекомендуется заменить его на "gvisor" с помощью пункта 4 в меню настроек (см. выше). 84 | 85 | [Android и iOS](https://github.com/A-Zuro/Secret-Sing-Box/blob/main/Docs/Sing-Box-Android-iOS-ru.md). Инструкция дана для Android, на iOS интерфейс приложения отличается, но настройки аналогичны. 86 | 87 | [Windows](https://github.com/A-Zuro/Secret-Sing-Box/blob/main/Docs/Sing-Box-Windows-ru.md). Рекомендован данный способ, так как он обеспечивает более полные настройки маршрутизации, но можно также вставить ссылку в клиент [Hiddify](https://github.com/hiddify/hiddify-app/releases/latest). Если при использовании Hiddify не проксируются некоторые приложения, то измените параметры конфигурации > режим работы > VPN. 88 | 89 | [Linux](https://github.com/A-Zuro/Secret-Sing-Box#%D0%BD%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0-%D0%BA%D0%BB%D0%B8%D0%B5%D0%BD%D1%82%D0%BE%D0%B2). Запустите команду ниже и следуйте инструкциям или используйте клиент [Hiddify](https://github.com/hiddify/hiddify-app/releases/latest). 90 | ``` 91 | bash <(curl -Ls https://raw.githubusercontent.com/A-Zuro/Secret-Sing-Box/master/Scripts/sb-pc-linux-ru.sh) 92 | ``` 93 | 94 | ### Звёзды по времени: 95 | [](https://starchart.cc/A-Zuro/Secret-Sing-Box) 96 | -------------------------------------------------------------------------------- /Scripts/ruleset-update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rulesetpath=$(grep "alias /var/www/" /etc/nginx/nginx.conf | head -n 1) 4 | rulesetpath=${rulesetpath#*"alias /var/www/"} 5 | rulesetpath=${rulesetpath%"/;"*} 6 | rulesetlist=$(ls -A1 /var/www/${rulesetpath} | grep -v ".1") 7 | 8 | for k in $(seq 1 $(echo "$rulesetlist" | wc -l)) 9 | do 10 | ruleset=$(echo "$rulesetlist" | sed -n "${k}p") 11 | wget -q -O /var/www/${rulesetpath}/${ruleset}.1 https://github.com/SagerNet/sing-geosite/raw/rule-set/${ruleset} 12 | if [ $? -eq 0 ] 13 | then 14 | mv -f /var/www/${rulesetpath}/${ruleset}.1 /var/www/${rulesetpath}/${ruleset} 15 | fi 16 | done 17 | 18 | wget -q -O /var/www/${rulesetpath}/torrent-clients.json.1 https://raw.githubusercontent.com/FPPweb3/sb-rule-sets/main/torrent-clients.json 19 | 20 | if [ $? -eq 0 ] 21 | then 22 | mv -f /var/www/${rulesetpath}/torrent-clients.json.1 /var/www/${rulesetpath}/torrent-clients.json 23 | fi 24 | 25 | wget -q -O /var/www/${rulesetpath}/geoip-ru.srs.1 https://github.com/SagerNet/sing-geoip/raw/rule-set/geoip-ru.srs 26 | 27 | if [ $? -eq 0 ] 28 | then 29 | mv -f /var/www/${rulesetpath}/geoip-ru.srs.1 /var/www/${rulesetpath}/geoip-ru.srs 30 | fi 31 | 32 | chmod -R 755 /var/www/${rulesetpath} 33 | journalctl --vacuum-time=7days &> /dev/null 34 | -------------------------------------------------------------------------------- /Scripts/sb-manager-en.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | textcolor='\033[1;34m' 4 | red='\033[1;31m' 5 | grey='\033[1;30m' 6 | clear='\033[0m' 7 | 8 | check_root() { 9 | if [[ $EUID -ne 0 ]] 10 | then 11 | echo "" 12 | echo -e "${red}Error: this command should be run as root, use \"sudo -i\" command${clear}" 13 | echo "" 14 | exit 1 15 | fi 16 | } 17 | 18 | banner() { 19 | echo "" 20 | echo "╔══╗ ╔══╗ ╦══╗" 21 | echo "║ ║ ║ ║" 22 | echo "╚══╗ ╚══╗ ╠══╣" 23 | echo " ║ ║ ║ ║" 24 | echo "╚══╝ ╚══╝ ╩══╝" 25 | } 26 | 27 | templates() { 28 | wget -q -O /var/www/${subspath}/template-1.json https://raw.githubusercontent.com/A-Zuro/Secret-Sing-Box/master/Config-Templates/client.json 29 | 30 | if [ $? -eq 0 ] 31 | then 32 | outboundnum=$(jq '[.outbounds[].tag] | index("proxy")' /var/www/${subspath}/template-1.json) 33 | 34 | if [ ! -f /etc/haproxy/auth.lua ] && [[ $(jq -r '.inbounds[] | select(.tag=="trojan-in") | .transport.type' /etc/sing-box/config.json) == "ws" ]] 35 | then 36 | mv -f /var/www/${subspath}/template-1.json /var/www/${subspath}/template.json 37 | elif [ ! -f /etc/haproxy/auth.lua ] && [[ $(jq -r '.inbounds[] | select(.tag=="trojan-in") | .transport.type' /etc/sing-box/config.json) == "httpupgrade" ]] 38 | then 39 | echo "$(jq ".outbounds[${outboundnum}].transport.type = \"httpupgrade\"" /var/www/${subspath}/template-1.json)" > /var/www/${subspath}/template.json 40 | rm /var/www/${subspath}/template-1.json 41 | else 42 | echo "$(jq "del(.outbounds[${outboundnum}].transport.type) | del(.outbounds[${outboundnum}].transport.path)" /var/www/${subspath}/template-1.json)" > /var/www/${subspath}/template.json 43 | rm /var/www/${subspath}/template-1.json 44 | fi 45 | 46 | outboundnum="" 47 | fi 48 | 49 | if [ ! -f /var/www/${subspath}/template-loc.json ] && [ -f /var/www/${subspath}/template.json ] && [ $(jq -e . < /var/www/${subspath}/template.json &>/dev/null; echo $?) -eq 0 ] && [ -s /var/www/${subspath}/template.json ] 50 | then 51 | cp /var/www/${subspath}/template.json /var/www/${subspath}/template-loc.json 52 | fi 53 | } 54 | 55 | get_ip() { 56 | serverip=$(curl -s -4 https://cloudflare.com/cdn-cgi/trace | grep "ip" | cut -d "=" -f 2) 57 | 58 | if [[ ! $serverip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] 59 | then 60 | serverip=$(curl -s ipinfo.io/ip) 61 | fi 62 | 63 | if [[ ! $serverip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] 64 | then 65 | serverip=$(curl -s 2ip.io) 66 | fi 67 | } 68 | 69 | get_data() { 70 | get_ip 71 | 72 | if [ -f /etc/haproxy/auth.lua ] 73 | then 74 | domain=$(grep "/etc/haproxy/certs/" /etc/haproxy/haproxy.cfg | head -n 1) 75 | domain=${domain#*"/etc/haproxy/certs/"} 76 | domain=${domain%".pem"*} 77 | else 78 | domain=$(grep "ssl_certificate" /etc/nginx/nginx.conf | head -n 1) 79 | domain=${domain#*"/live/"} 80 | domain=${domain%"/"*} 81 | 82 | trojanpath=$(jq -r '.inbounds[] | select(.tag=="trojan-in") | .transport.path' /etc/sing-box/config.json) 83 | trojanpath=${trojanpath#"/"} 84 | 85 | vlesspath=$(jq -r '.inbounds[] | select(.tag=="vless-in") | .transport.path' /etc/sing-box/config.json) 86 | vlesspath=${vlesspath#"/"} 87 | fi 88 | 89 | subspath=$(grep "location ~ ^/" /etc/nginx/nginx.conf | head -n 1) 90 | subspath=${subspath#*"location ~ ^/"} 91 | subspath=${subspath%" {"*} 92 | 93 | rulesetpath=$(grep "alias /var/www/" /etc/nginx/nginx.conf | head -n 1) 94 | rulesetpath=${rulesetpath#*"alias /var/www/"} 95 | rulesetpath=${rulesetpath%"/;"*} 96 | 97 | templates 98 | 99 | tempip=$(jq -r '.dns.servers[] | select(has("client_subnet")) | .client_subnet' /var/www/${subspath}/template.json) 100 | tempdomain=$(jq -r '.outbounds[] | select(.tag=="proxy") | .server' /var/www/${subspath}/template.json) 101 | 102 | if [ -z ${tempip} ] 103 | then 104 | tempip=$(jq -r '.route.rules[] | select(has("ip_cidr")) | .ip_cidr[0]' /var/www/${subspath}/template.json) 105 | fi 106 | 107 | temprulesetpath=$(jq -r ".route.rule_set[-1].url" /var/www/${subspath}/template.json) 108 | temprulesetpath=${temprulesetpath#*"https://${tempdomain}/"} 109 | temprulesetpath=${temprulesetpath%"/"*} 110 | } 111 | 112 | validate_template() { 113 | if [ $(jq -e . < /var/www/${subspath}/template.json &>/dev/null; echo $?) -ne 0 ] || [ ! -s /var/www/${subspath}/template.json ] 114 | then 115 | echo -e "${red}Error: failed to download data from GitHub${clear}" 116 | echo "" 117 | main_menu 118 | fi 119 | } 120 | 121 | validate_local_template() { 122 | if [ $(jq -e . < /var/www/${subspath}/template-loc.json &>/dev/null; echo $?) -ne 0 ] || [ ! -s /var/www/${subspath}/template-loc.json ] || [[ $(jq 'any(.inbounds[]; .tag == "tun-in")' /var/www/${subspath}/template-loc.json) == "false" ]] || [[ $(jq 'any(.outbounds[]; .tag == "proxy")' /var/www/${subspath}/template-loc.json) == "false" ]] 123 | then 124 | echo -e "${red}Error: template-loc.json contains mistakes, corrections needed${clear}" 125 | echo "" 126 | echo -e "${textcolor}[?]${clear} Enter ${textcolor}reset${clear} to reset the template to default version or enter ${textcolor}x${clear} to exit:" 127 | read resettemp 128 | echo "" 129 | if [[ "$resettemp" == "reset" ]] 130 | then 131 | validate_template 132 | rm /var/www/${subspath}/template-loc.json 133 | cp /var/www/${subspath}/template.json /var/www/${subspath}/template-loc.json 134 | echo "The template has been reset to its default version" 135 | echo "" 136 | fi 137 | main_menu 138 | fi 139 | } 140 | 141 | exit_username() { 142 | if [[ $username == "x" ]] || [[ $username == "х" ]] 143 | then 144 | username="" 145 | main_menu 146 | fi 147 | } 148 | 149 | check_username_add() { 150 | while [[ -f /var/www/${subspath}/${username}-TRJ-CLIENT.json ]] || [[ ! $username =~ ^[a-zA-Z0-9_-]+$ ]] || [ -z "$username" ] 151 | do 152 | if [[ -f /var/www/${subspath}/${username}-TRJ-CLIENT.json ]] 153 | then 154 | echo -e "${red}Error: this user already exists${clear}" 155 | echo "" 156 | elif [ -z "$username" ] 157 | then 158 | : 159 | elif [[ ! $username =~ ^[a-zA-Z0-9_-]+$ ]] 160 | then 161 | echo -e "${red}Error: the username should contain only letters, numbers, _ and - symbols${clear}" 162 | echo "" 163 | fi 164 | echo -e "${textcolor}[?]${clear} Enter the name of the new user or enter ${textcolor}x${clear} to exit:" 165 | read username 166 | [[ ! -z $username ]] && echo "" 167 | done 168 | } 169 | 170 | check_trjpass() { 171 | while ([[ $trjpass =~ '"' ]] || [[ $(jq "any(.inbounds[].users[]; .password == \"$trjpass\")" /etc/sing-box/config.json) == "true" ]]) && [ ! -z "$trjpass" ] 172 | do 173 | if [[ $trjpass =~ '"' ]] 174 | then 175 | echo -e "${red}Error: Trojan password should not contain quotes \"${clear}" 176 | else 177 | echo -e "${red}Error: this password is already assigned to another user${clear}" 178 | fi 179 | echo "" 180 | echo -e "${textcolor}[?]${clear} Enter the password for Trojan or leave this empty to generate a random password:" 181 | read trjpass 182 | [[ ! -z $trjpass ]] && echo "" 183 | done 184 | } 185 | 186 | check_uuid() { 187 | while ([[ ! $uuid =~ ^\{?[A-F0-9a-f]{8}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{12}\}?$ ]] || [[ $(jq "any(.inbounds[].users[]; .uuid == \"$uuid\")" /etc/sing-box/config.json) == "true" ]]) && [ ! -z "$uuid" ] 188 | do 189 | if [[ ! $uuid =~ ^\{?[A-F0-9a-f]{8}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{12}\}?$ ]] 190 | then 191 | echo -e "${red}Error: this is not an UUID${clear}" 192 | elif [[ $(jq "any(.inbounds[].users[]; .uuid == \"$uuid\")" /etc/sing-box/config.json) == "true" ]] 193 | then 194 | echo -e "${red}Error: this UUID is already assigned to another user${clear}" 195 | fi 196 | echo "" 197 | echo -e "${textcolor}[?]${clear} Enter the UUID for VLESS or leave this empty to generate a random UUID:" 198 | read uuid 199 | [[ ! -z $uuid ]] && echo "" 200 | done 201 | } 202 | 203 | enter_user_data_add_ws() { 204 | echo -e "${textcolor}[?]${clear} Enter the name of the new user or enter ${textcolor}x${clear} to exit:" 205 | read username 206 | [[ ! -z $username ]] && echo "" 207 | check_username_add 208 | exit_username 209 | echo -e "${textcolor}[?]${clear} Enter the password for Trojan or leave this empty to generate a random password:" 210 | read trjpass 211 | [[ ! -z $trjpass ]] && echo "" 212 | check_trjpass 213 | echo -e "${textcolor}[?]${clear} Enter the UUID for VLESS or leave this empty to generate a random UUID:" 214 | read uuid 215 | [[ ! -z $uuid ]] && echo "" 216 | check_uuid 217 | } 218 | 219 | enter_user_data_add_haproxy() { 220 | echo -e "${textcolor}[?]${clear} Enter the name of the new user or enter ${textcolor}x${clear} to exit:" 221 | read username 222 | [[ ! -z $username ]] && echo "" 223 | check_username_add 224 | exit_username 225 | echo -e "${textcolor}[?]${clear} Enter the password for Trojan or leave this empty to generate a random password:" 226 | read trjpass 227 | [[ ! -z $trjpass ]] && echo "" 228 | check_trjpass 229 | } 230 | 231 | enter_user_data_add() { 232 | if [ -f /etc/haproxy/auth.lua ] 233 | then 234 | enter_user_data_add_haproxy 235 | else 236 | enter_user_data_add_ws 237 | fi 238 | } 239 | 240 | generate_pass() { 241 | if [ -z "$trjpass" ] 242 | then 243 | trjpass=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 30) 244 | fi 245 | 246 | if [ ! -f /etc/haproxy/auth.lua ] && [ -z "$uuid" ] 247 | then 248 | uuid=$(sing-box generate uuid) 249 | fi 250 | } 251 | 252 | add_to_server_conf() { 253 | inboundnum=$(jq '[.inbounds[].tag] | index("trojan-in")' /etc/sing-box/config.json) 254 | echo "$(jq ".inbounds[${inboundnum}].users[.inbounds[${inboundnum}].users | length] |= . + {\"name\":\"${username}\",\"password\":\"${trjpass}\"}" /etc/sing-box/config.json)" > /etc/sing-box/config.json 255 | 256 | if [ ! -f /etc/haproxy/auth.lua ] 257 | then 258 | inboundnum=$(jq '[.inbounds[].tag] | index("vless-in")' /etc/sing-box/config.json) 259 | echo "$(jq ".inbounds[${inboundnum}].users[.inbounds[${inboundnum}].users | length] |= . + {\"name\":\"${username}\",\"uuid\":\"${uuid}\"}" /etc/sing-box/config.json)" > /etc/sing-box/config.json 260 | fi 261 | 262 | systemctl reload sing-box.service 263 | } 264 | 265 | add_to_client_conf() { 266 | cp /var/www/${subspath}/template.json /var/www/${subspath}/${username}-TRJ-CLIENT.json 267 | outboundnum=$(jq '[.outbounds[].tag] | index("proxy")' /var/www/${subspath}/${username}-TRJ-CLIENT.json) 268 | if [ ! -f /etc/haproxy/auth.lua ] 269 | then 270 | echo "$(jq ".outbounds[${outboundnum}].password = \"${trjpass}\" | .outbounds[${outboundnum}].transport.path = \"/${trojanpath}\"" /var/www/${subspath}/${username}-TRJ-CLIENT.json)" > /var/www/${subspath}/${username}-TRJ-CLIENT.json 271 | else 272 | echo "$(jq ".outbounds[${outboundnum}].password = \"${trjpass}\"" /var/www/${subspath}/${username}-TRJ-CLIENT.json)" > /var/www/${subspath}/${username}-TRJ-CLIENT.json 273 | fi 274 | sed -i -e "s/$tempdomain/$domain/g" -e "s/$tempip/$serverip/g" -e "s/$temprulesetpath/$rulesetpath/g" /var/www/${subspath}/${username}-TRJ-CLIENT.json 275 | 276 | if [ ! -f /etc/haproxy/auth.lua ] 277 | then 278 | cp /var/www/${subspath}/template.json /var/www/${subspath}/${username}-VLESS-CLIENT.json 279 | outboundnum=$(jq '[.outbounds[].tag] | index("proxy")' /var/www/${subspath}/${username}-VLESS-CLIENT.json) 280 | echo "$(jq ".outbounds[${outboundnum}].password = \"${uuid}\" | .outbounds[${outboundnum}].transport.path = \"/${vlesspath}\" | .outbounds[${outboundnum}].type = \"vless\" | .outbounds[${outboundnum}] |= with_entries(.key |= if . == \"password\" then \"uuid\" else . end)" /var/www/${subspath}/${username}-VLESS-CLIENT.json)" > /var/www/${subspath}/${username}-VLESS-CLIENT.json 281 | sed -i -e "s/$tempdomain/$domain/g" -e "s/$tempip/$serverip/g" -e "s/$temprulesetpath/$rulesetpath/g" /var/www/${subspath}/${username}-VLESS-CLIENT.json 282 | fi 283 | 284 | echo -e "Added user ${textcolor}${username}${clear}:" 285 | echo "https://${domain}/${subspath}/${username}-TRJ-CLIENT.json" 286 | if [ ! -f /etc/haproxy/auth.lua ] 287 | then 288 | echo "https://${domain}/${subspath}/${username}-VLESS-CLIENT.json" 289 | fi 290 | echo "" 291 | } 292 | 293 | add_to_auth_lua() { 294 | if [ -f /etc/haproxy/auth.lua ] 295 | then 296 | passhash=$(echo -n "${trjpass}" | openssl dgst -sha224 | sed 's/.* //') 297 | sed -i "2i \ \ \ \ [\"${passhash}\"] = true," /etc/haproxy/auth.lua 298 | systemctl reload haproxy.service 299 | fi 300 | } 301 | 302 | check_username_del() { 303 | while [[ ! -f /var/www/${subspath}/${username}-TRJ-CLIENT.json ]] 304 | do 305 | echo -e "${red}Error: a user with this name does not exist${clear}" 306 | echo "" 307 | echo -e "${textcolor}[?]${clear} Enter the name of the user or enter ${textcolor}x${clear} to exit:" 308 | read username 309 | echo "" 310 | exit_username 311 | done 312 | } 313 | 314 | enter_user_data_del() { 315 | echo -e "${textcolor}[?]${clear} Enter the name of the user or enter ${textcolor}x${clear} to exit:" 316 | read username 317 | echo "" 318 | exit_username 319 | check_username_del 320 | } 321 | 322 | del_from_server_conf() { 323 | inboundnum=$(jq '[.inbounds[].tag] | index("trojan-in")' /etc/sing-box/config.json) 324 | echo "$(jq /etc/sing-box/config.json 325 | 326 | if [ ! -f /etc/haproxy/auth.lua ] 327 | then 328 | inboundnum=$(jq '[.inbounds[].tag] | index("vless-in")' /etc/sing-box/config.json) 329 | echo "$(jq /etc/sing-box/config.json 330 | fi 331 | 332 | systemctl reload sing-box.service 333 | } 334 | 335 | del_client_conf() { 336 | if [ ! -f /etc/haproxy/auth.lua ] 337 | then 338 | rm /var/www/${subspath}/${username}-TRJ-CLIENT.json /var/www/${subspath}/${username}-VLESS-CLIENT.json 339 | else 340 | rm /var/www/${subspath}/${username}-TRJ-CLIENT.json 341 | fi 342 | echo -e "Deleted user ${textcolor}${username}${clear}" 343 | echo "" 344 | } 345 | 346 | del_from_auth_lua() { 347 | if [ -f /etc/haproxy/auth.lua ] 348 | then 349 | inboundnum=$(jq '[.inbounds[].tag] | index("trojan-in")' /etc/sing-box/config.json) 350 | trjpass=$(jq -r ".inbounds[${inboundnum}].users[] | select(.name==\"${username}\") | .password" /etc/sing-box/config.json) 351 | passhash=$(echo -n "${trjpass}" | openssl dgst -sha224 | sed 's/.* //') 352 | sed -i "/$passhash/d" /etc/haproxy/auth.lua 353 | systemctl reload haproxy.service 354 | fi 355 | } 356 | 357 | sync_github_message() { 358 | echo -e "${red}ATTENTION!${clear}" 359 | echo "The settings in client configs of all users will be synchronized with the latest version on GitHub (for Russia)" 360 | echo "" 361 | echo -e "${textcolor}[?]${clear} Press ${textcolor}Enter${clear} to synchronize the settings or enter ${textcolor}x${clear} to exit:" 362 | read sync 363 | } 364 | 365 | exit_sync() { 366 | if [[ "$sync" == "x" ]] || [[ "$sync" == "х" ]] 367 | then 368 | echo "" 369 | sync="" 370 | main_menu 371 | fi 372 | } 373 | 374 | check_users() { 375 | if [ $(ls -A1 /var/www/${subspath} | grep "CLIENT.json" | wc -l) -eq 0 ] 376 | then 377 | echo -e "${red}Error: no users found${clear}" 378 | echo "" 379 | main_menu 380 | fi 381 | } 382 | 383 | get_pass() { 384 | stack=$(jq -r '.inbounds[] | select(.tag=="tun-in") | .stack' ${file}) 385 | 386 | if grep -q ": \"trojan\"" "$file" 387 | then 388 | protocol="trojan" 389 | cred=$(jq -r '.outbounds[] | select(.tag=="proxy") | .password' ${file}) 390 | else 391 | protocol="vless" 392 | cred=$(jq -r '.outbounds[] | select(.tag=="proxy") | .uuid' ${file}) 393 | fi 394 | 395 | if [[ $(jq '.outbounds[] | select(.tag=="proxy") | .transport | has("headers")' ${file}) == "true" ]] 396 | then 397 | cfip=$(jq -r '.outbounds[] | select(.tag=="proxy") | .server' ${file}) 398 | fi 399 | } 400 | 401 | edit_configs_sync() { 402 | for file in /var/www/${subspath}/*-CLIENT.json 403 | do 404 | get_pass 405 | rm ${file} 406 | cp /var/www/${subspath}/${sync_template_file} ${file} 407 | inboundnum=$(jq '[.inbounds[].tag] | index("tun-in")' ${file}) 408 | outboundnum=$(jq '[.outbounds[].tag] | index("proxy")' ${file}) 409 | 410 | if [[ "$protocol" == "trojan" ]] && [ -f /etc/haproxy/auth.lua ] 411 | then 412 | echo "$(jq ".inbounds[${inboundnum}].stack = \"${stack}\" | .outbounds[${outboundnum}].password = \"${cred}\"" ${file})" > ${file} 413 | elif [[ "$protocol" == "trojan" ]] && [ ! -f /etc/haproxy/auth.lua ] 414 | then 415 | echo "$(jq ".inbounds[${inboundnum}].stack = \"${stack}\" | .outbounds[${outboundnum}].password = \"${cred}\" | .outbounds[${outboundnum}].transport.path = \"/${trojanpath}\"" ${file})" > ${file} 416 | else 417 | echo "$(jq ".inbounds[${inboundnum}].stack = \"${stack}\" | .outbounds[${outboundnum}].password = \"${cred}\" | .outbounds[${outboundnum}].transport.path = \"/${vlesspath}\" | .outbounds[${outboundnum}].type = \"vless\" | .outbounds[${outboundnum}] |= with_entries(.key |= if . == \"password\" then \"uuid\" else . end)" ${file})" > ${file} 418 | fi 419 | 420 | if [[ "$sync_template_file" == "template.json" ]] 421 | then 422 | sed -i -e "s/$tempdomain/$domain/g" -e "s/$tempip/$serverip/g" -e "s/$temprulesetpath/$rulesetpath/g" ${file} 423 | else 424 | sed -i -e "s/$loctempdomain/$domain/g" -e "s/$loctempip/$serverip/g" -e "s/$loctemprulesetpath/$rulesetpath/g" ${file} 425 | fi 426 | 427 | if [[ ! -z $cfip ]] 428 | then 429 | echo "$(jq ".outbounds[${outboundnum}].server = \"${cfip}\" | .outbounds[${outboundnum}].transport.headers |= {\"Host\":\"${domain}\"} | .route.rule_set[].download_detour = \"proxy\"" ${file})" > ${file} 430 | fi 431 | 432 | cfip="" 433 | cred="" 434 | inboundnum="" 435 | outboundnum="" 436 | done 437 | } 438 | 439 | sync_client_configs_github() { 440 | sync_template_file="template.json" 441 | edit_configs_sync 442 | 443 | for i in $(seq 0 $(expr $(jq ".route.rule_set | length" /var/www/${subspath}/template.json) - 1)) 444 | do 445 | ruleset_link=$(jq -r ".route.rule_set[${i}].url" /var/www/${subspath}/template.json) 446 | ruleset=${ruleset_link#"https://${tempdomain}/${temprulesetpath}/"} 447 | if [ ! -f /var/www/${rulesetpath}/${ruleset} ] 448 | then 449 | wget -q -P /var/www/${rulesetpath} https://github.com/SagerNet/sing-geosite/raw/rule-set/${ruleset} 450 | fi 451 | done 452 | 453 | chmod -R 755 /var/www/${rulesetpath} 454 | echo "Synchronization of the settings with GitHub is completed" 455 | echo "" 456 | } 457 | 458 | sync_local_message() { 459 | echo -e "${red}ATTENTION!${clear}" 460 | echo -e "You can manually edit the settings in ${textcolor}/var/www/${subspath}/template-loc.json${clear} template" 461 | echo "The settings in this file will be applied to client configs of all users" 462 | echo "Do not change \"tag\" values in \"inbounds\" and \"outbounds\" while editing" 463 | echo "" 464 | echo -e "${textcolor}[?]${clear} Press ${textcolor}Enter${clear} to synchronize the settings or enter ${textcolor}x${clear} to exit:" 465 | read sync 466 | } 467 | 468 | sync_client_configs_local() { 469 | loctempip=$(jq -r '.dns.servers[] | select(has("client_subnet")) | .client_subnet' /var/www/${subspath}/template-loc.json) 470 | loctempdomain=$(jq -r '.outbounds[] | select(.tag=="proxy") | .server' /var/www/${subspath}/template-loc.json) 471 | 472 | if [ -z ${loctempip} ] 473 | then 474 | loctempip=$(jq -r '.route.rules[] | select(has("ip_cidr")) | .ip_cidr[0]' /var/www/${subspath}/template-loc.json) 475 | fi 476 | 477 | loctemprulesetpath=$(jq -r ".route.rule_set[-1].url" /var/www/${subspath}/template-loc.json) 478 | loctemprulesetpath=${loctemprulesetpath#*"https://${loctempdomain}/"} 479 | loctemprulesetpath=${loctemprulesetpath%"/"*} 480 | 481 | sync_template_file="template-loc.json" 482 | edit_configs_sync 483 | 484 | if [[ $(jq ".route.rule_set | length" /var/www/${subspath}/template-loc.json) =~ ^[0-9]+$ ]] && [[ $(jq ".route.rule_set | length" /var/www/${subspath}/template-loc.json) != "0" ]] 485 | then 486 | for i in $(seq 0 $(expr $(jq ".route.rule_set | length" /var/www/${subspath}/template-loc.json) - 1)) 487 | do 488 | ruleset_link=$(jq -r ".route.rule_set[${i}].url" /var/www/${subspath}/template-loc.json) 489 | ruleset=${ruleset_link#"https://${loctempdomain}/${loctemprulesetpath}/"} 490 | if [ ! -f /var/www/${rulesetpath}/${ruleset} ] 491 | then 492 | wget -q -P /var/www/${rulesetpath} https://github.com/SagerNet/sing-geosite/raw/rule-set/${ruleset} 493 | fi 494 | done 495 | fi 496 | 497 | chmod -R 755 /var/www/${rulesetpath} 498 | echo "Synchronization of the settings with local template is completed" 499 | echo "" 500 | } 501 | 502 | show_users() { 503 | usernum=$(ls -A1 /var/www/${subspath} | grep "CLIENT.json" | wc -l) 504 | if [ ! -f /etc/haproxy/auth.lua ] 505 | then 506 | usernum=$(expr ${usernum} / 2) 507 | fi 508 | echo -e "${textcolor}Number of users:${clear} ${usernum}" 509 | ls -A1 /var/www/${subspath} | grep "CLIENT.json" | sed "s/-TRJ-CLIENT\.json//g" | sed "s/-VLESS-CLIENT\.json//g" | uniq 510 | echo "" 511 | main_menu 512 | } 513 | 514 | add_users() { 515 | validate_template 516 | while [[ $username != "x" ]] && [[ $username != "х" ]] 517 | do 518 | enter_user_data_add 519 | generate_pass 520 | add_to_auth_lua 521 | add_to_server_conf 522 | add_to_client_conf 523 | done 524 | main_menu 525 | } 526 | 527 | delete_users() { 528 | while [[ $username != "x" ]] && [[ $username != "х" ]] 529 | do 530 | enter_user_data_del 531 | del_from_auth_lua 532 | del_from_server_conf 533 | del_client_conf 534 | done 535 | main_menu 536 | } 537 | 538 | stack_text() { 539 | echo -e "${textcolor}[?]${clear} Select \"stack\" value for the user ${textcolor}${username}${clear}:" 540 | echo "0 - Exit" 541 | echo "1 - \"system\" (system stack, the best performance, default value) ${stack_sel_1}" 542 | echo "2 - \"gvisor\" (runs in userspace, is recommended if \"system\" isn't working) ${stack_sel_2}" 543 | echo "3 - \"mixed\" (mixed variant: \"system\" for TCP, \"gvisor\" for UDP) ${stack_sel_3}" 544 | read stackoption 545 | echo "" 546 | } 547 | 548 | change_stack() { 549 | while [[ $username != "x" ]] && [[ $username != "х" ]] 550 | do 551 | enter_user_data_del 552 | 553 | if [[ $(jq -r '.inbounds[] | select(.tag=="tun-in") | .stack' /var/www/${subspath}/${username}-TRJ-CLIENT.json) == "system" ]] 554 | then 555 | stack_sel_1="[Selected]" 556 | stack_sel_2="" 557 | stack_sel_3="" 558 | elif [[ $(jq -r '.inbounds[] | select(.tag=="tun-in") | .stack' /var/www/${subspath}/${username}-TRJ-CLIENT.json) == "gvisor" ]] 559 | then 560 | stack_sel_1="" 561 | stack_sel_2="[Selected]" 562 | stack_sel_3="" 563 | elif [[ $(jq -r '.inbounds[] | select(.tag=="tun-in") | .stack' /var/www/${subspath}/${username}-TRJ-CLIENT.json) == "mixed" ]] 564 | then 565 | stack_sel_1="" 566 | stack_sel_2="" 567 | stack_sel_3="[Selected]" 568 | fi 569 | 570 | stack_text 571 | 572 | case $stackoption in 573 | 1) 574 | stack_value="system" 575 | ;; 576 | 2) 577 | stack_value="gvisor" 578 | ;; 579 | 3) 580 | stack_value="mixed" 581 | ;; 582 | *) 583 | main_menu 584 | esac 585 | 586 | inboundnum=$(jq '[.inbounds[].tag] | index("tun-in")' /var/www/${subspath}/${username}-TRJ-CLIENT.json) 587 | echo "$(jq ".inbounds[${inboundnum}].stack = \"${stack_value}\"" /var/www/${subspath}/${username}-TRJ-CLIENT.json)" > /var/www/${subspath}/${username}-TRJ-CLIENT.json 588 | 589 | if [ ! -f /etc/haproxy/auth.lua ] 590 | then 591 | inboundnum=$(jq '[.inbounds[].tag] | index("tun-in")' /var/www/${subspath}/${username}-VLESS-CLIENT.json) 592 | echo "$(jq ".inbounds[${inboundnum}].stack = \"${stack_value}\"" /var/www/${subspath}/${username}-VLESS-CLIENT.json)" > /var/www/${subspath}/${username}-VLESS-CLIENT.json 593 | fi 594 | 595 | inboundnum="" 596 | echo -e "The \"stack\" value for the user ${textcolor}${username}${clear} has been changed, update the config on the client app to apply new settings" 597 | echo "" 598 | done 599 | } 600 | 601 | sync_with_github() { 602 | sync_github_message 603 | exit_sync 604 | check_users 605 | validate_template 606 | sync_client_configs_github 607 | main_menu 608 | } 609 | 610 | sync_with_local_temp() { 611 | sync_local_message 612 | exit_sync 613 | check_users 614 | validate_local_template 615 | sync_client_configs_local 616 | main_menu 617 | } 618 | 619 | sync_client_configs() { 620 | echo -e "${textcolor}Select synchronisation option:${clear}" 621 | echo "0 - Exit" 622 | echo "1 - Sync with GitHub" 623 | echo "2 - Sync with local template (custom settings)" 624 | read syncoption 625 | echo "" 626 | 627 | case $syncoption in 628 | 1) 629 | sync_with_github 630 | ;; 631 | 2) 632 | sync_with_local_temp 633 | ;; 634 | *) 635 | main_menu 636 | esac 637 | } 638 | 639 | check_cfip() { 640 | while [[ ! $cfip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] 641 | do 642 | echo -e "${red}Error: the entered value is not an IP${clear}" 643 | echo "" 644 | echo -e "${textcolor}[?]${clear} Enter the custom Cloudflare IP:" 645 | read cfip 646 | echo "" 647 | done 648 | } 649 | 650 | set_cf_ip() { 651 | echo -e "${textcolor}[?]${clear} Enter the custom Cloudflare IP:" 652 | read cfip 653 | echo "" 654 | check_cfip 655 | 656 | outboundnum=$(jq '[.outbounds[].tag] | index("proxy")' /var/www/${subspath}/${username}-TRJ-CLIENT.json) 657 | echo "$(jq ".outbounds[${outboundnum}].server = \"${cfip}\" | .outbounds[${outboundnum}].transport.headers |= {\"Host\":\"${domain}\"} | .route.rule_set[].download_detour = \"proxy\"" /var/www/${subspath}/${username}-TRJ-CLIENT.json)" > /var/www/${subspath}/${username}-TRJ-CLIENT.json 658 | 659 | outboundnum=$(jq '[.outbounds[].tag] | index("proxy")' /var/www/${subspath}/${username}-VLESS-CLIENT.json) 660 | echo "$(jq ".outbounds[${outboundnum}].server = \"${cfip}\" | .outbounds[${outboundnum}].transport.headers |= {\"Host\":\"${domain}\"} | .route.rule_set[].download_detour = \"proxy\"" /var/www/${subspath}/${username}-VLESS-CLIENT.json)" > /var/www/${subspath}/${username}-VLESS-CLIENT.json 661 | 662 | echo -e "Changed the settings for the user ${textcolor}${username}${clear}, IP ${textcolor}${cfip}${clear} has been set" 663 | outboundnum="" 664 | cfip="" 665 | echo "" 666 | } 667 | 668 | remove_cf_ip() { 669 | outboundnum=$(jq '[.outbounds[].tag] | index("proxy")' /var/www/${subspath}/${username}-TRJ-CLIENT.json) 670 | echo "$(jq ".outbounds[${outboundnum}].server = \"${domain}\" | del(.outbounds[${outboundnum}].transport.headers) | del(.route.rule_set[].download_detour)" /var/www/${subspath}/${username}-TRJ-CLIENT.json)" > /var/www/${subspath}/${username}-TRJ-CLIENT.json 671 | 672 | outboundnum=$(jq '[.outbounds[].tag] | index("proxy")' /var/www/${subspath}/${username}-VLESS-CLIENT.json) 673 | echo "$(jq ".outbounds[${outboundnum}].server = \"${domain}\" | del(.outbounds[${outboundnum}].transport.headers) | del(.route.rule_set[].download_detour)" /var/www/${subspath}/${username}-VLESS-CLIENT.json)" > /var/www/${subspath}/${username}-VLESS-CLIENT.json 674 | 675 | outboundnum="" 676 | echo -e "Changed the settings for the user ${textcolor}${username}${clear}" 677 | echo "" 678 | } 679 | 680 | cf_text() { 681 | echo -e "${textcolor}[?]${clear} Select an option for the user ${textcolor}${username}${clear}:" 682 | echo "0 - Exit" 683 | echo "1 - Setup/change custom Cloudflare IP ${cf_ip_status}" 684 | echo "2 - Remove custom Cloudflare IP" 685 | read cfoption 686 | echo "" 687 | } 688 | 689 | cf_ip_settings() { 690 | if [ -f /etc/haproxy/auth.lua ] 691 | then 692 | echo -e "${red}Error: this option is only available for the setup variants with WebSocket or HTTPUpgrade transport${clear}" 693 | echo "" 694 | main_menu 695 | fi 696 | 697 | echo -e "${red}ATTENTION!${clear}" 698 | echo "This option is recommended in case of unavailability of the IP that Cloudflare allocated to your domain for proxying" 699 | echo "You need to scan Cloudflare IP ranges from your device and choose the optimal IP by yourself" 700 | echo "Instruction: https://github.com/A-Zuro/Secret-Sing-Box/blob/main/Docs/cf-scan-ip-en.md" 701 | echo "" 702 | 703 | while [[ $username != "x" ]] && [[ $username != "х" ]] 704 | do 705 | enter_user_data_del 706 | sel_cfip=$(jq -r '.outbounds[] | select(.tag=="proxy") | .server' /var/www/${subspath}/${username}-TRJ-CLIENT.json) 707 | 708 | if [[ $sel_cfip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] 709 | then 710 | cf_ip_status="[Selected: ${sel_cfip}]" 711 | else 712 | cf_ip_status="[Cloudflare IP is not selected]" 713 | fi 714 | 715 | cf_text 716 | 717 | while [[ $(jq '.outbounds[] | select(.tag=="proxy") | .transport | has("headers")' /var/www/${subspath}/${username}-TRJ-CLIENT.json) == "false" ]] && [[ $cfoption == "2" ]] 718 | do 719 | echo -e "${red}Error: the config file of this user does not contain Cloudflare IP anyway${clear}" 720 | echo "" 721 | cf_text 722 | done 723 | 724 | case $cfoption in 725 | 1) 726 | set_cf_ip 727 | ;; 728 | 2) 729 | remove_cf_ip 730 | ;; 731 | *) 732 | main_menu 733 | esac 734 | done 735 | } 736 | 737 | show_warp_domains() { 738 | echo -e "${textcolor}List of domains/suffixes routed through WARP:${clear}" 739 | jq -r '.route.rules[] | select(.outbound=="warp") | .domain_suffix[]' /etc/sing-box/config.json 740 | echo "" 741 | main_menu 742 | } 743 | 744 | exit_add_warp() { 745 | if [[ $newwarp == "x" ]] || [[ $newwarp == "х" ]] 746 | then 747 | newwarp="" 748 | main_menu 749 | fi 750 | } 751 | 752 | exit_del_warp() { 753 | if [[ $delwarp == "x" ]] || [[ $delwarp == "х" ]] 754 | then 755 | delwarp="" 756 | main_menu 757 | fi 758 | } 759 | 760 | crop_newwarp() { 761 | if [[ "$newwarp" == "https://"* ]] 762 | then 763 | newwarp=${newwarp#"https://"} 764 | fi 765 | 766 | if [[ "$newwarp" == "http://"* ]] 767 | then 768 | newwarp=${newwarp#"http://"} 769 | fi 770 | 771 | if [[ "$newwarp" =~ "/" ]] 772 | then 773 | newwarp=$(echo "${newwarp}" | cut -d "/" -f 1) 774 | fi 775 | } 776 | 777 | check_warp_domain_add() { 778 | while [[ -n $(jq '.route.rules[] | select(.outbound=="warp") | .domain_suffix[]' /etc/sing-box/config.json | grep "\"${newwarp}\"") ]] || [ -z "$newwarp" ] 779 | do 780 | if [ -z "$newwarp" ] 781 | then 782 | : 783 | else 784 | echo -e "${red}Error: this domain/suffix is already added to WARP${clear}" 785 | echo "" 786 | fi 787 | echo -e "${textcolor}[?]${clear} Enter a new domain/suffix for WARP routing or enter ${textcolor}x${clear} to exit:" 788 | read newwarp 789 | echo "" 790 | exit_add_warp 791 | crop_newwarp 792 | done 793 | } 794 | 795 | check_warp_domain_del() { 796 | while [[ -z $(jq '.route.rules[] | select(.outbound=="warp") | .domain_suffix[]' /etc/sing-box/config.json | grep "\"${delwarp}\"") ]] || [ -z "$delwarp" ] 797 | do 798 | echo -e "${red}Error: this domain/suffix is not added to WARP routing${clear}" 799 | echo "" 800 | echo -e "${textcolor}[?]${clear} Enter a domain/suffix to delete from WARP routing or enter ${textcolor}x${clear} to exit:" 801 | read delwarp 802 | echo "" 803 | exit_del_warp 804 | done 805 | } 806 | 807 | add_warp_domains() { 808 | warpnum=$(jq '[.route.rules[].outbound] | index("warp")' /etc/sing-box/config.json) 809 | while [[ $newwarp != "x" ]] && [[ $newwarp != "х" ]] 810 | do 811 | echo -e "${textcolor}[?]${clear} Enter a new domain/suffix for WARP routing or enter ${textcolor}x${clear} to exit:" 812 | read newwarp 813 | echo "" 814 | crop_newwarp 815 | check_warp_domain_add 816 | exit_add_warp 817 | echo "$(jq ".route.rules[${warpnum}].domain_suffix[.route.rules[${warpnum}].domain_suffix | length]? += \"${newwarp}\"" /etc/sing-box/config.json)" > /etc/sing-box/config.json 818 | systemctl reload sing-box.service 819 | echo -e "Domain/suffix ${textcolor}${newwarp}${clear} is added to WARP routing" 820 | echo "" 821 | done 822 | } 823 | 824 | delete_warp_domains() { 825 | warpnum=$(jq '[.route.rules[].outbound] | index("warp")' /etc/sing-box/config.json) 826 | while [[ $delwarp != "x" ]] && [[ $delwarp != "х" ]] 827 | do 828 | echo -e "${textcolor}[?]${clear} Enter a domain/suffix to delete from WARP routing or enter ${textcolor}x${clear} to exit:" 829 | read delwarp 830 | echo "" 831 | exit_del_warp 832 | check_warp_domain_del 833 | echo "$(jq "del(.route.rules[${warpnum}].domain_suffix[] | select(. == \"${delwarp}\"))" /etc/sing-box/config.json)" > /etc/sing-box/config.json 834 | systemctl reload sing-box.service 835 | echo -e "Domain/suffix ${textcolor}${delwarp}${clear} is deleted from WARP routing" 836 | echo "" 837 | done 838 | } 839 | 840 | exit_enter_nextlink() { 841 | if [[ $nextlink == "x" ]] || [[ $nextlink == "х" ]] 842 | then 843 | nextlink="" 844 | main_menu 845 | fi 846 | } 847 | 848 | check_nextlink() { 849 | nextconfig=$(curl -s ${nextlink}) 850 | 851 | while [ $(jq -e . >/dev/null 2>&1 <<< "${nextconfig}"; echo $?) -ne 0 ] || [[ $(echo "${nextconfig}" | jq 'any(.outbounds[]; .tag == "proxy")') == "false" ]] || [ -z "${nextconfig}" ] 852 | do 853 | nextlink="" 854 | echo -e "${red}Error: invalid link to client config or the next server does not respond${clear}" 855 | echo "" 856 | while [[ -z $nextlink ]] 857 | do 858 | echo -e "${textcolor}[?]${clear} Enter the link to client config from the next server in the chain or enter ${textcolor}x${clear} to exit:" 859 | read nextlink 860 | echo "" 861 | exit_enter_nextlink 862 | done 863 | nextconfig=$(curl -s ${nextlink}) 864 | done 865 | } 866 | 867 | chain_end() { 868 | config_temp=$(curl -s https://raw.githubusercontent.com/A-Zuro/Secret-Sing-Box/master/Config-Templates/config.json) 869 | 870 | if [ $(jq -e . >/dev/null 2>&1 <<< "${config_temp}"; echo $?) -eq 0 ] && [ -n "${config_temp}" ] 871 | then 872 | warp_rule=$(echo "${config_temp}" | jq '.route.rules[] | select(.outbound=="warp")') 873 | warpnum=$(jq '[.route.rules[].outbound] | index("warp")' /etc/sing-box/config.json) 874 | echo "$(jq ".route.rules[${warpnum}] |= . + ${warp_rule}" /etc/sing-box/config.json)" > /etc/sing-box/config.json 875 | else 876 | echo -e "${red}Error: failed to download data from GitHub${clear}" 877 | echo "" 878 | main_menu 879 | fi 880 | 881 | echo "$(jq 'del(.route.rules[] | select(.outbound=="proxy")) | del(.outbounds[] | select(.tag=="proxy"))' /etc/sing-box/config.json)" > /etc/sing-box/config.json 882 | 883 | if [[ $(jq 'any(.outbounds[]; .tag == "IPv4")' /etc/sing-box/config.json) == "false" ]] 884 | then 885 | echo "$(jq '.outbounds[.outbounds | length] |= . + {"type":"direct","tag":"IPv4","domain_strategy":"ipv4_only"}' /etc/sing-box/config.json)" > /etc/sing-box/config.json 886 | fi 887 | 888 | if [[ $(jq 'any(.route.rules[]; .outbound == "IPv4")' /etc/sing-box/config.json) == "false" ]] 889 | then 890 | echo "$(jq '.route.rules[.route.rules | length] |= . + {"rule_set":["google"],"outbound":"IPv4"}' /etc/sing-box/config.json)" > /etc/sing-box/config.json 891 | fi 892 | 893 | rule_sets=(google telegram openai) 894 | 895 | for ruleset_tag in "${rule_sets[@]}" 896 | do 897 | if [[ $(jq "any(.route.rule_set[]; .tag == \"${ruleset_tag}\")" /etc/sing-box/config.json) == "false" ]] 898 | then 899 | echo "$(jq ".route.rule_set[.route.rule_set | length] |= . + {\"tag\":\"${ruleset_tag}\",\"type\":\"local\",\"format\":\"binary\",\"path\":\"/var/www/${rulesetpath}/geosite-${ruleset_tag}.srs\"}" /etc/sing-box/config.json)" > /etc/sing-box/config.json 900 | fi 901 | done 902 | 903 | sed -i -e "s/$temprulesetpath/$rulesetpath/g" /etc/sing-box/config.json 904 | 905 | for i in $(seq 0 $(expr $(jq ".route.rules[${warpnum}].rule_set | length" /etc/sing-box/config.json) - 1)) 906 | do 907 | ruleset_tag=$(jq -r ".route.rules[${warpnum}].rule_set[${i}]" /etc/sing-box/config.json) 908 | 909 | if [[ $(jq "any(.route.rule_set[]; .tag == \"${ruleset_tag}\")" /etc/sing-box/config.json) == "false" ]] 910 | then 911 | echo "$(jq ".route.rule_set[.route.rule_set | length] |= . + {\"tag\":\"${ruleset_tag}\",\"type\":\"local\",\"format\":\"binary\",\"path\":\"/var/www/${rulesetpath}/geosite-${ruleset_tag}.srs\"}" /etc/sing-box/config.json)" > /etc/sing-box/config.json 912 | fi 913 | 914 | if [ ! -f /var/www/${rulesetpath}/geosite-${ruleset_tag}.srs ] 915 | then 916 | wget -q -P /var/www/${rulesetpath} https://github.com/SagerNet/sing-geosite/raw/rule-set/geosite-${ruleset_tag}.srs 917 | chmod -R 755 /var/www/${rulesetpath} 918 | fi 919 | done 920 | 921 | systemctl reload sing-box.service 922 | 923 | echo "Settings changed successfully" 924 | echo "" 925 | main_menu 926 | } 927 | 928 | chain_middle() { 929 | nextlink="" 930 | while [[ -z $nextlink ]] 931 | do 932 | echo -e "${textcolor}[?]${clear} Enter the link to client config from the next server in the chain or enter ${textcolor}x${clear} to exit:" 933 | read nextlink 934 | echo "" 935 | done 936 | exit_enter_nextlink 937 | check_nextlink 938 | 939 | nextoutbound=$(echo "${nextconfig}" | jq '.outbounds[] | select(.tag=="proxy")') 940 | warpnum=$(jq '[.route.rules[].outbound] | index("warp")' /etc/sing-box/config.json) 941 | 942 | if [[ $(jq 'any(.outbounds[]; .tag == "proxy")' /etc/sing-box/config.json) == "false" ]] 943 | then 944 | proxy_num=$(jq '.outbounds | length' /etc/sing-box/config.json) 945 | proxy_rule_num=$(jq '.route.rules | length' /etc/sing-box/config.json) 946 | else 947 | proxy_num=$(jq '[.outbounds[].tag] | index("proxy")' /etc/sing-box/config.json) 948 | proxy_rule_num=$(jq '[.route.rules[].outbound] | index("proxy")' /etc/sing-box/config.json) 949 | fi 950 | 951 | if [ -f /etc/haproxy/auth.lua ] 952 | then 953 | echo "$(jq ".route.rules[${proxy_rule_num}] |= . + {\"inbound\":[\"trojan-in\"],\"outbound\":\"proxy\"} | .outbounds[${proxy_num}] |= . + ${nextoutbound}" /etc/sing-box/config.json)" > /etc/sing-box/config.json 954 | else 955 | echo "$(jq ".route.rules[${proxy_rule_num}] |= . + {\"inbound\":[\"trojan-in\",\"vless-in\"],\"outbound\":\"proxy\"} | .outbounds[${proxy_num}] |= . + ${nextoutbound}" /etc/sing-box/config.json)" > /etc/sing-box/config.json 956 | fi 957 | 958 | echo "$(jq ".route.rules[${warpnum}] |= . + {\"rule_set\":[\"geoip-ru\",\"gov-ru\"],\"domain_suffix\":[\".ru\",\".su\",\".ru.com\",\".ru.net\"],\"domain_keyword\":[\"xn--\"],\"outbound\":\"warp\"}" /etc/sing-box/config.json)" > /etc/sing-box/config.json 959 | 960 | if [[ $(jq 'any(.outbounds[]; .tag == "IPv4")' /etc/sing-box/config.json) == "true" ]] 961 | then 962 | echo "$(jq /etc/sing-box/config.json 963 | fi 964 | 965 | if [[ $(jq 'any(.route.rules[]; .outbound == "IPv4")' /etc/sing-box/config.json) == "true" ]] 966 | then 967 | echo "$(jq /etc/sing-box/config.json 968 | fi 969 | 970 | rule_sets=(google telegram openai) 971 | 972 | for ruleset_tag in "${rule_sets[@]}" 973 | do 974 | if [[ $(jq "any(.route.rule_set[]; .tag == \"${ruleset_tag}\")" /etc/sing-box/config.json) == "true" ]] 975 | then 976 | echo "$(jq /etc/sing-box/config.json 977 | fi 978 | done 979 | 980 | systemctl reload sing-box.service 981 | 982 | echo "Settings changed successfully" 983 | echo "" 984 | main_menu 985 | } 986 | 987 | chain_text() { 988 | echo -e "${textcolor}[?]${clear} Select the position of the server in the chain:" 989 | echo "0 - Exit" 990 | echo "1 - Configure this server as the end of the chain or the only one ${chain_sel_1}" 991 | echo "2 - Configure this server as intermediate in the chain or change the next server ${chain_sel_2}" 992 | read chain_option 993 | echo "" 994 | } 995 | 996 | chain_setup() { 997 | if [[ $(jq 'any(.outbounds[]; .tag == "proxy")' /etc/sing-box/config.json) == "false" ]] 998 | then 999 | chain_sel_1="[Selected]" 1000 | chain_sel_2="" 1001 | else 1002 | chain_sel_1="" 1003 | chain_sel_2="[Selected: $(jq -r '.outbounds[] | select(.tag=="proxy") | .server' /etc/sing-box/config.json)]" 1004 | fi 1005 | 1006 | chain_text 1007 | 1008 | while [[ $(jq 'any(.outbounds[]; .tag == "proxy")' /etc/sing-box/config.json) == "false" ]] && [[ $chain_option == "1" ]] 1009 | do 1010 | echo -e "${red}Error: this server is already configured as the end of the chain or the only one${clear}" 1011 | echo "" 1012 | chain_text 1013 | done 1014 | 1015 | case $chain_option in 1016 | 1) 1017 | chain_end 1018 | ;; 1019 | 2) 1020 | chain_middle 1021 | ;; 1022 | *) 1023 | main_menu 1024 | esac 1025 | } 1026 | 1027 | exit_renew_cert() { 1028 | if [[ $certrenew == "x" ]] || [[ $certrenew == "х" ]] 1029 | then 1030 | echo "" 1031 | certrenew="" 1032 | main_menu 1033 | fi 1034 | } 1035 | 1036 | cert_final_text() { 1037 | if [ $? -eq 0 ] 1038 | then 1039 | echo "" 1040 | echo "Certificate has been issued successfully" 1041 | else 1042 | echo "" 1043 | echo -e "${red}Error: certificate has not been issued${clear}" 1044 | fi 1045 | } 1046 | 1047 | reissue_cert() { 1048 | email="" 1049 | while [[ -z $email ]] 1050 | do 1051 | if [ -f /etc/letsencrypt/cloudflare.credentials ] 1052 | then 1053 | echo -e "${textcolor}[?]${clear} Enter your email registered on Cloudflare:" 1054 | else 1055 | echo -e "${textcolor}[?]${clear} Enter your email to issue a certificate:" 1056 | fi 1057 | read email 1058 | echo "" 1059 | done 1060 | 1061 | rm -rf /etc/letsencrypt/live/${domain} &> /dev/null 1062 | rm -rf /etc/letsencrypt/archive/${domain} &> /dev/null 1063 | rm /etc/letsencrypt/renewal/${domain}.conf &> /dev/null 1064 | 1065 | echo -e "${textcolor}Requesting a certificate...${clear}" 1066 | if [ -f /etc/letsencrypt/cloudflare.credentials ] 1067 | then 1068 | certbot certonly --dns-cloudflare --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.credentials --dns-cloudflare-propagation-seconds 35 -d ${domain},*.${domain} --agree-tos -m ${email} --no-eff-email --non-interactive 1069 | cert_final_text 1070 | ufw_close_80="" 1071 | else 1072 | ufw allow 80 &> /dev/null 1073 | certbot certonly --standalone --preferred-challenges http --agree-tos --email ${email} -d ${domain} --no-eff-email --non-interactive 1074 | cert_final_text 1075 | ufw delete allow 80 &> /dev/null 1076 | ufw_close_80=" && ufw delete allow 80" 1077 | fi 1078 | 1079 | if [ ! -f /etc/haproxy/auth.lua ] && [ -f /etc/letsencrypt/renewal/${domain}.conf ] 1080 | then 1081 | echo "renew_hook = systemctl reload nginx${ufw_close_80}" >> /etc/letsencrypt/renewal/${domain}.conf 1082 | systemctl start nginx.service 1083 | elif [ -f /etc/haproxy/auth.lua ] && [ -f /etc/letsencrypt/renewal/${domain}.conf ] 1084 | then 1085 | echo "renew_hook = cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /etc/haproxy/certs/${domain}.pem && systemctl reload haproxy${ufw_close_80}" >> /etc/letsencrypt/renewal/${domain}.conf 1086 | cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /etc/haproxy/certs/${domain}.pem 1087 | systemctl start haproxy.service 1088 | fi 1089 | 1090 | echo "" 1091 | main_menu 1092 | } 1093 | 1094 | renew_cert() { 1095 | echo -e "${red}ATTENTION!${clear}" 1096 | echo "The script has a built-in automatic certificate renewal every 2 months, and manual renewal is recommended only in case of failures" 1097 | echo "Renewing a certificate more than 5 times a week can result in reaching the Let's Encrypt limit, requiring you to wait before the next renewal" 1098 | echo "" 1099 | echo -e "${textcolor}[?]${clear} Press ${textcolor}Enter${clear} to renew certificate or enter ${textcolor}x${clear} to exit:" 1100 | read certrenew 1101 | exit_renew_cert 1102 | 1103 | if [ ! -f /etc/letsencrypt/live/${domain}/fullchain.pem ] 1104 | then 1105 | reissue_cert 1106 | fi 1107 | 1108 | echo -e "${textcolor}Renewing a certificate...${clear}" 1109 | if [ -f /etc/letsencrypt/cloudflare.credentials ] 1110 | then 1111 | certbot renew --force-renewal 1112 | cert_final_text 1113 | else 1114 | ufw allow 80 &> /dev/null && certbot renew --force-renewal 1115 | cert_final_text 1116 | fi 1117 | 1118 | echo "" 1119 | main_menu 1120 | } 1121 | 1122 | exit_change_domain() { 1123 | if [[ $domain == "x" ]] || [[ $domain == "х" ]] 1124 | then 1125 | domain="${old_domain}" 1126 | main_menu 1127 | fi 1128 | } 1129 | 1130 | crop_domain() { 1131 | if [[ "$domain" == "https://"* ]] 1132 | then 1133 | domain=${domain#"https://"} 1134 | fi 1135 | 1136 | if [[ "$domain" == "http://"* ]] 1137 | then 1138 | domain=${domain#"http://"} 1139 | fi 1140 | 1141 | if [[ "$domain" == "www."* ]] 1142 | then 1143 | domain=${domain#"www."} 1144 | fi 1145 | 1146 | if [[ "$domain" =~ "/" ]] 1147 | then 1148 | domain=$(echo "${domain}" | cut -d "/" -f 1) 1149 | fi 1150 | } 1151 | 1152 | get_test_response() { 1153 | testdomain=$(echo "${domain}" | rev | cut -d '.' -f 1-2 | rev) 1154 | 1155 | if [[ "$cftoken" =~ [A-Z] ]] 1156 | then 1157 | test_response=$(curl --silent --request GET --url https://api.cloudflare.com/client/v4/zones --header "Authorization: Bearer ${cftoken}" --header "Content-Type: application/json") 1158 | else 1159 | test_response=$(curl --silent --request GET --url https://api.cloudflare.com/client/v4/zones --header "X-Auth-Key: ${cftoken}" --header "X-Auth-Email: ${email}" --header "Content-Type: application/json") 1160 | fi 1161 | } 1162 | 1163 | enter_domain_data() { 1164 | domain="" 1165 | email="" 1166 | cftoken="" 1167 | echo "" 1168 | while [[ -z $domain ]] 1169 | do 1170 | echo -e "${textcolor}[?]${clear} Enter new domain name or enter ${textcolor}x${clear} to exit:" 1171 | read domain 1172 | echo "" 1173 | done 1174 | exit_change_domain 1175 | crop_domain 1176 | while [[ -z $email ]] 1177 | do 1178 | echo -e "${textcolor}[?]${clear} Enter your email${email_text}:" 1179 | read email 1180 | echo "" 1181 | done 1182 | if [[ "${validation_type}" == "1" ]] 1183 | then 1184 | while [[ -z $cftoken ]] 1185 | do 1186 | echo -e "${textcolor}[?]${clear} Enter your Cloudflare API token (Edit zone DNS) or Cloudflare global API key:" 1187 | read cftoken 1188 | echo "" 1189 | done 1190 | fi 1191 | } 1192 | 1193 | check_cf_token() { 1194 | echo "Checking domain name, API token/key and email..." 1195 | get_test_response 1196 | 1197 | while [[ -z $(echo $test_response | grep "\"${testdomain}\"") ]] || [[ -z $(echo $test_response | grep "\"#dns_records:edit\"") ]] || [[ -z $(echo $test_response | grep "\"#dns_records:read\"") ]] || [[ -z $(echo $test_response | grep "\"#zone:read\"") ]] 1198 | do 1199 | echo "" 1200 | echo -e "${red}Error: invalid domain name, API token/key or email${clear}" 1201 | enter_domain_data 1202 | echo "Checking domain name, API token/key and email..." 1203 | get_test_response 1204 | done 1205 | 1206 | echo "Success!" 1207 | echo "" 1208 | } 1209 | 1210 | issue_cert_dns_cf() { 1211 | if [[ "$cftoken" =~ [A-Z] ]] 1212 | then 1213 | echo "dns_cloudflare_api_token = ${cftoken}" > /etc/letsencrypt/cloudflare.credentials 1214 | else 1215 | echo "dns_cloudflare_email = ${email}" > /etc/letsencrypt/cloudflare.credentials 1216 | echo "dns_cloudflare_api_key = ${cftoken}" >> /etc/letsencrypt/cloudflare.credentials 1217 | fi 1218 | 1219 | chown root:root /etc/letsencrypt/cloudflare.credentials 1220 | chmod 600 /etc/letsencrypt/cloudflare.credentials 1221 | 1222 | echo -e "${textcolor}Requesting a certificate...${clear}" 1223 | certbot certonly --dns-cloudflare --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.credentials --dns-cloudflare-propagation-seconds 35 -d ${domain},*.${domain} --agree-tos -m ${email} --no-eff-email --non-interactive 1224 | 1225 | if [ $? -ne 0 ] 1226 | then 1227 | sleep 3 1228 | echo "" 1229 | rm -rf /etc/letsencrypt/live/${domain} &> /dev/null 1230 | rm -rf /etc/letsencrypt/archive/${domain} &> /dev/null 1231 | rm /etc/letsencrypt/renewal/${domain}.conf &> /dev/null 1232 | echo -e "${textcolor}Requesting a certificate: 2nd attempt...${clear}" 1233 | certbot certonly --dns-cloudflare --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.credentials --dns-cloudflare-propagation-seconds 35 -d ${domain},*.${domain} --agree-tos -m ${email} --no-eff-email --non-interactive 1234 | fi 1235 | 1236 | ufw_close_80="" 1237 | crontab -l | sed 's/ufw allow 80 && //g' | crontab - 1238 | } 1239 | 1240 | issue_cert_standalone() { 1241 | rm /etc/letsencrypt/cloudflare.credentials &> /dev/null 1242 | ufw allow 80 &> /dev/null 1243 | 1244 | echo -e "${textcolor}Requesting a certificate...${clear}" 1245 | certbot certonly --standalone --preferred-challenges http --agree-tos --email ${email} -d ${domain} --no-eff-email --non-interactive 1246 | 1247 | if [ $? -ne 0 ] 1248 | then 1249 | sleep 3 1250 | echo "" 1251 | rm -rf /etc/letsencrypt/live/${domain} &> /dev/null 1252 | rm -rf /etc/letsencrypt/archive/${domain} &> /dev/null 1253 | rm /etc/letsencrypt/renewal/${domain}.conf &> /dev/null 1254 | echo -e "${textcolor}Requesting a certificate: 2nd attempt...${clear}" 1255 | certbot certonly --standalone --preferred-challenges http --agree-tos --email ${email} -d ${domain} --no-eff-email --non-interactive 1256 | fi 1257 | 1258 | ufw delete allow 80 &> /dev/null 1259 | ufw_close_80=" && ufw delete allow 80" 1260 | 1261 | if [[ -z $(crontab -l | grep "ufw allow 80") ]] 1262 | then 1263 | crontab -l | sed 's/certbot -q renew --force-renewal/ufw allow 80 \&\& certbot -q renew --force-renewal/' | crontab - 1264 | fi 1265 | } 1266 | 1267 | change_domain() { 1268 | old_domain="${domain}" 1269 | echo -e "${red}ATTENTION!${clear}" 1270 | echo "Don't forget to create an A record for the new domain and change the domain in client config links" 1271 | echo "" 1272 | echo -e "Current domain: ${textcolor}${old_domain}${clear}" 1273 | if [ -f /etc/letsencrypt/cloudflare.credentials ] 1274 | then 1275 | echo -e "Certificate validation method: ${textcolor}DNS Cloudflare${clear}" 1276 | else 1277 | echo -e "Certificate validation method: ${textcolor}Standalone${clear}" 1278 | fi 1279 | echo "" 1280 | echo -e "${textcolor}[?]${clear} Select a certificate validation method for the new domain:" 1281 | echo "0 - Exit" 1282 | echo "1 - DNS Cloudflare (if your domain is linked to Cloudflare)" 1283 | echo "2 - Standalone (if your domain is linked to another service)" 1284 | read validation_type 1285 | 1286 | case $validation_type in 1287 | 1) 1288 | email_text=" registered on Cloudflare" 1289 | enter_domain_data 1290 | check_cf_token 1291 | ;; 1292 | 2) 1293 | email_text=" to issue a certificate" 1294 | echo "" 1295 | echo -e "${red}ATTENTION!${clear}" 1296 | echo "Be sure to check the spelling of the domain name" 1297 | enter_domain_data 1298 | ;; 1299 | *) 1300 | echo "" 1301 | domain="${old_domain}" 1302 | main_menu 1303 | esac 1304 | 1305 | rm -rf /etc/letsencrypt/live/${old_domain} &> /dev/null 1306 | rm -rf /etc/letsencrypt/archive/${old_domain} &> /dev/null 1307 | rm /etc/letsencrypt/renewal/${old_domain}.conf &> /dev/null 1308 | 1309 | if [[ "${validation_type}" == "1" ]] 1310 | then 1311 | issue_cert_dns_cf 1312 | else 1313 | issue_cert_standalone 1314 | fi 1315 | 1316 | if [ ! -f /etc/haproxy/auth.lua ] 1317 | then 1318 | echo "renew_hook = systemctl reload nginx${ufw_close_80}" >> /etc/letsencrypt/renewal/${domain}.conf 1319 | sed -i -e "s/$old_domain/$domain/g" /etc/nginx/nginx.conf 1320 | systemctl reload nginx.service 1321 | if [ $? -ne 0 ] 1322 | then 1323 | systemctl start nginx.service 1324 | fi 1325 | else 1326 | echo "renew_hook = cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /etc/haproxy/certs/${domain}.pem && systemctl reload haproxy${ufw_close_80}" >> /etc/letsencrypt/renewal/${domain}.conf 1327 | cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /etc/haproxy/certs/${domain}.pem 1328 | sed -i -e "s/$old_domain/$domain/g" /etc/haproxy/haproxy.cfg 1329 | systemctl reload haproxy.service 1330 | if [ $? -ne 0 ] 1331 | then 1332 | systemctl start haproxy.service 1333 | fi 1334 | fi 1335 | 1336 | for file in /var/www/${subspath}/*-CLIENT.json 1337 | do 1338 | sed -i -e "s/$old_domain/$domain/g" ${file} 1339 | done 1340 | 1341 | sed -i -e "s/$old_domain/$domain/g" /var/www/${subspath}/sub.html 1342 | 1343 | echo "" 1344 | echo -e "Domain ${textcolor}${old_domain}${clear} changed to ${textcolor}${domain}${clear}" 1345 | 1346 | echo "" 1347 | main_menu 1348 | } 1349 | 1350 | disable_ipv6() { 1351 | if ! grep -q "net.ipv6.conf.all.disable_ipv6 = 1" /etc/sysctl.conf 1352 | then 1353 | echo "net.ipv6.conf.all.disable_ipv6 = 1" >> /etc/sysctl.conf 1354 | fi 1355 | 1356 | if ! grep -q "net.ipv6.conf.default.disable_ipv6 = 1" /etc/sysctl.conf 1357 | then 1358 | echo "net.ipv6.conf.default.disable_ipv6 = 1" >> /etc/sysctl.conf 1359 | fi 1360 | 1361 | if ! grep -q "net.ipv6.conf.lo.disable_ipv6 = 1" /etc/sysctl.conf 1362 | then 1363 | echo "net.ipv6.conf.lo.disable_ipv6 = 1" >> /etc/sysctl.conf 1364 | fi 1365 | 1366 | echo -e "${textcolor}IPv6 is disabled:${clear}" 1367 | sysctl -p 1368 | 1369 | if [[ -z $(crontab -l | grep "@reboot sysctl -p") ]] 1370 | then 1371 | { crontab -l; echo "@reboot sysctl -p"; } | crontab - 1372 | fi 1373 | 1374 | echo "" 1375 | main_menu 1376 | } 1377 | 1378 | enable_ipv6() { 1379 | sed -i "/net.ipv6.conf.all.disable_ipv6 = 1/d" /etc/sysctl.conf 1380 | sed -i "/net.ipv6.conf.default.disable_ipv6 = 1/d" /etc/sysctl.conf 1381 | sed -i "/net.ipv6.conf.lo.disable_ipv6 = 1/d" /etc/sysctl.conf 1382 | 1383 | echo -e "${textcolor}IPv6 is enabled:${clear}" 1384 | sysctl -p 1385 | 1386 | if [[ ! -z $(crontab -l | grep "@reboot sysctl -p") ]] 1387 | then 1388 | crontab -l | sed "/@reboot sysctl -p/d" | crontab - 1389 | fi 1390 | 1391 | echo "" 1392 | main_menu 1393 | } 1394 | 1395 | show_paths() { 1396 | echo -e "${textcolor}Subscription page:${clear}" 1397 | echo -e "https://${domain}/${subspath}/sub.html${grey}?name=$(ls -A1 /var/www/${subspath} | grep "CLIENT.json" | sed "s/-TRJ-CLIENT\.json//g" | sed "s/-VLESS-CLIENT\.json//g" | uniq | tail -n 1)${clear}" 1398 | echo "Grey text shows an example of autofilling the username field" 1399 | echo "" 1400 | 1401 | echo -e "${textcolor}Configuration of the services:${clear}" 1402 | echo "Sing-Box config /etc/sing-box/config.json" 1403 | echo "NGINX config /etc/nginx/nginx.conf" 1404 | if [ -f /etc/haproxy/haproxy.cfg ] 1405 | then 1406 | echo "HAProxy config /etc/haproxy/haproxy.cfg" 1407 | echo "Trojan password reading script /etc/haproxy/auth.lua" 1408 | fi 1409 | echo "" 1410 | 1411 | echo -e "${textcolor}Content delivered by NGINX:${clear}" 1412 | echo "Subscription directory /var/www/${subspath}/" 1413 | echo "Rule set directory /var/www/${rulesetpath}/" 1414 | sitedir=$(grep "/var/www/" /etc/nginx/nginx.conf | head -n 1) 1415 | sitedir=${sitedir#*"/var/www/"} 1416 | sitedir=${sitedir%";"*} 1417 | if [[ "$sitedir" =~ "/" ]] 1418 | then 1419 | sitedir=$(echo "${sitedir}" | cut -d "/" -f 1) 1420 | fi 1421 | if [ -d /var/www/${sitedir} ] 1422 | then 1423 | echo "Site directory /var/www/${sitedir}/" 1424 | fi 1425 | echo "" 1426 | 1427 | echo -e "${textcolor}Certificates and accessory files:${clear}" 1428 | echo "Certificate directory /etc/letsencrypt/live/${domain}/" 1429 | if [ -f /etc/haproxy/certs/${domain}.pem ] 1430 | then 1431 | echo "Combined file with certificates /etc/haproxy/certs/${domain}.pem" 1432 | fi 1433 | echo "Certificate renewal config /etc/letsencrypt/renewal/${domain}.conf" 1434 | if [ -f /etc/letsencrypt/cloudflare.credentials ] 1435 | then 1436 | echo "File with Cloudflare API token/key /etc/letsencrypt/cloudflare.credentials" 1437 | fi 1438 | echo "" 1439 | 1440 | echo -e "${textcolor}Scripts:${clear}" 1441 | echo "This script (settings menu) /usr/local/bin/sbmanager" 1442 | echo "Rule set renewal script /usr/local/bin/rsupdate" 1443 | echo "" 1444 | echo "" 1445 | exit 0 1446 | } 1447 | 1448 | update_ssb() { 1449 | export version="1.2.2" 1450 | export language="2" 1451 | export -f get_ip 1452 | export -f templates 1453 | export -f get_data 1454 | export -f check_users 1455 | export -f validate_template 1456 | export -f get_pass 1457 | export -f edit_configs_sync 1458 | export -f sync_client_configs_github 1459 | 1460 | if [ $(wget -q -O /dev/null https://raw.githubusercontent.com/A-Zuro/Secret-Sing-Box/master/Scripts/update-server.sh; echo $?) -eq 0 ] 1461 | then 1462 | bash <(curl -Ls https://raw.githubusercontent.com/A-Zuro/Secret-Sing-Box/master/Scripts/update-server.sh) 1463 | exit 0 1464 | else 1465 | echo -e "${red}Error: failed to download data from GitHub${clear}" 1466 | echo "" 1467 | main_menu 1468 | fi 1469 | } 1470 | 1471 | main_menu() { 1472 | echo "" 1473 | echo -e "${textcolor}Select an option:${clear}" 1474 | echo "0 - Exit" 1475 | echo "1 - Show the list of users" 1476 | echo "2 - Add a new user" 1477 | echo "3 - Delete a user" 1478 | echo "---------------------------------" 1479 | echo "4 - Change \"stack\" in tun interface of the user" 1480 | echo "5 - Sync settings in all client configs" 1481 | echo "6 - Setup connection to custom Cloudflare IP on the client" 1482 | echo "---------------------------------" 1483 | echo "7 - Show the list of domains/suffixes routed through WARP" 1484 | echo "8 - Add a new domain/suffix to WARP routing" 1485 | echo "9 - Delete a domain/suffix from WARP routing" 1486 | echo "10 - Setup/remove a chain of two or more servers" 1487 | echo "---------------------------------" 1488 | echo "11 - Renew certificate manually" 1489 | echo "12 - Change domain" 1490 | echo "---------------------------------" 1491 | echo "13 - Disable IPv6 on the server" 1492 | echo "14 - Enable IPv6 on the server" 1493 | echo "---------------------------------" 1494 | echo "15 - Show paths to configs and other important files" 1495 | echo "16 - Update" 1496 | read option 1497 | echo "" 1498 | 1499 | case $option in 1500 | 1) 1501 | show_users 1502 | ;; 1503 | 2) 1504 | add_users 1505 | ;; 1506 | 3) 1507 | delete_users 1508 | ;; 1509 | 4) 1510 | change_stack 1511 | ;; 1512 | 5) 1513 | sync_client_configs 1514 | ;; 1515 | 6) 1516 | cf_ip_settings 1517 | ;; 1518 | 7) 1519 | show_warp_domains 1520 | ;; 1521 | 8) 1522 | add_warp_domains 1523 | ;; 1524 | 9) 1525 | delete_warp_domains 1526 | ;; 1527 | 10) 1528 | chain_setup 1529 | ;; 1530 | 11) 1531 | renew_cert 1532 | ;; 1533 | 12) 1534 | change_domain 1535 | ;; 1536 | 13) 1537 | disable_ipv6 1538 | ;; 1539 | 14) 1540 | enable_ipv6 1541 | ;; 1542 | 15) 1543 | show_paths 1544 | ;; 1545 | 16) 1546 | update_ssb 1547 | ;; 1548 | *) 1549 | exit 0 1550 | esac 1551 | } 1552 | 1553 | check_root 1554 | banner 1555 | get_data 1556 | main_menu 1557 | -------------------------------------------------------------------------------- /Scripts/sb-pc-linux-en.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | textcolor='\033[1;34m' 4 | red='\033[1;31m' 5 | clear='\033[0m' 6 | 7 | check_root() { 8 | if [[ $EUID -ne 0 ]] 9 | then 10 | echo "" 11 | echo -e "${red}Error: this script should be run as root, use \"sudo -i\" command${clear}" 12 | echo "" 13 | exit 1 14 | fi 15 | } 16 | 17 | check_sbmanager() { 18 | if [[ -f /usr/local/bin/sbmanager ]] && [[ ! -f /usr/local/bin/proxylist ]] 19 | then 20 | echo "" 21 | echo -e "${red}Error: this script should be run on the client device, not on the server${clear}" 22 | echo "" 23 | exit 1 24 | fi 25 | } 26 | 27 | banner() { 28 | echo "" 29 | echo "╔══╗ ╔══╗ ╦══╗" 30 | echo "║ ║ ║ ║" 31 | echo "╚══╗ ╚══╗ ╠══╣" 32 | echo " ║ ║ ║ ║" 33 | echo "╚══╝ ╚══╝ ╩══╝" 34 | } 35 | 36 | install_sing_box() { 37 | if [[ ! -f /usr/local/bin/proxylist ]] 38 | then 39 | touch /usr/local/bin/proxylist 40 | fi 41 | 42 | if [ $(sing-box version &> /dev/null; echo $?) -ne 0 ] 43 | then 44 | echo "" 45 | echo -e "${textcolor}Sing-Box is not installed${clear}" 46 | echo "" 47 | echo -e "${textcolor}[?]${clear} Press ${textcolor}Enter${clear} to install it or enter ${textcolor}x${clear} to exit:" 48 | read sbinstall 49 | exit_install 50 | 51 | echo -e "${textcolor}Installing Sing-Box...${clear}" 52 | curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc 53 | chmod a+r /etc/apt/keyrings/sagernet.asc 54 | echo "deb [arch=`dpkg --print-architecture` signed-by=/etc/apt/keyrings/sagernet.asc] https://deb.sagernet.org/ * *" | tee /etc/apt/sources.list.d/sagernet.list > /dev/null 55 | apt-get update -y 56 | apt-get install sing-box -y 57 | systemctl disable sing-box.service 58 | 59 | echo "" 60 | echo -e "${textcolor}Sing-Box is installed${clear}" 61 | echo "" 62 | echo -e "It can be updated with ${textcolor}apt-get install sing-box -y${clear} command" 63 | echo "" 64 | main_menu 65 | fi 66 | } 67 | 68 | exit_install() { 69 | if [[ "$sbinstall" == "x" ]] || [[ "$sbinstall" == "х" ]] 70 | then 71 | echo "" 72 | exit 0 73 | fi 74 | } 75 | 76 | exit_add_proxy() { 77 | if [[ "$link" == "x" ]] || [[ "$link" == "х" ]] 78 | then 79 | link="" 80 | main_menu 81 | fi 82 | } 83 | 84 | exit_del_proxy() { 85 | if [[ "$delcomm" == "x" ]] || [[ "$delcomm" == "х" ]] 86 | then 87 | delcomm="" 88 | main_menu 89 | fi 90 | } 91 | 92 | check_link() { 93 | while [[ -z $link ]] || [[ "$(curl -s -o /dev/null -w "%{http_code}" ${link})" != "200" ]] 94 | do 95 | if [[ -z $link ]] 96 | then 97 | : 98 | else 99 | echo -e "${red}Error: the link is incorrect or the server is not available${clear}" 100 | echo "" 101 | fi 102 | echo -e "${textcolor}[?]${clear} Enter your client config link or enter ${textcolor}x${clear} to exit:" 103 | read link 104 | [[ ! -z $link ]] && echo "" 105 | exit_add_proxy 106 | done 107 | } 108 | 109 | check_command_add() { 110 | while [[ -f /usr/local/bin/${newcomm} ]] || [[ $newcomm =~ " " ]] || [[ $newcomm =~ '$' ]] || [[ -z $newcomm ]] 111 | do 112 | if [[ -f /usr/local/bin/${newcomm} ]] 113 | then 114 | echo -e "${red}Error: this command already exists in /usr/local/bin${clear}" 115 | echo "" 116 | elif [[ $newcomm =~ " " ]] || [[ $newcomm =~ '$' ]] 117 | then 118 | echo -e "${red}Error: the command should not contain spaces and \$${clear}" 119 | echo "" 120 | elif [[ -z $newcomm ]] 121 | then 122 | : 123 | fi 124 | echo -e "${textcolor}[?]${clear} Enter the command for the new proxy:" 125 | read newcomm 126 | [[ ! -z $newcomm ]] && echo "" 127 | done 128 | } 129 | 130 | check_command_del() { 131 | while [[ -z $delcomm ]] || [[ ! -f /usr/local/bin/${delcomm} ]] 132 | do 133 | if [[ -z $delcomm ]] 134 | then 135 | : 136 | else 137 | echo -e "${red}Error: this command does not exist in /usr/local/bin${clear}" 138 | echo "" 139 | fi 140 | echo -e "${textcolor}[?]${clear} Enter the proxy command you want to delete or enter ${textcolor}x${clear} to exit:" 141 | read delcomm 142 | [[ ! -z $delcomm ]] && echo "" 143 | exit_del_proxy 144 | done 145 | } 146 | 147 | show_proxies() { 148 | proxynum=$(cat /usr/local/bin/proxylist | wc -l) 149 | echo -e "${textcolor}Number of proxies:${clear} ${proxynum}" 150 | cat /usr/local/bin/proxylist | sed "s/#//g" 151 | echo "" 152 | main_menu 153 | } 154 | 155 | add_proxies() { 156 | while [[ $link != "x" ]] && [[ $link != "х" ]] 157 | do 158 | echo -e "${textcolor}[?]${clear} Enter your client config link or enter ${textcolor}x${clear} to exit:" 159 | read link 160 | [[ ! -z $link ]] && echo "" 161 | exit_add_proxy 162 | check_link 163 | echo -e "${textcolor}[?]${clear} Enter the command for the new proxy:" 164 | read newcomm 165 | [[ ! -z $newcomm ]] && echo "" 166 | check_command_add 167 | 168 | touch /usr/local/bin/${newcomm} 169 | cat > /usr/local/bin/${newcomm} <<-EOF 170 | #!/bin/bash 171 | textcolor='\033[1;34m' 172 | red='\033[1;31m' 173 | clear='\033[0m' 174 | if [[ \$EUID -ne 0 ]] 175 | then 176 | echo "" 177 | echo -e "\${red}Error: this command should be run with sudo or as root\${clear}" 178 | echo "" 179 | exit 1 180 | fi 181 | wget -q -O /etc/sing-box/config.json ${link} 182 | systemctl start sing-box.service 183 | echo "" 184 | echo -e "\${textcolor}Started Sing-Box\${clear}" 185 | echo "Do not close this window while Sing-Box is running" 186 | echo -e "Enter \${textcolor}x\${clear} to disconnect:" 187 | while [[ \$run != "x" ]] && [[ \$run != "х" ]] 188 | do 189 | read run 190 | done 191 | echo "" 192 | systemctl stop sing-box.service 193 | EOF 194 | chmod +x /usr/local/bin/${newcomm} 195 | echo "#${newcomm}" >> /usr/local/bin/proxylist 196 | 197 | echo -e "Command ${textcolor}${newcomm}${clear} has been added, use it to connect to the proxy" 198 | echo "" 199 | done 200 | 201 | main_menu 202 | } 203 | 204 | delete_proxies() { 205 | while [[ $delcomm != "x" ]] && [[ $delcomm != "х" ]] 206 | do 207 | echo -e "${textcolor}[?]${clear} Enter the proxy command you want to delete or enter ${textcolor}x${clear} to exit:" 208 | read delcomm 209 | [[ ! -z $delcomm ]] && echo "" 210 | exit_del_proxy 211 | check_command_del 212 | 213 | rm /usr/local/bin/${delcomm} 214 | sed -i "/#$delcomm/d" /usr/local/bin/proxylist 215 | echo -e "Command ${textcolor}${delcomm}${clear} has been deleted" 216 | echo "" 217 | done 218 | 219 | main_menu 220 | } 221 | 222 | main_menu() { 223 | echo "" 224 | echo -e "${textcolor}Select an option:${clear}" 225 | echo "0 - Exit" 226 | echo "1 - Show the list of proxies" 227 | echo "2 - Add a new proxy" 228 | echo "3 - Delete a proxy" 229 | read option 230 | echo "" 231 | 232 | case $option in 233 | 1) 234 | show_proxies 235 | ;; 236 | 2) 237 | add_proxies 238 | ;; 239 | 3) 240 | delete_proxies 241 | ;; 242 | *) 243 | exit 0 244 | esac 245 | } 246 | 247 | check_root 248 | check_sbmanager 249 | banner 250 | install_sing_box 251 | main_menu -------------------------------------------------------------------------------- /Scripts/sb-pc-linux-ru.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | textcolor='\033[1;34m' 4 | red='\033[1;31m' 5 | clear='\033[0m' 6 | 7 | check_root() { 8 | if [[ $EUID -ne 0 ]] 9 | then 10 | echo "" 11 | echo -e "${red}Ошибка: этот скрипт нужно запускать от имени root, сначала введите команду \"sudo -i\"${clear}" 12 | echo "" 13 | exit 1 14 | fi 15 | } 16 | 17 | check_sbmanager() { 18 | if [[ -f /usr/local/bin/sbmanager ]] && [[ ! -f /usr/local/bin/proxylist ]] 19 | then 20 | echo "" 21 | echo -e "${red}Ошибка: этот скрипт нужно запускать на клиенте, а не на сервере${clear}" 22 | echo "" 23 | exit 1 24 | fi 25 | } 26 | 27 | banner() { 28 | echo "" 29 | echo "╔══╗ ╔══╗ ╦══╗" 30 | echo "║ ║ ║ ║" 31 | echo "╚══╗ ╚══╗ ╠══╣" 32 | echo " ║ ║ ║ ║" 33 | echo "╚══╝ ╚══╝ ╩══╝" 34 | } 35 | 36 | install_sing_box() { 37 | if [[ ! -f /usr/local/bin/proxylist ]] 38 | then 39 | touch /usr/local/bin/proxylist 40 | fi 41 | 42 | if [ $(sing-box version &> /dev/null; echo $?) -ne 0 ] 43 | then 44 | echo "" 45 | echo -e "${textcolor}Sing-Box не установлен${clear}" 46 | echo "" 47 | echo -e "${textcolor}[?]${clear} Нажмите ${textcolor}Enter${clear}, чтобы установить, или введите ${textcolor}x${clear}, чтобы выйти:" 48 | read sbinstall 49 | exit_install 50 | 51 | echo -e "${textcolor}Установка Sing-Box...${clear}" 52 | curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc 53 | chmod a+r /etc/apt/keyrings/sagernet.asc 54 | echo "deb [arch=`dpkg --print-architecture` signed-by=/etc/apt/keyrings/sagernet.asc] https://deb.sagernet.org/ * *" | tee /etc/apt/sources.list.d/sagernet.list > /dev/null 55 | apt-get update -y 56 | apt-get install sing-box -y 57 | systemctl disable sing-box.service 58 | 59 | echo "" 60 | echo -e "${textcolor}Sing-Box установлен${clear}" 61 | echo "" 62 | echo -e "Его можно обновлять командой ${textcolor}apt-get install sing-box -y${clear}" 63 | echo "" 64 | main_menu 65 | fi 66 | } 67 | 68 | exit_install() { 69 | if [[ "$sbinstall" == "x" ]] || [[ "$sbinstall" == "х" ]] 70 | then 71 | echo "" 72 | exit 0 73 | fi 74 | } 75 | 76 | exit_add_proxy() { 77 | if [[ "$link" == "x" ]] || [[ "$link" == "х" ]] 78 | then 79 | link="" 80 | main_menu 81 | fi 82 | } 83 | 84 | exit_del_proxy() { 85 | if [[ "$delcomm" == "x" ]] || [[ "$delcomm" == "х" ]] 86 | then 87 | delcomm="" 88 | main_menu 89 | fi 90 | } 91 | 92 | check_link() { 93 | while [[ -z $link ]] || [[ "$(curl -s -o /dev/null -w "%{http_code}" ${link})" != "200" ]] 94 | do 95 | if [[ -z $link ]] 96 | then 97 | : 98 | else 99 | echo -e "${red}Ошибка: ссылка введена неправильно или сервер недоступен${clear}" 100 | echo "" 101 | fi 102 | echo -e "${textcolor}[?]${clear} Введите ссылку на ваш клиентский конфиг или введите ${textcolor}x${clear}, чтобы выйти:" 103 | read link 104 | [[ ! -z $link ]] && echo "" 105 | exit_add_proxy 106 | done 107 | } 108 | 109 | check_command_add() { 110 | while [[ -f /usr/local/bin/${newcomm} ]] || [[ $newcomm =~ " " ]] || [[ $newcomm =~ '$' ]] || [[ -z $newcomm ]] 111 | do 112 | if [[ -f /usr/local/bin/${newcomm} ]] 113 | then 114 | echo -e "${red}Ошибка: эта команда уже существует в /usr/local/bin${clear}" 115 | echo "" 116 | elif [[ $newcomm =~ " " ]] || [[ $newcomm =~ '$' ]] 117 | then 118 | echo -e "${red}Ошибка: команда не должна содержать пробелы и \$${clear}" 119 | echo "" 120 | elif [[ -z $newcomm ]] 121 | then 122 | : 123 | fi 124 | echo -e "${textcolor}[?]${clear} Введите команду для нового прокси:" 125 | read newcomm 126 | [[ ! -z $newcomm ]] && echo "" 127 | done 128 | } 129 | 130 | check_command_del() { 131 | while [[ -z $delcomm ]] || [[ ! -f /usr/local/bin/${delcomm} ]] 132 | do 133 | if [[ -z $delcomm ]] 134 | then 135 | : 136 | else 137 | echo -e "${red}Ошибка: эта команда не существует в /usr/local/bin${clear}" 138 | echo "" 139 | fi 140 | echo -e "${textcolor}[?]${clear} Введите удаляемую команду для прокси или введите ${textcolor}x${clear}, чтобы выйти:" 141 | read delcomm 142 | [[ ! -z $delcomm ]] && echo "" 143 | exit_del_proxy 144 | done 145 | } 146 | 147 | show_proxies() { 148 | proxynum=$(cat /usr/local/bin/proxylist | wc -l) 149 | echo -e "${textcolor}Количество прокси:${clear} ${proxynum}" 150 | cat /usr/local/bin/proxylist | sed "s/#//g" 151 | echo "" 152 | main_menu 153 | } 154 | 155 | add_proxies() { 156 | while [[ $link != "x" ]] && [[ $link != "х" ]] 157 | do 158 | echo -e "${textcolor}[?]${clear} Введите ссылку на ваш клиентский конфиг или введите ${textcolor}x${clear}, чтобы выйти:" 159 | read link 160 | [[ ! -z $link ]] && echo "" 161 | exit_add_proxy 162 | check_link 163 | echo -e "${textcolor}[?]${clear} Введите команду для нового прокси:" 164 | read newcomm 165 | [[ ! -z $newcomm ]] && echo "" 166 | check_command_add 167 | 168 | touch /usr/local/bin/${newcomm} 169 | cat > /usr/local/bin/${newcomm} <<-EOF 170 | #!/bin/bash 171 | textcolor='\033[1;34m' 172 | red='\033[1;31m' 173 | clear='\033[0m' 174 | if [[ \$EUID -ne 0 ]] 175 | then 176 | echo "" 177 | echo -e "\${red}Ошибка: эту команду нужно запускать с sudo или от имени root\${clear}" 178 | echo "" 179 | exit 1 180 | fi 181 | wget -q -O /etc/sing-box/config.json ${link} 182 | systemctl start sing-box.service 183 | echo "" 184 | echo -e "\${textcolor}Sing-Box запущен\${clear}" 185 | echo "Не закрывайте это окно, пока Sing-Box работает" 186 | echo -e "Введите \${textcolor}x\${clear}, чтобы отключиться:" 187 | while [[ \$run != "x" ]] && [[ \$run != "х" ]] 188 | do 189 | read run 190 | done 191 | echo "" 192 | systemctl stop sing-box.service 193 | EOF 194 | chmod +x /usr/local/bin/${newcomm} 195 | echo "#${newcomm}" >> /usr/local/bin/proxylist 196 | 197 | echo -e "Команда ${textcolor}${newcomm}${clear} добавлена, используйте её для подключения к прокси" 198 | echo "" 199 | done 200 | 201 | main_menu 202 | } 203 | 204 | delete_proxies() { 205 | while [[ $delcomm != "x" ]] && [[ $delcomm != "х" ]] 206 | do 207 | echo -e "${textcolor}[?]${clear} Введите удаляемую команду для прокси или введите ${textcolor}x${clear}, чтобы выйти:" 208 | read delcomm 209 | [[ ! -z $delcomm ]] && echo "" 210 | exit_del_proxy 211 | check_command_del 212 | 213 | rm /usr/local/bin/${delcomm} 214 | sed -i "/#$delcomm/d" /usr/local/bin/proxylist 215 | echo -e "Команда ${textcolor}${delcomm}${clear} удалена" 216 | echo "" 217 | done 218 | 219 | main_menu 220 | } 221 | 222 | main_menu() { 223 | echo "" 224 | echo -e "${textcolor}Выберите действие:${clear}" 225 | echo "0 - Выйти" 226 | echo "1 - Вывести список прокси" 227 | echo "2 - Добавить новый прокси" 228 | echo "3 - Удалить прокси" 229 | read option 230 | echo "" 231 | 232 | case $option in 233 | 1) 234 | show_proxies 235 | ;; 236 | 2) 237 | add_proxies 238 | ;; 239 | 3) 240 | delete_proxies 241 | ;; 242 | *) 243 | exit 0 244 | esac 245 | } 246 | 247 | check_root 248 | check_sbmanager 249 | banner 250 | install_sing_box 251 | main_menu -------------------------------------------------------------------------------- /Scripts/update-server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | textcolor='\033[1;34m' 4 | textcolor_light='\033[1;36m' 5 | red='\033[1;31m' 6 | clear='\033[0m' 7 | 8 | check_parent() { 9 | if [[ -z $version ]] 10 | then 11 | echo "" 12 | echo -e "${red}Error: this script should be run from the settings menu, not manually${clear}" 13 | echo "" 14 | exit 1 15 | fi 16 | } 17 | 18 | check_update() { 19 | new_version="1.2.2" 20 | 21 | if [[ "${version}" == "${new_version}" ]] 22 | then 23 | if [[ "${language}" == "1" ]] 24 | then 25 | echo -e "Установлена последняя версия: ${textcolor}v${version}${clear}" 26 | else 27 | echo -e "The latest version is already installed: ${textcolor}v${version}${clear}" 28 | fi 29 | echo "" 30 | exit 0 31 | fi 32 | 33 | if [[ "${language}" == "1" ]] 34 | then 35 | echo -e "Текущая версия: ${textcolor}v${version}${clear}" 36 | echo -e "Доступна новая версия: ${textcolor}v${new_version}${clear}" 37 | else 38 | echo -e "Current version: ${textcolor}v${version}${clear}" 39 | echo -e "New version is available: ${textcolor}v${new_version}${clear}" 40 | fi 41 | } 42 | 43 | extract_values() { 44 | inboundnumbertr=$(jq '[.inbounds[].tag] | index("trojan-in")' /etc/sing-box/config.json) 45 | userstr=$(jq ".inbounds[${inboundnumbertr}].users" /etc/sing-box/config.json) 46 | 47 | if [ ! -f /etc/haproxy/auth.lua ] 48 | then 49 | inboundnumbervl=$(jq '[.inbounds[].tag] | index("vless-in")' /etc/sing-box/config.json) 50 | usersvl=$(jq ".inbounds[${inboundnumbervl}].users" /etc/sing-box/config.json) 51 | transport=$(jq -r '.inbounds[] | select(.tag=="trojan-in") | .transport.type' /etc/sing-box/config.json) 52 | fi 53 | 54 | warp_domain_suffix=$(cat /etc/sing-box/config.json | jq '.route.rules[] | select(.outbound=="warp") | .domain_suffix') 55 | 56 | if [[ $(jq 'any(.outbounds[]; .tag == "proxy")' /etc/sing-box/config.json) == "true" ]] 57 | then 58 | nextoutbound=$(cat /etc/sing-box/config.json | jq '.outbounds[] | select(.tag=="proxy")') 59 | fi 60 | } 61 | 62 | insert_values() { 63 | inboundnumbertr=$(jq '[.inbounds[].tag] | index("trojan-in")' /etc/sing-box/config.json) 64 | inboundnumbervl=$(jq '[.inbounds[].tag] | index("vless-in")' /etc/sing-box/config.json) 65 | 66 | echo "$(jq /etc/sing-box/config.json 67 | echo "$(jq ".inbounds[${inboundnumbertr}].users |= . + ${userstr}" /etc/sing-box/config.json)" > /etc/sing-box/config.json 68 | 69 | if [ ! -f /etc/haproxy/auth.lua ] 70 | then 71 | echo "$(jq /etc/sing-box/config.json 72 | echo "$(jq ".inbounds[${inboundnumbervl}].users |= . + ${usersvl}" /etc/sing-box/config.json)" > /etc/sing-box/config.json 73 | fi 74 | 75 | echo "$(jq ".inbounds[${inboundnumbertr}].transport.path = \"/${trojanpath}\" | .inbounds[${inboundnumbervl}].transport.path = \"/${vlesspath}\"" /etc/sing-box/config.json)" > /etc/sing-box/config.json 76 | warpnum=$(jq '[.route.rules[].outbound] | index("warp")' /etc/sing-box/config.json) 77 | echo "$(jq ".route.rules[${warpnum}].domain_suffix = [] | .route.rules[${warpnum}].domain_suffix |= . + ${warp_domain_suffix}" /etc/sing-box/config.json)" > /etc/sing-box/config.json 78 | 79 | if [[ "${transport}" == "httpupgrade" ]] 80 | then 81 | echo "$(jq ".inbounds[${inboundnumbertr}].transport.type = \"httpupgrade\" | .inbounds[${inboundnumbervl}].transport.type = \"httpupgrade\"" /etc/sing-box/config.json)" > /etc/sing-box/config.json 82 | fi 83 | 84 | if [ -f /etc/haproxy/auth.lua ] 85 | then 86 | echo "$(jq "del(.inbounds[${inboundnumbertr}].transport.type) | del(.inbounds[${inboundnumbertr}].transport.path) | del(.inbounds[${inboundnumbervl}])" /etc/sing-box/config.json)" > /etc/sing-box/config.json 87 | fi 88 | 89 | if [[ ! -z ${nextoutbound} ]] 90 | then 91 | insert_chain 92 | fi 93 | 94 | sed -i -e "s/$temprulesetpath/$rulesetpath/g" /etc/sing-box/config.json 95 | } 96 | 97 | insert_chain() { 98 | proxy_num=$(jq '.outbounds | length' /etc/sing-box/config.json) 99 | proxy_rule_num=$(jq '.route.rules | length' /etc/sing-box/config.json) 100 | 101 | if [ -f /etc/haproxy/auth.lua ] 102 | then 103 | echo "$(jq ".route.rules[${proxy_rule_num}] |= . + {\"inbound\":[\"trojan-in\"],\"outbound\":\"proxy\"} | .outbounds[${proxy_num}] |= . + ${nextoutbound}" /etc/sing-box/config.json)" > /etc/sing-box/config.json 104 | else 105 | echo "$(jq ".route.rules[${proxy_rule_num}] |= . + {\"inbound\":[\"trojan-in\",\"vless-in\"],\"outbound\":\"proxy\"} | .outbounds[${proxy_num}] |= . + ${nextoutbound}" /etc/sing-box/config.json)" > /etc/sing-box/config.json 106 | fi 107 | 108 | warpnum=$(jq '[.route.rules[].outbound] | index("warp")' /etc/sing-box/config.json) 109 | echo "$(jq ".route.rules[${warpnum}] |= . + {\"rule_set\":[\"geoip-ru\",\"gov-ru\"],\"domain_suffix\":[\".ru\",\".su\",\".ru.com\",\".ru.net\"],\"domain_keyword\":[\"xn--\"],\"outbound\":\"warp\"}" /etc/sing-box/config.json)" > /etc/sing-box/config.json 110 | 111 | if [[ $(jq 'any(.outbounds[]; .tag == "IPv4")' /etc/sing-box/config.json) == "true" ]] 112 | then 113 | echo "$(jq /etc/sing-box/config.json 114 | fi 115 | 116 | if [[ $(jq 'any(.route.rules[]; .outbound == "IPv4")' /etc/sing-box/config.json) == "true" ]] 117 | then 118 | echo "$(jq /etc/sing-box/config.json 119 | fi 120 | 121 | rule_sets=(google telegram openai) 122 | 123 | for ruleset_tag in "${rule_sets[@]}" 124 | do 125 | if [[ $(jq "any(.route.rule_set[]; .tag == \"${ruleset_tag}\")" /etc/sing-box/config.json) == "true" ]] 126 | then 127 | echo "$(jq /etc/sing-box/config.json 128 | fi 129 | done 130 | } 131 | 132 | update_services() { 133 | echo "" 134 | 135 | if [[ "${language}" == "1" ]] 136 | then 137 | echo -e "${textcolor_light}Обновление пакетов...${clear}" 138 | else 139 | echo -e "${textcolor_light}Updating packages...${clear}" 140 | fi 141 | 142 | systemctl stop sing-box.service 143 | systemctl stop warp-svc.service 144 | systemctl stop nginx.service 145 | 146 | if [ -f /etc/haproxy/auth.lua ] 147 | then 148 | systemctl stop haproxy.service 149 | fi 150 | 151 | if [ -f /etc/apt/apt.conf.d/50unattended-upgrades ] 152 | then 153 | systemctl stop unattended-upgrades 154 | fi 155 | 156 | extract_values 157 | cp /etc/sing-box/config.json /etc/sing-box/config.json.0 158 | wget -O /etc/sing-box/config.json.1 https://raw.githubusercontent.com/A-Zuro/Secret-Sing-Box/master/Config-Templates/config.json 159 | 160 | if [ $? -eq 0 ] 161 | then 162 | mv -f /etc/sing-box/config.json.1 /etc/sing-box/config.json 163 | insert_values 164 | fi 165 | 166 | for i in $(seq 0 $(expr $(jq ".route.rule_set | length" /etc/sing-box/config.json) - 1)) 167 | do 168 | ruleset_link=$(jq -r ".route.rule_set[${i}].path" /etc/sing-box/config.json) 169 | ruleset=${ruleset_link#"/var/www/${rulesetpath}/"} 170 | if [ ! -f ${ruleset_link} ] 171 | then 172 | wget -P /var/www/${rulesetpath} https://github.com/SagerNet/sing-geosite/raw/rule-set/${ruleset} 173 | fi 174 | done 175 | 176 | chmod -R 755 /var/www/${rulesetpath} 177 | 178 | apt-mark unhold sing-box 179 | apt update -y && apt -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" full-upgrade -y 180 | apt-mark hold sing-box 181 | apt autoremove -y && apt autoclean -y 182 | systemctl daemon-reload 183 | 184 | systemctl start sing-box.service 185 | systemctl start warp-svc.service 186 | systemctl start nginx.service 187 | 188 | if [ -f /etc/haproxy/auth.lua ] 189 | then 190 | systemctl start haproxy.service 191 | fi 192 | 193 | if [ -f /etc/apt/apt.conf.d/50unattended-upgrades ] 194 | then 195 | systemctl start unattended-upgrades 196 | fi 197 | 198 | echo "" 199 | } 200 | 201 | check_sync_client() { 202 | if [[ "${language}" == "1" ]] 203 | then 204 | echo -e "${textcolor_light}Синхронизация настроек в клиентских конфигах с GitHub...${clear}" 205 | else 206 | echo -e "${textcolor_light}Syncing settings in client configs with GitHub...${clear}" 207 | fi 208 | 209 | check_users 210 | validate_template 211 | 212 | if [[ "${stop_sync}" != "1" ]] 213 | then 214 | sync_client_configs_github 215 | fi 216 | } 217 | 218 | update_sub_page() { 219 | if [[ "${language}" == "1" ]] 220 | then 221 | echo -e "${textcolor_light}Обновление страницы выдачи подписок...${clear}" 222 | else 223 | echo -e "${textcolor_light}Updating subscription page...${clear}" 224 | fi 225 | 226 | if [ ! -f /etc/haproxy/auth.lua ] && [[ "${language}" == "1" ]] 227 | then 228 | sub_page_file="sub-ru.html" 229 | elif [ ! -f /etc/haproxy/auth.lua ] && [[ "${language}" != "1" ]] 230 | then 231 | sub_page_file="sub-en.html" 232 | elif [ -f /etc/haproxy/auth.lua ] && [[ "${language}" == "1" ]] 233 | then 234 | sub_page_file="sub-ru-hapr.html" 235 | else 236 | sub_page_file="sub-en-hapr.html" 237 | fi 238 | 239 | wget -O /var/www/${subspath}/sub.html https://raw.githubusercontent.com/A-Zuro/Secret-Sing-Box/master/Subscription-Page/${sub_page_file} 240 | wget -O /var/www/${subspath}/background.jpg https://raw.githubusercontent.com/A-Zuro/Secret-Sing-Box/master/Subscription-Page/background.jpg 241 | sed -i -e "s/DOMAIN/$domain/g" -e "s/SUBSCRIPTION-PATH/$subspath/g" /var/www/${subspath}/sub.html 242 | } 243 | 244 | update_scripts() { 245 | if [[ "${language}" == "1" ]] 246 | then 247 | echo -e "${textcolor_light}Обновление скриптов...${clear}" 248 | else 249 | echo -e "${textcolor_light}Updating scripts...${clear}" 250 | fi 251 | 252 | if [[ "${language}" == "1" ]] 253 | then 254 | sbmanager_file="sb-manager-ru.sh" 255 | else 256 | sbmanager_file="sb-manager-en.sh" 257 | fi 258 | 259 | wget -O /usr/local/bin/sbmanager https://raw.githubusercontent.com/A-Zuro/Secret-Sing-Box/master/Scripts/${sbmanager_file} 260 | wget -O /usr/local/bin/rsupdate https://raw.githubusercontent.com/A-Zuro/Secret-Sing-Box/master/Scripts/ruleset-update.sh 261 | chmod +x /usr/local/bin/sbmanager /usr/local/bin/rsupdate 262 | 263 | if ! grep -q "alias ssb=" /etc/bash.bashrc 264 | then 265 | echo "alias ssb='/usr/local/bin/sbmanager'" >> /etc/bash.bashrc 266 | fi 267 | 268 | echo "" 269 | } 270 | 271 | final_message() { 272 | if [[ "${language}" == "1" ]] 273 | then 274 | echo -e "${textcolor}Установка обновления v${new_version} завершена!${clear}" 275 | echo "Перезагружать сервер не обязательно" 276 | echo "" 277 | echo "При проблемах с Sing-Box запустите команду:" 278 | echo "cp -f /etc/sing-box/config.json.0 /etc/sing-box/config.json && systemctl restart sing-box" 279 | else 280 | echo -e "${textcolor}The update v${new_version} has been installed!${clear}" 281 | echo "It is not necessary to reboot the server" 282 | echo "" 283 | echo "If you are having problems with Sing-Box, run this command:" 284 | echo "cp -f /etc/sing-box/config.json.0 /etc/sing-box/config.json && systemctl restart sing-box" 285 | fi 286 | 287 | echo "" 288 | echo "" 289 | sleep 1 290 | exit 0 291 | } 292 | 293 | main_menu() { 294 | stop_sync="1" 295 | } 296 | 297 | update_menu() { 298 | echo "" 299 | if [[ "${language}" == "1" ]] 300 | then 301 | echo -e "${textcolor}[?]${clear} Выберите вариант обновления:" 302 | echo "0 - Выйти" 303 | echo "1 - Обновить всё" 304 | echo "2 - Обновить без синхронизации клиентских конфигов с GitHub" 305 | else 306 | echo -e "${textcolor}[?]${clear} Select an update option:" 307 | echo "0 - Exit" 308 | echo "1 - Update everything" 309 | echo "2 - Update without syncing client configs with GitHub" 310 | fi 311 | read update_option 312 | echo "" 313 | 314 | case $update_option in 315 | 1) 316 | update_services 317 | check_sync_client 318 | update_sub_page 319 | update_scripts 320 | final_message 321 | ;; 322 | 2) 323 | update_services 324 | update_sub_page 325 | update_scripts 326 | final_message 327 | ;; 328 | *) 329 | exit 0 330 | esac 331 | } 332 | 333 | check_parent 334 | check_update 335 | get_data 336 | update_menu 337 | -------------------------------------------------------------------------------- /Subscription-Page/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-Zuro/Secret-Sing-Box/af35f034a52ed440f9cbc5f07d28b484e93ac942/Subscription-Page/background.jpg -------------------------------------------------------------------------------- /Subscription-Page/sub-en-hapr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 |For Android, iOS and Windows (Hiddify)
120 |121 |
125 | 126 |For Android, iOS and Windows (Hiddify)
127 |128 |
133 | 134 |Для Android, iOS и Windows (Hiddify)
120 |121 |
125 | 126 |Для Android, iOS и Windows (Hiddify)
127 |128 |
133 | 134 |