diff --git a/changelog b/changelog
index 029b0ed..a1270a5 100644
--- a/changelog
+++ b/changelog
@@ -1,3 +1,3 @@
-🚀 feat: Improve Hysteria2 IP detection with fallbacks
-
-⚙️ Enhance: SNI entry handling in CONFIG_ENV file
+🚀 feat: Add version check and notification new releases
+🛠️ refactor: Improve utils package structure with cleaner imports
+🐛 fix: IPv6 validation pattern
diff --git a/core/cli.py b/core/cli.py
index 779e3a6..5679ea0 100644
--- a/core/cli.py
+++ b/core/cli.py
@@ -485,6 +485,31 @@ def get_services_status():
click.echo('Error: Services status not available.')
except Exception as e:
click.echo(f'{e}', err=True)
+
+
+@cli.command('show-version')
+def show_version():
+ """Displays the currently installed version of the panel."""
+ try:
+ if version_info := cli_api.show_version():
+ click.echo(version_info)
+ else:
+ click.echo("Error retrieving version")
+ except Exception as e:
+ click.echo(f"An unexpected error occurred: {e}", err=True)
+
+
+@cli.command('check-version')
+def check_version():
+ """Checks if the current version is up-to-date and displays changelog if not."""
+ try:
+ if version_info := cli_api.check_version():
+ click.echo(version_info)
+ else:
+ click.echo("Error retrieving version")
+ except Exception as e:
+ click.echo(f"An unexpected error occurred: {e}", err=True)
+
# endregion
diff --git a/core/cli_api.py b/core/cli_api.py
index d602d17..ddf0d66 100644
--- a/core/cli_api.py
+++ b/core/cli_api.py
@@ -47,6 +47,7 @@ class Command(Enum):
CONFIGURE_WARP = os.path.join(SCRIPT_DIR, 'warp', 'configure.sh')
STATUS_WARP = os.path.join(SCRIPT_DIR, 'warp', 'status.sh')
SERVICES_STATUS = os.path.join(SCRIPT_DIR, 'services_status.sh')
+ VERSION = os.path.join(SCRIPT_DIR, 'hysteria2', 'version.py')
# region Custom Exceptions
@@ -500,5 +501,15 @@ def get_services_status() -> dict[str, bool] | None:
'''Gets the status of all project services.'''
if res := run_cmd(['bash', Command.SERVICES_STATUS.value]):
return json.loads(res)
+
+def show_version() -> str | None:
+ """Displays the currently installed version of the panel."""
+ return run_cmd(['python3', Command.VERSION.value, 'show-version'])
+
+
+def check_version() -> str | None:
+ """Checks if the current version is up-to-date and displays changelog if not."""
+ return run_cmd(['python3', Command.VERSION.value, 'check-version'])
+
# endregion
# endregion
diff --git a/core/scripts/hysteria2/version.py b/core/scripts/hysteria2/version.py
new file mode 100644
index 0000000..dafe699
--- /dev/null
+++ b/core/scripts/hysteria2/version.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python3
+
+import os
+import sys
+import requests
+from pathlib import Path
+
+LOCALVERSION = "/etc/hysteria/VERSION"
+LATESTVERSION = "https://raw.githubusercontent.com/ReturnFI/Hysteria2/main/VERSION"
+LASTESTCHANGE = "https://raw.githubusercontent.com/ReturnFI/Hysteria2/main/changelog"
+
+def version_greater_equal(version1, version2):
+ version1_parts = [int(part) for part in version1.strip().split('.')]
+ version2_parts = [int(part) for part in version2.strip().split('.')]
+
+ max_length = max(len(version1_parts), len(version2_parts))
+ version1_parts.extend([0] * (max_length - len(version1_parts)))
+ version2_parts.extend([0] * (max_length - len(version2_parts)))
+
+ for i in range(max_length):
+ if version1_parts[i] > version2_parts[i]:
+ return True
+ elif version1_parts[i] < version2_parts[i]:
+ return False
+
+ # If we get here, they're equal
+ return True
+
+def check_version():
+ try:
+ with open(LOCALVERSION, 'r') as f:
+ local_version = f.read().strip()
+
+ latest_version = requests.get(LATESTVERSION).text.strip()
+ latest_changelog = requests.get(LASTESTCHANGE).text
+
+ print(f"Panel Version: {local_version}")
+
+ if not version_greater_equal(local_version, latest_version):
+ print(f"Latest Version: {latest_version}")
+ print(f"{latest_version} Version Change Log:")
+ print(latest_changelog)
+ except Exception as e:
+ print(f"Error checking version: {e}", file=sys.stderr)
+ sys.exit(1)
+
+def show_version():
+ try:
+ with open(LOCALVERSION, 'r') as f:
+ local_version = f.read().strip()
+
+ print(f"Panel Version: {local_version}")
+ except Exception as e:
+ print(f"Error showing version: {e}", file=sys.stderr)
+ sys.exit(1)
+
+def main():
+ if len(sys.argv) != 2:
+ print(f"Usage: {sys.argv[0]} [check-version|show-version]", file=sys.stderr)
+ sys.exit(1)
+
+ command = sys.argv[1]
+
+ if command == "check-version":
+ check_version()
+ elif command == "show-version":
+ show_version()
+ else:
+ print(f"Usage: {sys.argv[0]} [check-version|show-version]", file=sys.stderr)
+ sys.exit(1)
+
+ sys.exit(0)
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/core/scripts/telegrambot/tbot.py b/core/scripts/telegrambot/tbot.py
index 6c06904..a61bec1 100644
--- a/core/scripts/telegrambot/tbot.py
+++ b/core/scripts/telegrambot/tbot.py
@@ -1,13 +1,5 @@
from telebot import types
-from utils.common import create_main_markup
-from utils.adduser import *
-from utils.backup import *
-from utils.command import *
-from utils.deleteuser import *
-from utils.edituser import *
-from utils.search import *
-from utils.serverinfo import *
-from utils.cpu import *
+from utils import *
import threading
import time
@@ -27,4 +19,6 @@ def monitoring_thread():
if __name__ == '__main__':
monitor_thread = threading.Thread(target=monitoring_thread, daemon=True)
monitor_thread.start()
+ version_thread = threading.Thread(target=version_monitoring, daemon=True)
+ version_thread.start()
bot.polling(none_stop=True)
diff --git a/core/scripts/telegrambot/utils/__init__.py b/core/scripts/telegrambot/utils/__init__.py
index e69de29..49f6c6c 100644
--- a/core/scripts/telegrambot/utils/__init__.py
+++ b/core/scripts/telegrambot/utils/__init__.py
@@ -0,0 +1,10 @@
+from .common import *
+from .adduser import *
+from .backup import *
+from .command import *
+from .deleteuser import *
+from .edituser import *
+from .search import *
+from .serverinfo import *
+from .cpu import *
+from .check_version import *
diff --git a/core/scripts/telegrambot/utils/check_version.py b/core/scripts/telegrambot/utils/check_version.py
new file mode 100644
index 0000000..58fb4e0
--- /dev/null
+++ b/core/scripts/telegrambot/utils/check_version.py
@@ -0,0 +1,34 @@
+import telebot
+import subprocess
+import shlex
+import time
+import re
+from utils.command import *
+
+def check_version():
+ command = f"python3 {CLI_PATH} check-version"
+ try:
+ args = shlex.split(command)
+ result = subprocess.check_output(args, stderr=subprocess.STDOUT).decode("utf-8").strip()
+ panel_version = re.search(r'Panel Version: (\d+\.\d+\.\d+)', result)
+ latest_version = re.search(r'Latest Version: (\d+\.\d+\.\d+)', result)
+
+ if panel_version and latest_version and panel_version.group(1) != latest_version.group(1):
+ notify_admins(f"🔔 New version available!\n\n{result}")
+
+ except subprocess.CalledProcessError as e:
+ error_message = f"Error checking version: {e.output.decode('utf-8')}"
+ print(f"Error checking version: {e.output.decode('utf-8')}")
+ notify_admins(error_message)
+
+def notify_admins(message):
+ for admin_id in ADMIN_USER_IDS:
+ try:
+ bot.send_message(admin_id, message)
+ except Exception as e:
+ print(f"Failed to notify admin {admin_id}: {str(e)}")
+
+def version_monitoring():
+ while True:
+ check_version()
+ time.sleep(86400)
\ No newline at end of file
diff --git a/core/scripts/webpanel/routers/api/v1/schema/server.py b/core/scripts/webpanel/routers/api/v1/schema/server.py
index 9372c52..393f0bc 100644
--- a/core/scripts/webpanel/routers/api/v1/schema/server.py
+++ b/core/scripts/webpanel/routers/api/v1/schema/server.py
@@ -23,3 +23,13 @@ class ServerServicesStatusResponse(BaseModel):
hysteria_normal_sub: bool
hysteria_telegram_bot: bool
hysteria_warp: bool
+
+class VersionInfoResponse(BaseModel):
+ current_version: str
+
+
+class VersionCheckResponse(BaseModel):
+ is_latest: bool
+ current_version: str
+ latest_version: str
+ changelog: str
\ No newline at end of file
diff --git a/core/scripts/webpanel/routers/api/v1/server.py b/core/scripts/webpanel/routers/api/v1/server.py
index 2ef99b0..5639d9b 100644
--- a/core/scripts/webpanel/routers/api/v1/server.py
+++ b/core/scripts/webpanel/routers/api/v1/server.py
@@ -1,6 +1,6 @@
from fastapi import APIRouter, HTTPException
import cli_api
-from .schema.server import ServerStatusResponse, ServerServicesStatusResponse
+from .schema.server import ServerStatusResponse, ServerServicesStatusResponse, VersionCheckResponse, VersionInfoResponse
router = APIRouter()
@@ -144,3 +144,40 @@ def __parse_services_status(services_status: dict[str, bool]) -> ServerServicesS
elif 'wg-quick' in service:
parsed_services_status['hysteria_warp'] = status
return ServerServicesStatusResponse(**parsed_services_status)
+
+@router.get('/version', response_model=VersionInfoResponse)
+async def get_version_info():
+ """Retrieves the current version of the panel."""
+ try:
+ version_output = cli_api.show_version()
+ if version_output:
+ current_version = version_output.split(": ")[1].strip()
+ return VersionInfoResponse(current_version=current_version)
+ raise HTTPException(status_code=404, detail="Version information not found")
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e))
+
+
+@router.get('/version/check', response_model=VersionCheckResponse)
+async def check_version_info():
+ """Checks for updates and retrieves version information."""
+ try:
+ check_output = cli_api.check_version()
+ if check_output:
+ lines = check_output.splitlines()
+ current_version = lines[0].split(": ")[1].strip()
+
+ if len(lines) > 1 and "Latest Version" in lines[1]:
+ latest_version = lines[1].split(": ")[1].strip()
+ is_latest = current_version == latest_version
+ changelog_start_index = 3
+ changelog = "\n".join(lines[changelog_start_index:]).strip()
+ return VersionCheckResponse(is_latest=is_latest, current_version=current_version,
+ latest_version=latest_version, changelog=changelog)
+ else:
+ return VersionCheckResponse(is_latest=True, current_version=current_version)
+
+ raise HTTPException(status_code=404, detail="Version information not found")
+
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e))
\ No newline at end of file
diff --git a/core/scripts/webpanel/templates/base.html b/core/scripts/webpanel/templates/base.html
index f327d32..273531b 100644
--- a/core/scripts/webpanel/templates/base.html
+++ b/core/scripts/webpanel/templates/base.html
@@ -15,6 +15,8 @@
+
+
{% block stylesheets %}{% endblock %}
@@ -114,11 +116,28 @@
{% block content %}{% endblock %}
-
-
+
+
@@ -127,16 +146,18 @@
+
+
{% block javascripts %}{% endblock %}