1- import sys
21import os
2+ import re
3+ import shutil
34import subprocess
45import http .server
56import socketserver
67import threading
8+ import requests
9+ from flask import Flask , request , Response
10+ import json
11+ import time
12+ import base64
713
8- # 定义端口
9- port = int (os .getenv ('PORT' , 3000 ))
14+ app = Flask (__name__ )
1015
11- # http路由
16+ # Set environment variables
17+ FILE_PATH = os .environ .get ('FILE_PATH' , './temp' )
18+ PROJECT_URL = os .environ .get ('URL' , '' ) # 填写项目分配的url可实现自动访问,例如:https://www.google.com,留空即不启用该功能
19+ INTERVAL_SECONDS = int (os .environ .get ("TIME" , 120 )) # 访问间隔时间,默认120s,单位:秒
20+ UUID = os .environ .get ('UUID' , '0004add9-5c68-8bab-870c-08cd5320df00' )
21+ NEZHA_SERVER = os .environ .get ('NEZHA_SERVER' , 'nz.abcd.com' ) # 哪吒3个变量不全不运行
22+ NEZHA_PORT = os .environ .get ('NEZHA_PORT' , '5555' ) # 哪吒端口为443时开启tls
23+ NEZHA_KEY = os .environ .get ('NEZHA_KEY' , '' )
24+ ARGO_DOMAIN = os .environ .get ('ARGO_DOMAIN' , '' ) # 国定隧道域名,留空即启用临时隧道
25+ ARGO_AUTH = os .environ .get ('ARGO_AUTH' , '' ) # 国定隧道json或token,留空即启用临时隧道
26+ CFIP = os .environ .get ('CFIP' , 'skk.moe' )
27+ NAME = os .environ .get ('NAME' , 'Vls' )
28+ PORT = int (os .environ .get ('PORT' , 3000 )) # http端口,也是订阅端口,游戏玩具类需改为分配的端口,否则无法订阅
29+ ARGO_PORT = int (os .environ .get ('ARGO_PORT' , 8001 )) # Argo端口,固定隧道token请改回8080或在cf后台设置的端口与这里对应
30+ CFPORT = int (os .environ .get ('CFPORT' , 443 )) # 节点端口
31+
32+ # Create directory if it doesn't exist
33+ if not os .path .exists (FILE_PATH ):
34+ os .makedirs (FILE_PATH )
35+ print (f"{ FILE_PATH } has been created" )
36+ else :
37+ print (f"{ FILE_PATH } already exists" )
38+
39+ # Clean old files
40+ paths_to_delete = ['boot.log' , 'list.txt' ,'sub.txt' , 'swith' , 'web' , 'bot' , 'tunnel.yml' , 'tunnel.json' ]
41+ for file in paths_to_delete :
42+ file_path = os .path .join (FILE_PATH , file )
43+ try :
44+ os .unlink (file_path )
45+ print (f"{ file_path } has been deleted" )
46+ except Exception as e :
47+ print (f"Skip Delete { file_path } " )
48+
49+ # http server
1250class MyHandler (http .server .SimpleHTTPRequestHandler ):
1351
1452 def log_message (self , format , * args ):
@@ -21,7 +59,7 @@ def do_GET(self):
2159 self .wfile .write (b'Hello, world' )
2260 elif self .path == '/sub' :
2361 try :
24- with open ("./temp/ sub.txt" , 'rb' ) as file :
62+ with open (os . path . join ( FILE_PATH , ' sub.txt' ) , 'rb' ) as file :
2563 content = file .read ()
2664 self .send_response (200 )
2765 self .send_header ('Content-Type' , 'text/plain; charset=utf-8' )
@@ -35,27 +73,285 @@ def do_GET(self):
3573 self .send_response (404 )
3674 self .end_headers ()
3775 self .wfile .write (b'Not found' )
38- httpd = socketserver .TCPServer (('' , port ), MyHandler )
76+
77+ httpd = socketserver .TCPServer (('' , PORT ), MyHandler )
3978server_thread = threading .Thread (target = httpd .serve_forever )
4079server_thread .daemon = True
4180server_thread .start ()
4281
43- # 定义要执行的Shell命令并赋权
44- shell_command = "chmod +x start.sh && ./start.sh"
82+ # Generate xr-ay config file
83+ def generate_config ():
84+ config = {"log" :{"access" :"/dev/null" ,"error" :"/dev/null" ,"loglevel" :"none" ,},"inbounds" :[{"port" :ARGO_PORT ,"protocol" :"vless" ,"settings" :{"clients" :[{"id" :UUID ,"flow" :"xtls-rprx-vision" ,},],"decryption" :"none" ,"fallbacks" :[{"dest" :3001 },{"path" :"/vless" ,"dest" :3002 },{"path" :"/vmess" ,"dest" :3003 },{"path" :"/trojan" ,"dest" :3004 },],},"streamSettings" :{"network" :"tcp" ,},},{"port" :3001 ,"listen" :"127.0.0.1" ,"protocol" :"vless" ,"settings" :{"clients" :[{"id" :UUID },],"decryption" :"none" },"streamSettings" :{"network" :"ws" ,"security" :"none" }},{"port" :3002 ,"listen" :"127.0.0.1" ,"protocol" :"vless" ,"settings" :{"clients" :[{"id" :UUID ,"level" :0 }],"decryption" :"none" },"streamSettings" :{"network" :"ws" ,"security" :"none" ,"wsSettings" :{"path" :"/vless" }},"sniffing" :{"enabled" :True ,"destOverride" :["http" ,"tls" ,"quic" ],"metadataOnly" :False }},{"port" :3003 ,"listen" :"127.0.0.1" ,"protocol" :"vmess" ,"settings" :{"clients" :[{"id" :UUID ,"alterId" :0 }]},"streamSettings" :{"network" :"ws" ,"wsSettings" :{"path" :"/vmess" }},"sniffing" :{"enabled" :True ,"destOverride" :["http" ,"tls" ,"quic" ],"metadataOnly" :False }},{"port" :3004 ,"listen" :"127.0.0.1" ,"protocol" :"trojan" ,"settings" :{"clients" :[{"password" :UUID },]},"streamSettings" :{"network" :"ws" ,"security" :"none" ,"wsSettings" :{"path" :"/trojan" }},"sniffing" :{"enabled" :True ,"destOverride" :["http" ,"tls" ,"quic" ],"metadataOnly" :False }},],"dns" :{"servers" :["https+local://8.8.8.8/dns-query" ]},"outbounds" :[{"protocol" :"freedom" },{"tag" :"WARP" ,"protocol" :"wireguard" ,"settings" :{"secretKey" :"YFYOAdbw1bKTHlNNi+aEjBM3BO7unuFC5rOkMRAz9XY=" ,"address" :["172.16.0.2/32" ,"2606:4700:110:8a36:df92:102a:9602:fa18/128" ],"peers" :[{"publicKey" :"bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=" ,"allowedIPs" :["0.0.0.0/0" ,"::/0" ],"endpoint" :"162.159.193.10:2408" }],"reserved" :[78 ,135 ,76 ],"mtu" :1280 }},],"routing" :{"domainStrategy" :"AsIs" ,"rules" :[{"type" :"field" ,"domain" :["domain:openai.com" ,"domain:ai.com" ],"outboundTag" :"WARP" },]}}
85+
86+ with open (os .path .join (FILE_PATH , 'config.json' ), 'w' , encoding = 'utf-8' ) as config_file :
87+ json .dump (config , config_file , ensure_ascii = False , indent = 2 )
88+
89+ generate_config ()
90+
91+ # Determine system architecture
92+ def get_system_architecture ():
93+ arch = os .uname ().machine
94+ return 'arm' if 'arm' in arch else 'amd'
95+
96+ # Download file
97+ def download_file (file_name , file_url ):
98+ file_path = os .path .join (FILE_PATH , file_name )
99+ with requests .get (file_url , stream = True ) as response , open (file_path , 'wb' ) as file :
100+ shutil .copyfileobj (response .raw , file )
101+
102+ # Download and run files
103+ def download_files_and_run ():
104+ architecture = get_system_architecture ()
105+ files_to_download = get_files_for_architecture (architecture )
106+
107+ if not files_to_download :
108+ print ("Can't find a file for the current architecture" )
109+ return
110+
111+ for file_info in files_to_download :
112+ try :
113+ download_file (file_info ['file_name' ], file_info ['file_url' ])
114+ print (f"Downloaded { file_info ['file_name' ]} successfully" )
115+ except Exception as e :
116+ print (f"Download { file_info ['file_name' ]} failed: { e } " )
117+
118+ # Authorize and run
119+ files_to_authorize = ['./swith' , './web' , './bot' ]
120+ authorize_files (files_to_authorize )
121+
122+ # Run ne-zha
123+ NEZHA_TLS = ''
124+ if NEZHA_SERVER and NEZHA_PORT and NEZHA_KEY :
125+ NEZHA_TLS = '--tls' if NEZHA_PORT == '443' else ''
126+ command = f"nohup { FILE_PATH } /swith -s { NEZHA_SERVER } :{ NEZHA_PORT } -p { NEZHA_KEY } { NEZHA_TLS } >/dev/null 2>&1 &"
127+ try :
128+ subprocess .run (command , shell = True , check = True )
129+ print ('swith is running' )
130+ subprocess .run ('sleep 1' , shell = True ) # Wait for 1 second
131+ except subprocess .CalledProcessError as e :
132+ print (f'swith running error: { e } ' )
133+ else :
134+ print ('NEZHA variable is empty, skip running' )
135+
136+ # Run xr-ay
137+ command1 = f"nohup { FILE_PATH } /web -c { FILE_PATH } /config.json >/dev/null 2>&1 &"
138+ try :
139+ subprocess .run (command1 , shell = True , check = True )
140+ print ('web is running' )
141+ subprocess .run ('sleep 1' , shell = True ) # Wait for 1 second
142+ except subprocess .CalledProcessError as e :
143+ print (f'web running error: { e } ' )
144+
145+ # Run cloud-fared
146+ if os .path .exists (os .path .join (FILE_PATH , 'bot' )):
147+ # Get command line arguments for cloud-fared
148+ args = get_cloud_flare_args ()
149+ # print(args)
150+ try :
151+ subprocess .run (f"nohup { FILE_PATH } /bot { args } >/dev/null 2>&1 &" , shell = True , check = True )
152+ print ('bot is running' )
153+ subprocess .run ('sleep 2' , shell = True ) # Wait for 2 seconds
154+ except subprocess .CalledProcessError as e :
155+ print (f'Error executing command: { e } ' )
156+
157+ subprocess .run ('sleep 3' , shell = True ) # Wait for 3 seconds
158+
159+
160+ def get_cloud_flare_args ():
161+
162+ processed_auth = ARGO_AUTH
163+ try :
164+ auth_data = json .loads (ARGO_AUTH )
165+ if 'TunnelSecret' in auth_data and 'AccountTag' in auth_data and 'TunnelID' in auth_data :
166+ processed_auth = 'TunnelSecret'
167+ except json .JSONDecodeError :
168+ pass
169+
170+ # Determines the condition and generates the corresponding args
171+ if not processed_auth and not ARGO_DOMAIN :
172+ args = f'tunnel --edge-ip-version auto --no-autoupdate --protocol http2 --logfile { FILE_PATH } /boot.log --loglevel info --url http://localhost:{ ARGO_PORT } '
173+ elif processed_auth == 'TunnelSecret' :
174+ args = f'tunnel --edge-ip-version auto --config { FILE_PATH } /tunnel.yml run'
175+ elif processed_auth and ARGO_DOMAIN and 120 <= len (processed_auth ) <= 250 :
176+ args = f'tunnel --edge-ip-version auto --no-autoupdate --protocol http2 run --token { processed_auth } '
177+ else :
178+ # Default args for other cases
179+ args = f'tunnel --edge-ip-version auto --no-autoupdate --protocol http2 --logfile { FILE_PATH } /boot.log --loglevel info --url http://localhost:{ ARGO_PORT } '
180+
181+ return args
182+
183+ # Return file information based on system architecture
184+ def get_files_for_architecture (architecture ):
185+ if architecture == 'arm' :
186+ return [
187+ {'file_name' : 'swith' , 'file_url' : 'https://github.com/eooce/test/releases/download/ARM/swith' },
188+ {'file_name' : 'web' , 'file_url' : 'https://github.com/eooce/test/releases/download/ARM/web' },
189+ {'file_name' : 'bot' , 'file_url' : 'https://github.com/eooce/test/releases/download/ARM/server' },
190+ ]
191+ elif architecture == 'amd' :
192+ return [
193+ {'file_name' : 'swith' , 'file_url' : 'https://github.com/eooce/test/releases/download/bulid/swith' },
194+ {'file_name' : 'web' , 'file_url' : 'https://github.com/eooce/test/releases/download/123/web' },
195+ {'file_name' : 'bot' , 'file_url' : 'https://github.com/eooce/test/raw/main/server' },
196+ ]
197+ return []
198+
199+ # Authorize files
200+ def authorize_files (file_paths ):
201+ new_permissions = 0o775
202+
203+ for relative_file_path in file_paths :
204+ absolute_file_path = os .path .join (FILE_PATH , relative_file_path )
205+ try :
206+ os .chmod (absolute_file_path , new_permissions )
207+ print (f"Empowerment success for { absolute_file_path } : { oct (new_permissions )} " )
208+ except Exception as e :
209+ print (f"Empowerment failed for { absolute_file_path } : { e } " )
210+
211+
212+ # Get fixed tunnel JSON and yml
213+ def argo_config ():
214+ if not ARGO_AUTH or not ARGO_DOMAIN :
215+ print ("ARGO_DOMAIN or ARGO_AUTH is empty, use quick Tunnels" )
216+ return
217+
218+ if 'TunnelSecret' in ARGO_AUTH :
219+ with open (os .path .join (FILE_PATH , 'tunnel.json' ), 'w' ) as file :
220+ file .write (ARGO_AUTH )
221+ tunnel_yaml = f"""
222+ tunnel: { ARGO_AUTH .split ('"' )[11 ]}
223+ credentials-file: { os .path .join (FILE_PATH , 'tunnel.json' )}
224+ protocol: http2
225+
226+ ingress:
227+ - hostname: { ARGO_DOMAIN }
228+ service: http://localhost:{ ARGO_PORT }
229+ originRequest:
230+ noTLSVerify: true
231+ - service: http_status:404
232+ """
233+ with open (os .path .join (FILE_PATH , 'tunnel.yml' ), 'w' ) as file :
234+ file .write (tunnel_yaml )
235+ else :
236+ print ("Use token connect to tunnel" )
237+
238+ argo_config ()
239+
240+ # Get temporary tunnel domain
241+ def extract_domains ():
242+ argo_domain = ''
243+
244+ if ARGO_AUTH and ARGO_DOMAIN :
245+ argo_domain = ARGO_DOMAIN
246+ print ('ARGO_DOMAIN:' , argo_domain )
247+ generate_links (argo_domain )
248+ else :
249+ try :
250+ with open (os .path .join (FILE_PATH , 'boot.log' ), 'r' , encoding = 'utf-8' ) as file :
251+ content = file .read ()
252+ # Use regular expressions to match domain ending in trycloudflare.com
253+ match = re .search (r'https://([^ ]+\.trycloudflare\.com)' , content )
254+ if match :
255+ argo_domain = match .group (1 )
256+ print ('ArgoDomain:' , argo_domain )
257+ generate_links (argo_domain )
258+ else :
259+ print ('ArgoDomain not found, re-running bot to obtain ArgoDomain' )
260+ # delete boot.log file
261+ os .remove (os .path .join (FILE_PATH , 'boot.log' ))
262+ # Rerun the bot directly to get the ArgoDomain.
263+ args = f"tunnel --edge-ip-version auto --no-autoupdate --protocol http2 --logfile { FILE_PATH } /boot.log --loglevel info --url http://localhost:{ ARGO_PORT } "
264+ try :
265+ subprocess .run (f"nohup { FILE_PATH } /bot { args } >/dev/null 2>&1 &" , shell = True , check = True )
266+ print ('bot is running.' )
267+ time .sleep (3 )
268+ # Retrieve domain name
269+ extract_domains ()
270+ except subprocess .CalledProcessError as e :
271+ print (f"Error executing command: { e } " )
272+ except IndexError as e :
273+ print (f"IndexError while reading boot.log: { e } " )
274+ except Exception as e :
275+ print (f"Error reading boot.log: { e } " )
276+
277+
278+ # Generate list and sub info
279+ def generate_links (argo_domain ):
280+ meta_info = subprocess .run (['curl' , '-s' , 'https://speed.cloudflare.com/meta' ], capture_output = True , text = True )
281+ meta_info = meta_info .stdout .split ('"' )
282+ ISP = f"{ meta_info [25 ]} -{ meta_info [17 ]} " .replace (' ' , '_' ).strip ()
283+
284+ time .sleep (2 )
285+ VMESS = {"v" : "2" , "ps" : f"{ NAME } -{ ISP } " , "add" : CFIP , "port" : CFPORT , "id" : UUID , "aid" : "0" , "scy" : "none" , "net" : "ws" , "type" : "none" , "host" : argo_domain , "path" : "/vmess?ed=2048" , "tls" : "tls" , "sni" : argo_domain , "alpn" : "" }
286+
287+ list_txt = f"""
288+ vless://{ UUID } @{ CFIP } :{ CFPORT } ?encryption=none&security=tls&sni={ argo_domain } &type=ws&host={ argo_domain } &path=%2Fvless?ed=2048#{ NAME } -{ ISP }
289+
290+ vmess://{ base64 .b64encode (json .dumps (VMESS ).encode ('utf-8' )).decode ('utf-8' )}
291+
292+ trojan://{ UUID } @{ CFIP } :{ CFPORT } ?security=tls&sni={ argo_domain } &type=ws&host={ argo_domain } &path=%2Ftrojan?ed=2048#{ NAME } -{ ISP }
293+ """
294+
295+ with open (os .path .join (FILE_PATH , 'list.txt' ), 'w' , encoding = 'utf-8' ) as list_file :
296+ list_file .write (list_txt )
297+
298+ sub_txt = base64 .b64encode (list_txt .encode ('utf-8' )).decode ('utf-8' )
299+ with open (os .path .join (FILE_PATH , 'sub.txt' ), 'w' , encoding = 'utf-8' ) as sub_file :
300+ sub_file .write (sub_txt )
301+
302+ try :
303+ with open (os .path .join (FILE_PATH , 'sub.txt' ), 'rb' ) as file :
304+ sub_content = file .read ()
305+ print (f"\n { sub_content .decode ('utf-8' )} " )
306+ except FileNotFoundError :
307+ print (f"sub.txt not found" )
308+
309+ print (f'{ FILE_PATH } /sub.txt saved successfully' )
310+ time .sleep (20 )
311+
312+ # cleanup files
313+ files_to_delete = ['boot.log' , 'list.txt' ,'config.json' ,'tunnel.yml' ,'tunnel.json' ]
314+ for file_to_delete in files_to_delete :
315+ file_path_to_delete = os .path .join (FILE_PATH , file_to_delete )
316+ try :
317+ os .remove (file_path_to_delete )
318+ print (f"{ file_path_to_delete } has been deleted" )
319+ except Exception as e :
320+ print (f"Error deleting { file_path_to_delete } : { e } " )
321+
322+ print ('\033 c' , end = '' )
323+ print ('App is running' )
324+ print ('Thank you for using this script, enjoy!' )
325+
326+ # Run the callback
327+ def start_server ():
328+ download_files_and_run ()
329+ extract_domains ()
330+
331+ start_server ()
332+
333+ # auto visit project page
334+ has_logged_empty_message = False
45335
46- # 执行shell文件
47- try :
48- completed_process = subprocess .run (['bash' , '-c' , shell_command ], stdout = sys .stdout , stderr = subprocess .PIPE , text = True , check = True )
336+ def visit_project_page ():
337+ try :
338+ if not PROJECT_URL or not INTERVAL_SECONDS :
339+ global has_logged_empty_message
340+ if not has_logged_empty_message :
341+ print ("URL or TIME variable is empty, Skipping visit web" )
342+ has_logged_empty_message = True
343+ return
49344
50- print ( "App is running" )
51- print ( "Thank you for using this script,enjoy!" )
345+ response = requests . get ( PROJECT_URL )
346+ response . raise_for_status ()
52347
53- except subprocess .CalledProcessError as e :
54- print (f"Error: { e .returncode } " )
55- print ("Standard Output:" )
56- print (e .stdout )
57- print ("Standard Error:" )
58- print (e .stderr )
59- sys .exit (1 )
348+ # print(f"Visiting project page: {PROJECT_URL}")
349+ print ("Page visited successfully" )
350+ print ('\033 c' , end = '' )
351+ except requests .exceptions .RequestException as error :
352+ print (f"Error visiting project page: { error } " )
60353
61- server_thread .join ()
354+ if __name__ == "__main__" :
355+ while True :
356+ visit_project_page ()
357+ time .sleep (INTERVAL_SECONDS )
0 commit comments