Skip to content

Commit e0a6b9c

Browse files
authored
Update app.py
1 parent 9169ced commit e0a6b9c

File tree

1 file changed

+317
-21
lines changed

1 file changed

+317
-21
lines changed

app.py

Lines changed: 317 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,52 @@
1-
import sys
21
import os
2+
import re
3+
import shutil
34
import subprocess
45
import http.server
56
import socketserver
67
import 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
1250
class 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)
3978
server_thread = threading.Thread(target=httpd.serve_forever)
4079
server_thread.daemon = True
4180
server_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('\033c', 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('\033c', 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

Comments
 (0)