15
changelog
15
changelog
@ -1,13 +1,18 @@
|
|||||||
### API-CLI
|
### API-CLI
|
||||||
1. Added get_hysteria2_port and get_hysteria2_sni in cli_api
|
1. Add: get_hysteria2_port and get_hysteria2_sni in cli_api
|
||||||
2. Chg: API function names
|
2. Chg: API function names
|
||||||
3. Restart hysteria-server.service after updating config file through API
|
3. Fix: Restart hysteria-server.service after updating config file through API
|
||||||
|
|
||||||
### WebPanel
|
### WebPanel
|
||||||
1. Add: Interact with users when config file restored in config.html
|
1. Add: Interact with users when config file restored in config.html
|
||||||
2. Added documents and logo
|
2. Add: documents and logo
|
||||||
3. Add: Change port & Fill sni domain field on loading DOM & Adopt previous
|
3. Add: Change port & Fill sni domain field on loading DOM & Adopt previous
|
||||||
|
4. Chg: Change Webpanel Port
|
||||||
|
5. Fix: blocked path
|
||||||
|
|
||||||
### Main
|
### Main
|
||||||
1. Restart Caddy service
|
1. Fix: Restart Caddy service
|
||||||
2. Improve Hysteria2 uninstallation script for complete removal
|
2. Fix: Improve Hysteria2 uninstallation script for complete removal
|
||||||
|
3. Fix: Skip missing IPs while generating URI and QR codes
|
||||||
|
4. Add: Use kernel for UUID generation and Removed uuid-runtime dependency
|
||||||
|
5. Add: Userinfo on normalsub
|
||||||
|
|||||||
@ -53,7 +53,7 @@ EOF
|
|||||||
|
|
||||||
echo "Generating passwords and UUID..."
|
echo "Generating passwords and UUID..."
|
||||||
obfspassword=$(pwgen -s 32 1)
|
obfspassword=$(pwgen -s 32 1)
|
||||||
UUID=$(uuidgen)
|
UUID=$(cat /proc/sys/kernel/random/uuid)
|
||||||
|
|
||||||
chown hysteria:hysteria /etc/hysteria/ca.key /etc/hysteria/ca.crt
|
chown hysteria:hysteria /etc/hysteria/ca.key /etc/hysteria/ca.crt
|
||||||
chmod 640 /etc/hysteria/ca.key /etc/hysteria/ca.crt
|
chmod 640 /etc/hysteria/ca.key /etc/hysteria/ca.crt
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import subprocess
|
|||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
|
import json # Import the json module
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
from aiohttp.web_middlewares import middleware
|
from aiohttp.web_middlewares import middleware
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
@ -62,11 +63,37 @@ async def handle(request):
|
|||||||
|
|
||||||
def get_user_uri(username):
|
def get_user_uri(username):
|
||||||
try:
|
try:
|
||||||
|
user_info_command = [
|
||||||
|
'python3',
|
||||||
|
'/etc/hysteria/core/cli.py',
|
||||||
|
'get-user',
|
||||||
|
'-u', username
|
||||||
|
]
|
||||||
|
safe_user_info_command = [shlex.quote(arg) for arg in user_info_command]
|
||||||
|
user_info_output = subprocess.check_output(safe_user_info_command).decode()
|
||||||
|
user_info = json.loads(user_info_output)
|
||||||
|
|
||||||
|
|
||||||
|
upload = user_info.get('upload_bytes', 0)
|
||||||
|
download = user_info.get('download_bytes', 0)
|
||||||
|
total = user_info.get('max_download_bytes', 0)
|
||||||
|
creation_date_str = user_info.get('account_creation_date', '')
|
||||||
|
expiration_days = user_info.get('expiration_days', 0)
|
||||||
|
if creation_date_str and expiration_days > 0:
|
||||||
|
try:
|
||||||
|
creation_date = time.strptime(creation_date_str, "%Y-%m-%d")
|
||||||
|
expiration_timestamp = int(time.mktime(creation_date)) + (expiration_days * 24 * 60 * 60)
|
||||||
|
except ValueError:
|
||||||
|
expiration_timestamp = 0
|
||||||
|
else:
|
||||||
|
expiration_timestamp = 0
|
||||||
|
|
||||||
|
# Get URI
|
||||||
command = [
|
command = [
|
||||||
'python3',
|
'python3',
|
||||||
'/etc/hysteria/core/cli.py',
|
'/etc/hysteria/core/cli.py',
|
||||||
'show-user-uri',
|
'show-user-uri',
|
||||||
'-u', username,
|
'-u', username,
|
||||||
'-a'
|
'-a'
|
||||||
]
|
]
|
||||||
safe_command = [shlex.quote(arg) for arg in command]
|
safe_command = [shlex.quote(arg) for arg in command]
|
||||||
@ -74,9 +101,20 @@ def get_user_uri(username):
|
|||||||
output = re.sub(r'IPv4:\s*', '', output)
|
output = re.sub(r'IPv4:\s*', '', output)
|
||||||
output = re.sub(r'IPv6:\s*', '', output)
|
output = re.sub(r'IPv6:\s*', '', output)
|
||||||
|
|
||||||
|
subscription_info = (
|
||||||
|
f"//subscription-userinfo: upload={upload}; download={download}; total={total}; expire={expiration_timestamp}\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
profile_lines = f"//profile-title: {username}-Hysteria2 🚀\n//profile-update-interval: 1\n"
|
||||||
|
output = profile_lines + subscription_info + output
|
||||||
|
|
||||||
return output
|
return output
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
raise RuntimeError("Failed to get URI.")
|
raise RuntimeError("Failed to get URI or user info.")
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
raise RuntimeError("Failed to parse user info JSON.")
|
||||||
|
except ValueError:
|
||||||
|
raise RuntimeError("expiration_timestamp OR account_creation_date in config file is invalid")
|
||||||
|
|
||||||
async def handle_404(request):
|
async def handle_404(request):
|
||||||
print(f"404 Not Found: {request.path}")
|
print(f"404 Not Found: {request.path}")
|
||||||
@ -84,7 +122,7 @@ async def handle_404(request):
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app = web.Application(middlewares=[rate_limit_middleware])
|
app = web.Application(middlewares=[rate_limit_middleware])
|
||||||
|
|
||||||
app.add_routes([web.get('/sub/normal/{username}', handle)])
|
app.add_routes([web.get('/sub/normal/{username}', handle)])
|
||||||
app.router.add_route('*', '/sub/normal/{tail:.*}', handle_404)
|
app.router.add_route('*', '/sub/normal/{tail:.*}', handle_404)
|
||||||
|
|
||||||
@ -92,5 +130,5 @@ if __name__ == '__main__':
|
|||||||
ssl_context.load_cert_chain(certfile=CERTFILE, keyfile=KEYFILE)
|
ssl_context.load_cert_chain(certfile=CERTFILE, keyfile=KEYFILE)
|
||||||
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
|
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
|
||||||
ssl_context.set_ciphers('AES256+EECDH:AES256+EDH')
|
ssl_context.set_ciphers('AES256+EECDH:AES256+EDH')
|
||||||
|
|
||||||
web.run_app(app, port=PORT, ssl_context=ssl_context)
|
web.run_app(app, port=PORT, ssl_context=ssl_context)
|
||||||
|
|||||||
@ -71,7 +71,7 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
config = Config()
|
config = Config()
|
||||||
config.debug = CONFIGS.DEBUG
|
config.debug = CONFIGS.DEBUG
|
||||||
config.bind = ['127.0.0.1:8080']
|
config.bind = ['127.0.0.1:28260']
|
||||||
config.accesslog = '-'
|
config.accesslog = '-'
|
||||||
config.errorlog = '-'
|
config.errorlog = '-'
|
||||||
|
|
||||||
|
|||||||
@ -83,14 +83,14 @@ $DOMAIN:$PORT {
|
|||||||
# We don't strip the ROOT_PATH('/$ROOT_PATH/') from the request
|
# We don't strip the ROOT_PATH('/$ROOT_PATH/') from the request
|
||||||
# uri strip_prefix /$ROOT_PATH
|
# uri strip_prefix /$ROOT_PATH
|
||||||
|
|
||||||
# We are proxying all requests under the ROOT_PATH to FastAPI at 127.0.0.1:8080
|
# We are proxying all requests under the ROOT_PATH to FastAPI at 127.0.0.1:28260
|
||||||
# FastAPI handles these requests because we set the 'root_path' parameter in the FastAPI instance.
|
# FastAPI handles these requests because we set the 'root_path' parameter in the FastAPI instance.
|
||||||
reverse_proxy http://127.0.0.1:8080
|
reverse_proxy http://127.0.0.1:28260
|
||||||
}
|
}
|
||||||
|
|
||||||
# Any request that doesn't start with the ROOT_PATH('/$ROOT_PATH/') will be blocked and no response will be sent to the client
|
# Any request that doesn't start with the ROOT_PATH('/$ROOT_PATH/') will be blocked and no response will be sent to the client
|
||||||
@blocked {
|
@blocked {
|
||||||
not path /fd31b4edc70619d5d39edf3c2da97e2c/*
|
not path /$ROOT_PATH/*
|
||||||
}
|
}
|
||||||
|
|
||||||
# Abort the request, effectively dropping the connection without a response for invalid paths
|
# Abort the request, effectively dropping the connection without a response for invalid paths
|
||||||
@ -181,7 +181,7 @@ start_service() {
|
|||||||
|
|
||||||
# Check if the web panel is running
|
# Check if the web panel is running
|
||||||
if systemctl is-active --quiet hysteria-webpanel.service; then
|
if systemctl is-active --quiet hysteria-webpanel.service; then
|
||||||
echo -e "${green}Hysteria web panel setup completed. The web panel is running locally on: http://127.0.0.1:8080/${NC}"
|
echo -e "${green}Hysteria web panel setup completed. The web panel is running locally on: http://127.0.0.1:28260/${NC}"
|
||||||
else
|
else
|
||||||
echo -e "${red}Error: Hysteria web panel service failed to start.${NC}"
|
echo -e "${red}Error: Hysteria web panel service failed to start.${NC}"
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
@ -31,7 +31,7 @@ fi
|
|||||||
|
|
||||||
check_os_version
|
check_os_version
|
||||||
|
|
||||||
REQUIRED_PACKAGES=("jq" "qrencode" "curl" "pwgen" "uuid-runtime" "python3" "python3-pip" "python3-venv" "git" "bc" "zip" "cron" "lsof")
|
REQUIRED_PACKAGES=("jq" "qrencode" "curl" "pwgen" "python3" "python3-pip" "python3-venv" "git" "bc" "zip" "cron" "lsof")
|
||||||
MISSING_PACKAGES=()
|
MISSING_PACKAGES=()
|
||||||
heavy_checkmark=$(printf "\xE2\x9C\x85")
|
heavy_checkmark=$(printf "\xE2\x9C\x85")
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ FILES=(
|
|||||||
"/etc/hysteria/core/scripts/singbox/.env"
|
"/etc/hysteria/core/scripts/singbox/.env"
|
||||||
"/etc/hysteria/core/scripts/normalsub/.env"
|
"/etc/hysteria/core/scripts/normalsub/.env"
|
||||||
"/etc/hysteria/core/scripts/webpanel/.env"
|
"/etc/hysteria/core/scripts/webpanel/.env"
|
||||||
|
"/etc/hysteria/core/scripts/webpanel/Caddyfile"
|
||||||
)
|
)
|
||||||
|
|
||||||
echo "Backing up and stopping all cron jobs"
|
echo "Backing up and stopping all cron jobs"
|
||||||
|
|||||||
Reference in New Issue
Block a user