#!/bin/bash SCRIPT_VERSION="2.3.1c" UPDATE_AVAILABLE=false DIR_REMNAWAVE="/usr/local/remnawave_reverse/" LANG_FILE="${DIR_REMNAWAVE}selected_language" SCRIPT_URL="https://raw.githubusercontent.com/eGamesAPI/remnawave-reverse-proxy/refs/heads/main/install_remnawave.sh" COLOR_RESET="\033[0m" COLOR_GREEN="\033[1;32m" COLOR_YELLOW="\033[1;33m" COLOR_WHITE="\033[1;37m" COLOR_RED="\033[1;31m" COLOR_GRAY='\033[0;90m' load_language() { if [ -f "$LANG_FILE" ]; then local saved_lang=$(cat "$LANG_FILE") case $saved_lang in 1) set_language en ;; 2) set_language ru ;; *) rm -f "$LANG_FILE" return 1 ;; esac return 0 fi return 1 } # Language variables declare -A LANG=( [CHOOSE_LANG]="Select language:" [LANG_EN]="English" [LANG_RU]="Русский" ) show_language() { echo -e "${COLOR_GREEN}${LANG[CHOOSE_LANG]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}1. ${LANG[LANG_EN]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}2. ${LANG[LANG_RU]}${COLOR_RESET}" echo -e "" } set_language() { case $1 in en) LANG=( #Alias [ALIAS_ADDED]="Alias 'rr' for 'remnawave_reverse' added to %s" [ALIAS_ACTIVATE_GLOBAL]="Alias 'rr' is now available for all users. Run 'source %s' or open a new terminal to apply." #Lang [CHOOSE_LANG]="Select language:" [LANG_EN]="English" [LANG_RU]="Русский" #check [ERROR_ROOT]="Script must be run as root" [ERROR_OS]="Supported only Debian 11/12 and Ubuntu 22.04/24.04" #Install Packages [ERROR_UPDATE_LIST]="Failed to update package list" [ERROR_INSTALL_PACKAGES]="Failed to install required packages" [ERROR_INSTALL_CRON]="Failed to install cron" [ERROR_START_CRON]="Failed to start cron" [ERROR_CONFIGURE_LOCALES]="Error: Failed to configure locales" [ERROR_DOWNLOAD_DOCKER_KEY]="Failed to download Docker GPG key" [ERROR_UPDATE_DOCKER_LIST]="Failed to update package list after adding Docker repository" [ERROR_INSTALL_DOCKER]="Failed to install Docker" [ERROR_DOCKER_NOT_INSTALLED]="Docker is not installed" [ERROR_START_DOCKER]="Failed to start Docker" [ERROR_ENABLE_DOCKER]="Failed to enable Docker auto-start" [ERROR_DOCKER_NOT_WORKING]="Docker is not working properly" [ERROR_CONFIGURE_UFW]="Failed to configure UFW" [ERROR_CONFIGURE_UPGRADES]="Failed to configure unattended-upgrades" [ERROR_DOCKER_DNS]="Error: Unable to resolve download.docker.com. Check your DNS settings." [ERROR_INSTALL_CERTBOT]="Error: Failed to install certbot" [SUCCESS_INSTALL]="All packages installed successfully" #Menu [MENU_TITLE]="REMNAWAVE REVERSE-PROXY by eGames" [AVAILABLE_UPDATE]="update available" [VERSION_LABEL]="Version: %s" [EXIT]="Exit" [MENU_1]="Install Remnawave Components" [MENU_2]="Reinstall panel/node" [MENU_3]="Manage Panel/Node" [MENU_4]="Install random template for selfsteal node" [MENU_5]="Custom subscriptions by legiz" [MENU_6]="Extensions by distillium" [MENU_7]="Manage IPv6" [MENU_8]="Manage certificates domain" [MENU_9]="Check for updates script" [MENU_10]="Remove script" [PROMPT_ACTION]="Select action (0-10):" [INVALID_CHOICE]="Invalid choice. Please select 0-10" [WARNING_LABEL]="WARNING:" [CONFIRM_PROMPT]="Enter 'y' to continue or 'n' to exit (y/n):" [WARNING_NODE_PANEL]="Adding a node should only be done on the server where the panel is installed, not on the node server." [CONFIRM_SERVER_PANEL]="Are you sure you are on the server with the installed panel?" #Remove Script [REMOVE_SCRIPT_ONLY]="Remove script and its local files" [REMOVE_SCRIPT_AND_PANEL]="Remove script and remnawave panel/node data" [CONFIRM_REMOVE_SCRIPT]="All script data will be removed from the server. Are you sure? (y/n): " [CONFIRM_REMOVE_ALL]="All script and panel/node data will be removed from the server. Are you sure? (y/n): " [SCRIPT_REMOVED]="Script and its local files successfully removed!" [ALL_REMOVED]="Script and panel/node data successfully removed!" #Extensions by distillium [EXTENSIONS_MENU]="Extensions by distillium" [EXTENSIONS_MENU_TITLE]="Manage Extensions by distillium" [EXTENSIONS_PROMPT]="Select action (0-2):" [EXTENSIONS_INVALID_CHOICE]="Invalid choice. Please select 0-2." [BACKUP_RESTORE]="Backup and Restore" #Warp by distillium [WARP_MENU]="WARP Native" [WARP_MENU_TITLE]="Manage WARP Native" [WARP_INSTALL]="Install WARP Native" [WARP_ADD_CONFIG]="Add WARP-configuration to node configuration" [WARP_DELETE_WARP_SETTINGS]="Remove WARP-configuration from node configuration" [WARP_CONFIRM_SERVER_PANEL]="Are you sure you are on the server with the installed panel?\nAdding WARP-configuration should only be done on the server where the panel is installed, not on the node server" [WARP_UNINSTALL]="Uninstall WARP Native" [WARP_PROMPT]="Select action (0-4):" [WARP_PROMPT1]="Select action:" [WARP_INVALID_CHOICE]="Invalid choice. Please select 0-4." [WARP_INVALID_CHOICE2]="Invalid choice." [WARP_NO_NODE]="Node Remnawave not found. First install the node." [WARP_SELECT_CONFIG]="Select node to add WARP-configuration?" [WARP_SELECT_CONFIG_DELETE]="Select node to remove WARP-configuration?" [WARP_NO_CONFIGS]="No configurations found." [WARP_UPDATE_SUCCESS]="Configuration successfully updated!" [WARP_UPDATE_FAIL]="Failed to update configuration." [WARP_WARNING]="warp-out already exists in outbounds." [WARP_WARNING2]="warp rule already exists in routing rules." [WARP_REMOVED_WARP_SETTINGS1]="Removed warp-out from outbounds" [WARP_NO_WARP_SETTINGS1]="warp-out not found in outbounds" [WARP_REMOVED_WARP_SETTINGS2]="Removed warp rule from routing rules" [WARP_NO_WARP_SETTINGS2]="warp rule not found in routing rules" #Manage Panel/Node [START_PANEL_NODE]="Start panel/node" [STOP_PANEL_NODE]="Stop panel/node" [UPDATE_PANEL_NODE]="Update panel/node" [VIEW_LOGS]="View logs" [PRESS_ENTER_RETURN_MENU]="Press Enter to return to the menu..." [REMNAWAVE_CLI]="Remnawave CLI" [ACCESS_PANEL]="Access panel via port 8443 (Only for panel + node)" [MANAGE_PANEL_NODE_PROMPT]="Select action (0-6):" [MANAGE_PANEL_NODE_INVALID_CHOICE]="Invalid choice. Please select 0-6." #Manage Certificates [CERT_UPDATE]="Update current certificates" [CERT_GENERATE]="Generate new certificates for another domain" [CERT_PROMPT1]="Select action (0-2):" [CERT_INVALID_CHOICE]="Invalid choice. Please select 0-2." [CERT_UPDATE_CHECK]="Checking certificate generation method..." [CERT_UPDATE_SUCCESS]="Certificates successfully updated." [CERT_UPDATE_FAIL]="Failed to update certificates." [CERT_GENERATE_PROMPT]="Enter the domain for new certificates (e.g., example.com):" [CERT_METHOD_UNKNOWN]="Unknown certificate generation method." [CERT_NOT_DUE]="Certificate for %s is not yet due for renewal." #Install Remnawave Components [INSTALL_MENU_TITLE]="Install Remnawave Components" [INSTALL_PANEL_NODE]="Install panel and node on one server" [INSTALL_PANEL]="Install only the panel" [INSTALL_ADD_NODE]="Add node to panel" [INSTALL_NODE]="Install only the node" [INSTALL_PROMPT]="Select action (0-3):" [INSTALL_INVALID_CHOICE]="Invalid choice. Please select 0-3." #Manage IPv6 [IPV6_MENU_TITLE]="Manage IPv6" [IPV6_ENABLE]="Enable IPv6" [IPV6_DISABLE]="Disable IPv6" [IPV6_PROMPT]="Select action (0-2):" [IPV6_INVALID_CHOICE]="Invalid choice. Please select 0-2." [IPV6_ALREADY_ENABLED]="IPv6 already enabled" [IPV6_ALREADY_DISABLED]="IPv6 already disabled" [ENABLE_IPV6]="Enabling IPv6..." [IPV6_ENABLED]="IPv6 has been enabled." [DISABLING_IPV6]="Disabling IPv6..." [IPV6_DISABLED]="IPv6 has been disabled." #Remna [INSTALL_PACKAGES]="Installing required packages..." [INSTALLING]="Installing panel and node" [INSTALLING_PANEL]="Installing panel" [INSTALLING_NODE]="Installing node" [ENTER_PANEL_DOMAIN]="Enter panel domain (e.g. panel.example.com):" [ENTER_SUB_DOMAIN]="Enter subscription domain (e.g. sub.example.com):" [ENTER_NODE_DOMAIN]="Enter selfsteal domain for node (e.g. node.example.com):" [ENTER_CF_TOKEN]="Enter your Cloudflare API token or global API key:" [ENTER_CF_EMAIL]="Enter your Cloudflare registered email:" [ENTER_GCORE_TOKEN]="Enter Gcore API token:" [CERT_GCORE_FILE_NOT_FOUND]="Gcore credentials file not found. Please re-enter token." [CHECK_CERTS]="Checking certificates..." [CERT_FOUND]="Certificates found in /etc/letsencrypt/live/" [CF_VALIDATING]="Cloudflare API key and email are valid" [CF_INVALID]="Invalid Cloudflare API token or email after %d attempts." [CF_INVALID_ATTEMPT]="Invalid Cloudflare API key or email. Attempt %d of %d." [WAITING]="Please wait..." #API [REGISTERING_REMNAWAVE]="Registration in Remnawave" [CHECK_CONTAINERS]="Checking containers availability..." [CONTAINERS_NOT_READY_ATTEMPT]="Containers are not ready, waiting... Attempt %d of %d." [CONTAINERS_TIMEOUT]="Containers not ready after %d attempts.\n\nCheck logs:\n cd /opt/remnawave && docker compose logs -f\n\nAlso check typical Docker issues:\n https://wiki.egam.es/troubleshooting/docker-issues/" [REGISTRATION_SUCCESS]="Registration completed successfully!" [GET_PUBLIC_KEY]="Getting public key..." [PUBLIC_KEY_SUCCESS]="Public key successfully obtained" [GENERATE_KEYS]="Generating x25519 keys..." [GENERATE_KEYS_SUCCESS]="Keys successfully generated" [ERROR_NO_CONFIGS]="No config profiles found" [NO_DEFAULT_PROFILE]="Default-Profile not found" [ERROR_DELETE_PROFILE]="Failed to delete profile" [CREATING_CONFIG_PROFILE]="Creating config profile..." [CONFIG_PROFILE_CREATED]="Config profile successfully created" [CREATING_NODE]="Creating node" [NODE_CREATED]="Node successfully created" [CREATE_HOST]="Creating host" [HOST_CREATED]="Host successfully created" [GET_DEFAULT_SQUAD]="Getting default squad" [UPDATE_SQUAD]="Squad successfully updated" [NO_SQUADS_FOUND]="No squads found" [INVALID_UUID_FORMAT]="Invalid UUID format" [NO_VALID_SQUADS_FOUND]="No valid squads found" [ERROR_GET_SQUAD]="Failed to get squad" [INVALID_SQUAD_UUID]="Invalid squad UUID" [INVALID_INBOUND_UUID]="Invalid inbound UUID" #Stop/Start/Update [CHANGE_DIR_FAILED]="Failed to change to directory %s" [DIR_NOT_FOUND]="Directory /opt/remnawave not found" [PANEL_RUNNING]="Panel/node already running" [PANEL_RUN]="Panel/node running" [PANEL_STOP]="Panel/node stopped" [PANEL_STOPPED]="Panel/node already stopped" [NO_UPDATE]="No updates available for panel/node" [UPDATING]="Updating panel/node..." [UPDATE_SUCCESS1]="Panel/node successfully updated" [STARTING_PANEL_NODE]="Starting panel and node" [STARTING_PANEL]="Starting panel" [STARTING_NODE]="Starting node" [STOPPING_REMNAWAVE]="Stopping panel and node" [IMAGES_DETECTED]="Images detected, restarting containers..." #Menu End [INSTALL_COMPLETE]=" INSTALLATION COMPLETE!" [PANEL_ACCESS]="Panel URL:" [ADMIN_CREDS]="To log into the panel, use the following data:" [USERNAME]="Username:" [PASSWORD]="Password:" [RELAUNCH_CMD]="To relaunch the manager, use the following command:" #RandomHTML [RANDOM_TEMPLATE]="Installing random template for camouflage site" [DOWNLOAD_FAIL]="Download failed, retrying..." [UNPACK_ERROR]="Error unpacking archive" [TEMPLATE_COPY]="Template copied to /var/www/html/" [SELECT_TEMPLATE]="Selected template:" #Error [ERROR_TOKEN]="Failed to get token." [ERROR_PUBLIC_KEY]="Failed to get public key." [ERROR_EXTRACT_PUBLIC_KEY]="Failed to extract public key from response." [ERROR_GENERATE_KEYS]="Failed to generate keys." [ERROR_EMPTY_RESPONSE_NODE]="Empty response from server when creating node." [ERROR_CREATE_NODE]="Failed to create node." [ERROR_EMPTY_RESPONSE_HOST]="Empty response from server when creating host." [ERROR_CREATE_HOST]="Failed to create host." [ERROR_EMPTY_RESPONSE_REGISTER]="Registration error - empty server response" [ERROR_REGISTER]="Registration error" [ERROR_UPDATE_SQUAD]="Failed to update squad" [ERROR_GET_SQUAD_LIST]="Failed to get squad list" [NO_SQUADS_TO_UPDATE]="No squads to update" #Reinstall Panel/Node [REINSTALL_WARNING]="All data panel/node will be deleted from the server. Are you sure? (y/n):" [REINSTALL_TYPE_TITLE]="Select reinstallation method:" [REINSTALL_PROMPT]="Select action (0-3):" [INVALID_REINSTALL_CHOICE]="Invalid choice. Please select 0-3." [POST_PANEL_MESSAGE]="Panel successfully installed!" [POST_PANEL_INSTRUCTION]="To install the node, follow these steps:\n1. Run this script on the server where the node will be installed.\n2. Select 'Install Remnawave Components', then 'Install only the node'." [SELFSTEAL_PROMPT]="Enter the selfsteal domain for the node (e.g. node.example.com):" [SELFSTEAL]="Enter the selfsteal domain for the node specified during panel installation:" [PANEL_IP_PROMPT]="Enter the IP address of the panel to establish a connection between the panel and the node:" [IP_ERROR]="Enter a valid IP address in the format X.X.X.X (e.g., 192.168.1.1)" [CERT_PROMPT]="Enter the certificate obtained from the panel (paste the content and press Enter twice):" [CERT_CONFIRM]="Are you sure the certificate is correct? (y/n):" [ABORT_MESSAGE]="Installation aborted by user" [SUCCESS_MESSAGE]="Node successfully connected" #Node Check [NODE_CHECK]="Checking node connection for %s..." [NODE_ATTEMPT]="Attempt %d of %d..." [NODE_UNAVAILABLE]="Node is unavailable on attempt %d." [NODE_LAUNCHED]="Node successfully launched!" [NODE_NOT_CONNECTED]="Node not connected after %d attempts!" [CHECK_CONFIG]="Check the configuration or restart the panel." #Add node to panel [ADD_NODE_TO_PANEL]="Adding node to panel" [ENTER_NODE_NAME]="Enter node name (e.g., Germany):" [USING_SAVED_TOKEN]="Using saved token..." [INVALID_SAVED_TOKEN]="Saved token is invalid. Requesting a new one..." [ENTER_PANEL_USERNAME]="Enter panel username: " [ENTER_PANEL_PASSWORD]="Enter panel password: " [TOKEN_RECEIVED_AND_SAVED]="Token successfully received and saved" [TOKEN_USED_SUCCESSFULLY]="Token successfully used" [DOMAIN_ALREADY_EXISTS]="Domain already exists" [TRY_ANOTHER_DOMAIN]="Please use another domain" [ERROR_CHECK_DOMAIN]="Error checking domain" [NODE_ADDED_SUCCESS]="Node successfully added!" [CREATE_NEW_NODE]="Creating new node for %s..." [CF_INVALID_NAME]="Error: The name of the configuration profile %s is already in use.\nPlease choose another name." [CF_INVALID_LENGTH]="Error: The name of the configuration profile should contain from 3 to 20 characters." [CF_INVALID_CHARS]="Error: The name of the configuration profile should contain only English letters, numbers, and hyphens." #check [CHECK_UPDATE]="Check for updates" [GENERATING_CERTS]="Generating certificates for %s" [REQUIRED_DOMAINS]="Required domains for certificates:" [CHECK_DOMAIN_IP_FAIL]="Failed to determine the domain or server IP address." [CHECK_DOMAIN_IP_FAIL_INSTRUCTION]="Ensure that the domain %s is correctly configured and points to this server (%s)." [CHECK_DOMAIN_CLOUDFLARE]="The domain %s points to a Cloudflare IP (%s)." [CHECK_DOMAIN_CLOUDFLARE_INSTRUCTION]="Cloudflare proxying is not allowed for the selfsteal domain. Disable proxying (switch to 'DNS Only')." [CHECK_DOMAIN_MISMATCH]="The domain %s points to IP address %s, which differs from this server's IP (%s)." [CHECK_DOMAIN_MISMATCH_INSTRUCTION]="For proper operation, the domain must point to the current server." [NO_PANEL_NODE_INSTALLED]="Panel or node is not installed. Please install panel or node first." #update [UPDATE_AVAILABLE]="A new version of the script is available: %s (current version: %s)." [UPDATE_CONFIRM]="Update the script? (y/n):" [UPDATE_CANCELLED]="Update cancelled by user." [UPDATE_SUCCESS]="Script successfully updated to version %s!" [UPDATE_FAILED]="Error downloading the new version of the script." [VERSION_CHECK_FAILED]="Could not determine the version of the remote script. Skipping update." [LATEST_VERSION]="You already have the latest version of the script (%s)." [RESTART_REQUIRED]="Please restart the script to apply changes." [LOCAL_FILE_NOT_FOUND]="Local script file not found, downloading new version..." #CLI [RUNNING_CLI]="Running Remnawave CLI..." [CLI_SUCCESS]="Remnawave CLI executed successfully!" [CLI_FAILED]="Failed to execute Remnawave CLI. Ensure the 'remnawave' container is running." [CONTAINER_NOT_RUNNING]="Container 'remnawave' is not running. Please start it first." #Cert_choise [CERT_METHOD_PROMPT]="Select certificate generation method for all domains:" [CERT_METHOD_CF]="Cloudflare API (supports wildcard)" [CERT_METHOD_ACME]="ACME HTTP-01 (single domain, no wildcard)" [CERT_METHOD_GCORE]="Gcore DNS API (supports wildcard)" [ERROR_INSTALL_GCORE_PLUGIN]="Failed to install certbot-dns-gcore plugin" [CERT_METHOD_CHOOSE]="Select action (0-3):" [EMAIL_PROMPT]="Enter your email for Let's Encrypt registration:" [CERTS_SKIPPED]="All certificates already exist. Skipping generation." [ACME_METHOD]="Using ACME (Let's Encrypt) with HTTP-01 challenge (no wildcard support)..." [CERT_GENERATION_FAILED]="Certificate generation failed. Please check your input and DNS settings." [ADDING_CRON_FOR_EXISTING_CERTS]="Adding cron job for certificate renewal..." [CRON_ALREADY_EXISTS]="Cron job for certificate renewal already exists." [CERT_NOT_FOUND]="Certificate not found for domain." [ERROR_PARSING_CERT]="Error parsing certificate expiry date." [CERT_EXPIRY_SOON]="Certificates will expire soon in" [DAYS]="days" [UPDATING_CRON]="Updating cron job to match certificate expiry." [GENERATING_WILDCARD_CERT]="Generating wildcard certificate for" [WILDCARD_CERT_FOUND]="Wildcard certificate found in /etc/letsencrypt/live/" [FOR_DOMAIN]="for" [START_CRON_ERROR]="Not able to start cron. Please start it manually." [DOMAINS_MUST_BE_UNIQUE]="Error: All domains (panel, subscription, and node) must be unique." [CHOOSE_TEMPLATE_SOURCE]="Select template source:" [SIMPLE_WEB_TEMPLATES]="Simple web templates" [SNI_TEMPLATES]="Sni templates" [NOTHING_TEMPLATES]="Nothing Sni templates" [CHOOSE_TEMPLATE_OPTION]="Select action (0-3):" [INVALID_TEMPLATE_CHOICE]="Invalid choice. Please select 0-3." # Manage Panel Access [PORT_8443_OPEN]="Open port 8443 for panel access" [PORT_8443_CLOSE]="Close port 8443 for panel access" [PORT_8443_IN_USE]="Port 8443 already in use by another process. Check which services are using the port and free it." [NO_PORT_CHECK_TOOLS]="Port checking tools (ss or netstat) not found. Install one of them." [OPEN_PANEL_LINK]="Your panel access link:" [PORT_8443_WARNING]="Don't forget, port 8443 is now open to the world. After fixing the panel, select the option to close port 8443." [PORT_8443_CLOSED]="Port 8443 has been closed." [NGINX_CONF_NOT_FOUND]="File nginx.conf not found in $dir" [NGINX_CONF_ERROR]="Failed to extract necessary parameters from nginx.conf" [NGINX_CONF_MODIFY_FAILED]="Failed to modify nginx.conf" [PORT_8443_ALREADY_CONFIGURED]="Port 8443 already configured in nginx.conf" [UFW_RELOAD_FAILED]="Failed to reload UFW." [PORT_8443_ALREADY_CLOSED]="Port 8443 already closed in UFW." #Legiz Extensions [LEGIZ_EXTENSIONS_PROMPT]="Select action (0-2):" # Sub Page Upload [UPLOADING_SUB_PAGE]="Uploading custom sub page template..." [ERROR_FETCH_SUB_PAGE]="Failed to fetch custom sub page template." [SUB_PAGE_UPDATED_SUCCESS]="Custom sub page template successfully updated." [SELECT_SUB_PAGE_CUSTOM]="Select action (0-7):" [SELECT_SUB_PAGE_CUSTOM1]="Custom Sub Page Templates" [SELECT_SUB_PAGE_CUSTOM2]="Custom Sub Page Templates\nOnly run on panel server" [SELECT_SUB_PAGE_CUSTOM3]="Custom App Lists for original sub page:" [SELECT_SUB_PAGE_CUSTOM4]="Custom Sub Page:" [SUB_PAGE_SELECT_CHOICE]="Invalid choice. Please select 0-7." [RESTORE_SUB_PAGE]="Restore default sub page" [CONTAINER_NOT_FOUND]="Container %s not found" [SUB_WITH_APPCONFIG_ASK]="Do you want to include app-config.json?" [SUB_WITH_APPCONFIG_OPTION1]="Yes, use config from option 1 (Simple custom app list)" [SUB_WITH_APPCONFIG_OPTION2]="Yes, use config from option 2 (Multiapp custom app list)" [SUB_WITH_APPCONFIG_OPTION3]="Yes, use config from option 3 (HWID only app list)" [SUB_WITH_APPCONFIG_SKIP]="No, skip app-config.json" [SUB_WITH_APPCONFIG_INVALID]="Invalid option, skipping app-config.json" [SUB_WITH_APPCONFIG_INPUT]="Select action (0-3):" # Custom Branding [BRANDING_SUPPORT_ASK]="Add branding support to subscription page?" [BRANDING_SUPPORT_YES]="Yes, add branding support" [BRANDING_SUPPORT_NO]="No, skip branding" [BRANDING_NAME_PROMPT]="Enter your brand name:" [BRANDING_SUPPORT_URL_PROMPT]="Enter your support page URL:" [BRANDING_LOGO_URL_PROMPT]="Enter your brand logo URL:" [BRANDING_ADDED_SUCCESS]="Branding configuration successfully added" [CUSTOM_APP_LIST_MENU]="Edit custom application list and branding" [CUSTOM_APP_LIST_NOT_FOUND]="Custom application list not found" [EDIT_BRANDING]="Edit branding" [EDIT_LOGO]="Change logo" [EDIT_NAME]="Change name in branding" [EDIT_SUPPORT_URL]="Change support link" [DELETE_APPS]="Delete specific applications" [BRANDING_CURRENT_VALUES]="Current branding values:" [BRANDING_LOGO_URL]="Logo URL:" [BRANDING_NAME]="Name:" [BRANDING_SUPPORT_URL]="Support URL:" [ENTER_NEW_LOGO]="Enter new logo URL:" [ENTER_NEW_NAME]="Enter new brand name:" [ENTER_NEW_SUPPORT]="Enter new support URL:" [CONFIRM_CHANGE]="Confirm change? (y/n):" [PLATFORM_SELECT]="Select platform:" [APP_SELECT]="Which application do you want to delete?" [CONFIRM_DELETE_APP]="Are you sure you want to delete application %s from platform %s? (y/n):" [APP_DELETED_SUCCESS]="Application successfully deleted" [NO_APPS_FOUND]="No applications found in this platform" [RENEWAL_CONF_NOT_FOUND]="Renewal configuration file not found." [ARCHIVE_DIR_MISMATCH]="Archive directory mismatch in configuration." [CERT_VERSION_NOT_FOUND]="Failed to determine certificate version." [RESULTS_CERTIFICATE_UPDATES]="Results of certificate updates:" [CERTIFICATE_FOR]="Certificate for " [SUCCESSFULLY_UPDATED]="successfully updated" [FAILED_TO_UPDATE_CERTIFICATE_FOR]="Failed to update certificate for " [ERROR_CHECKING_EXPIRY_FOR]="Error checking expiry date for " [DOES_NOT_REQUIRE_UPDATE]="does not require updating (" [UPDATED]="Updated" [REMAINING]="Remaining" [ERROR_UPDATE]="Error updating" [ALREADY_EXPIRED]="already expired" [CERT_CLOUDFLARE_FILE_NOT_FOUND]="Cloudflare credentials file not found." [TELEGRAM_OAUTH_WARNING]="Telegram OAuth is enabled (TELEGRAM_OAUTH_ENABLED=true)." [CREATE_API_TOKEN_INSTRUCTION]="Go to the panel at: https://%s\nNavigate to 'API Tokens' -> 'Create New Token' and create a token.\nCopy the created token and enter it below." [ENTER_API_TOKEN]="Enter the API token: " [EMPTY_TOKEN_ERROR]="No token provided. Exiting." [RATE_LIMIT_EXCEEDED]="Rate limit exceeded for Let's Encrypt" [FAILED_TO_MODIFY_HTML_FILES]="Failed to modify HTML files" [INSTALLING_YQ]="Installing yq..." [ERROR_SETTING_YQ_PERMISSIONS]="Error setting yq permissions!" [YQ_SUCCESSFULLY_INSTALLED]="yq successfully installed!" [YQ_DOESNT_WORK_AFTER_INSTALLATION]="Error: yq doesn't work after installation!" [ERROR_DOWNLOADING_YQ]="Error downloading yq!" [FAST_START]="Quick start: remnawave_reverse" [CREATING_API_TOKEN]="Creating API token for Subscription Page..." [API_TOKEN_ADDED]="API token Subscription Page successfully added to docker-compose.yml" [ERROR_CREATE_API_TOKEN]="Error creating API token" [ERROR_API_TOKEN]="Failed to add API token" [STOPPING_REMNAWAVE_SUBSCRIPTION_PAGE]="Stopping Remnawave Subscription Page..." [STARTING_REMNAWAVE_SUBSCRIPTION_PAGE]="Starting Remnawave Subscription Page..." ) ;; ru) LANG=( #Alias [ALIAS_ADDED]="Алиас 'rr' для 'remnawave_reverse' добавлен в %s" [ALIAS_ACTIVATE_GLOBAL]="Алиас 'rr' теперь доступен для всех пользователей. Выполните 'source %s' или перезапустите терминал, чтобы применить алиас." #Check [ERROR_ROOT]="Скрипт нужно запускать с правами root" [ERROR_OS]="Поддержка только Debian 11/12 и Ubuntu 22.04/24.04" [MENU_TITLE]="REMNAWAVE REVERSE-PROXY by eGames" [AVAILABLE_UPDATE]="доступно обновление" [VERSION_LABEL]="Версия: %s" #Install Packages [ERROR_UPDATE_LIST]="Ошибка: Не удалось обновить список пакетов" [ERROR_INSTALL_PACKAGES]="Ошибка: Не удалось установить необходимые пакеты" [ERROR_INSTALL_CRON]="Ошибка: Не удалось установить cron" [ERROR_START_CRON]="Ошибка: Не удалось запустить cron" [ERROR_CONFIGURE_LOCALES]="Ошибка: Не удалось настроить локали" [ERROR_DOWNLOAD_DOCKER_KEY]="Ошибка: Не удалось загрузить ключ Docker" [ERROR_UPDATE_DOCKER_LIST]="Ошибка: Не удалось обновить список пакетов после добавления репозитория Docker" [ERROR_INSTALL_DOCKER]="Ошибка: Не удалось установить Docker" [ERROR_DOCKER_NOT_INSTALLED]="Ошибка: Docker не установлен" [ERROR_START_DOCKER]="Ошибка: Не удалось запустить Docker" [ERROR_ENABLE_AUTOSTART_DOCKER]="Ошибка: Не удалось включить автозапуск Docker" [ERROR_DOCKER_NOT_WORKING]="Ошибка: Docker не работает корректно" [ERROR_CONFIGURE_UFW]="Ошибка: Не удалось настроить UFW" [ERROR_CONFIGURE_UPGRADES]="Ошибка: Не удалось настроить unattended-upgrades" [ERROR_DOCKER_DNS]="Ошибка: Не удалось разрешить домен download.docker.com. Проверьте настройки DNS." [ERROR_INSTALL_CERTBOT]="Ошибка: Не удалось установить certbot" [SUCCESSFUL_INSTALL]="Все пакеты успешно установлены" #Main menu [EXIT]="Выход" [MENU_1]="Установка компонентов Remnawave" [MENU_2]="Переустановить панель/ноду" [MENU_3]="Управление панелью/нодой" [MENU_4]="Установить случайный шаблон для selfsteal ноды" [MENU_5]="Кастомные подписки от legiz" [MENU_6]="Управление расширениями от distillium" [MENU_7]="Управление IPv6" [MENU_8]="Управление сертификатами домена" [MENU_9]="Проверить обновления скрипта" [MENU_10]="Удалить скрипт" [PROMPT_ACTION]="Выберите действие (0-10):" [INVALID_CHOICE]="Неверный выбор. Выберите 0-10." [WARNING_LABEL]="ВНИМАНИЕ:" [CONFIRM_PROMPT]="Введите 'y' для продолжения или 'n' для выхода (y/n):" [WARNING_NODE_PANEL]="Добавление ноды должно выполняться только на сервере, где установлена панель, а не на сервере ноды." [CONFIRM_SERVER_PANEL]="Вы уверены, что находитесь на сервере с установленной панелью?" #Remove Script [REMOVE_SCRIPT_ONLY]="Удалить скрипт и его локальные файлы" [REMOVE_SCRIPT_AND_PANEL]="Удалить скрипт и данные панели/ноды remnawave" [CONFIRM_REMOVE_SCRIPT]="Все данные скрипта будут удалены с сервера. Вы уверены? (y/n): " [CONFIRM_REMOVE_ALL]="Все данные скрипта и панели/ноды будут удалены с сервера. Вы уверены? (y/n): " [SCRIPT_REMOVED]="Скрипт и его локальные файлы успешно удалены!" [ALL_REMOVED]="Скрипт и данные панели/ноды успешно удалены!" #Extensions by distillium [EXTENSIONS_MENU]="Расширения by distillium" [EXTENSIONS_MENU_TITLE]="Управление расширениями" [EXTENSIONS_PROMPT]="Выберите действие (0-2):" [EXTENSIONS_INVALID_CHOICE]="Неверный выбор. Выберите 0-2." [BACKUP_RESTORE]="Backup and Restore" #Warp by distillium [WARP_MENU]="WARP Native" [WARP_MENU_TITLE]="Управление WARP Native" [WARP_INSTALL]="Установить WARP Native" [WARP_ADD_CONFIG]="Добавить WARP-настройки в конфигурацию ноды" [WARP_DELETE_WARP_SETTINGS]="Удалить WARP-настройки из конфигурации ноды" [WARP_CONFIRM_SERVER_PANEL]="Вы уверены, что находитесь на сервере с установленной панелью?\nДобавление WARP-настроек должно выполняться только на сервере, где установлена панель, а не на сервере ноды" [WARP_UNINSTALL]="Удалить WARP Native" [WARP_PROMPT]="Выберите действие (0-4):" [WARP_PROMPT1]="Выберите действие:" [WARP_INVALID_CHOICE]="Неверный выбор. Выберите 0-4." [WARP_INVALID_CHOICE2]="Неверный выбор." [WARP_NO_NODE]="Нода Remnawave не найдена. Сначала установите ноду." [WARP_SELECT_CONFIG]="На какую ноду добавить WARP-настройки?" [WARP_SELECT_CONFIG_DELETE]="На какой ноде удалить WARP-настройки?" [WARP_NO_CONFIGS]="Конфигурации не найдены." [WARP_UPDATE_SUCCESS]="Конфигурация успешно обновлена!" [WARP_UPDATE_FAIL]="Не удалось обновить конфигурацию." [WARP_WARNING]="warp-out уже существует в outbounds." [WARP_WARNING2]="warp rule уже существует в routing rules." [WARP_REMOVED_WARP_SETTINGS1]="Удален warp-out из outbounds" [WARP_NO_WARP_SETTINGS1]="warp-out не найден в outbounds" [WARP_REMOVED_WARP_SETTINGS2]="Удален warp rule из routing rules" [WARP_NO_WARP_SETTINGS2]="warp rule не найден в routing rules" #Manage Panel/Node [START_PANEL_NODE]="Запустить панель/ноду" [STOP_PANEL_NODE]="Остановить панель/ноду" [UPDATE_PANEL_NODE]="Обновить панель/ноду" [VIEW_LOGS]="Просмотр логов" [PRESS_ENTER_RETURN_MENU]="Нажмите Enter для возврата в меню..." [REMNAWAVE_CLI]="Remnawave CLI" [ACCESS_PANEL]="Доступ к панели через порт 8443 (только для панели + ноды)" [MANAGE_PANEL_NODE_PROMPT]="Выберите действие (0-6):" [MANAGE_PANEL_NODE_INVALID_CHOICE]="Неверный выбор. Выберите 0-6." #Manage Certificates [CERT_UPDATE]="Обновить текущие сертификаты" [CERT_GENERATE]="Сгенерировать новые сертификаты для другого домена" [CERT_PROMPT1]="Выберите действие (0-2):" [CERT_INVALID_CHOICE]="Неверный выбор. Выберите 0-2." [CERT_UPDATE_CHECK]="Проверка метода генерации сертификата..." [CERT_UPDATE_SUCCESS]="Сертификаты успешно обновлены." [CERT_UPDATE_FAIL]="Не удалось обновить сертификаты." [CERT_GENERATE_PROMPT]="Введите домен для новых сертификатов (например, example.com):" [CERT_METHOD_UNKNOWN]="Неизвестный метод генерации сертификата." [CERT_NOT_DUE]="Сертификат для %s еще не требует обновления." #Install Remnawave Components [INSTALL_MENU_TITLE]="Установка компонентов Remnawave" [INSTALL_PANEL_NODE]="Установить панель и ноду на один сервер" [INSTALL_PANEL]="Установить только панель" [INSTALL_ADD_NODE]="Добавить ноду в панель" [INSTALL_NODE]="Установить только ноду" [INSTALL_PROMPT]="Выберите действие (0-4):" [INSTALL_INVALID_CHOICE]="Неверный выбор. Выберите 0-4." #Manage IPv6 [IPV6_MENU_TITLE]="Управление IPv6" [IPV6_ENABLE]="Включить IPv6" [IPV6_DISABLE]="Отключить IPv6" [IPV6_PROMPT]="Выберите действие (0-2):" [IPV6_INVALID_CHOICE]="Неверный выбор. Выберите 0-2." [IPV6_ALREADY_ENABLED]="IPv6 уже включен" [ENABLE_IPV6]="Включение IPv6..." [IPV6_ENABLED]="IPv6 включен." [IPV6_ALREADY_DISABLED]="IPv6 уже отключен" [DISABLING_IPV6]="Отключение IPv6..." [IPV6_DISABLED]="IPv6 отключен." #Remna [INSTALL_PACKAGES]="Установка необходимых пакетов..." [INSTALLING]="Установка панели и ноды" [INSTALLING_PANEL]="Установка панели" [INSTALLING_NODE]="Установка ноды" [ENTER_PANEL_DOMAIN]="Введите домен панели (например, panel.example.com):" [ENTER_SUB_DOMAIN]="Введите домен подписки (например, sub.example.com):" [ENTER_NODE_DOMAIN]="Введите selfsteal домен для ноды (например, node.example.com):" [ENTER_CF_TOKEN]="Введите Cloudflare API токен или глобальный ключ:" [ENTER_CF_EMAIL]="Введите зарегистрированную почту Cloudflare:" [ENTER_GCORE_TOKEN]="Введите API‑токен Gcore:" [CERT_GCORE_FILE_NOT_FOUND]="Файл с реквизитами Gcore не найден. Повторно введите токен." [ERROR_INSTALL_GCORE_PLUGIN]="Не удалось установить плагин certbot-dns-gcore" [CHECK_CERTS]="Проверка сертификатов..." [CERT_FOUND]="Сертификаты найдены в /etc/letsencrypt/live/" [CF_VALIDATING]="Cloudflare API ключ и email валидны" [CF_INVALID]="Неверный Cloudflare API ключ или email после %d попыток." [CF_INVALID_ATTEMPT]="Неверный Cloudflare API ключ или email. Попытка %d из %d." [WAITING]="Пожалуйста, подождите..." #API [REGISTERING_REMNAWAVE]="Регистрируем пользователя в панели Remnawave" [CHECK_CONTAINERS]="Проверка доступности контейнеров..." [CONTAINERS_NOT_READY_ATTEMPT]="Контейнеры не готовы, ожидание... Попытка %d из %d." [CONTAINERS_TIMEOUT]="Контейнеры не готовы после %d попыток.\n\nПроверьте логи:\n cd /opt/remnawave && docker compose logs -f\n\nТакже посмотрите типичные ошибки Docker:\n https://wiki.egam.es/ru/troubleshooting/docker-issues/" [REGISTRATION_SUCCESS]="Регистрация прошла успешно!" [GET_PUBLIC_KEY]="Получаем публичный ключ..." [PUBLIC_KEY_SUCCESS]="Публичный ключ успешно получен" [GENERATE_KEYS]="Генерация ключей x25519..." [GENERATE_KEYS_SUCCESS]="Ключи успешно сгенерированы" [CREATING_CONFIG_PROFILE]="Создаем конфигурационный профиль..." [CONFIG_PROFILE_CREATED]="Конфигурационный профиль успешно создан" [CREATING_NODE]="Создание ноды" [NODE_CREATED]="Нода успешно создана" [CREATE_HOST]="Создаем хост" [HOST_CREATED]="Хост успешно создан" [GET_DEFAULT_SQUAD]="Получение внутреннего сквада" [UPDATE_SQUAD]="Сквад успешно обновлен" [NO_SQUADS_FOUND]="Нет внутренних сквадов" [INVALID_UUID_FORMAT]="Неверный формат UUID" [NO_VALID_SQUADS_FOUND]="Нет валидных сквадов" [ERROR_GET_SQUAD]="Не удалось получить сквад" [INVALID_SQUAD_UUID]="Неверный UUID сквада" [INVALID_INBOUND_UUID]="Неверный UUID входа" #Stop/Start/Update [CHANGE_DIR_FAILED]="Не удалось перейти в директорию %s" [DIR_NOT_FOUND]="Директория /opt/remnawave не найдена" [PANEL_RUNNING]="Панель/нода уже запущена" [PANEL_RUN]="Панель/нода запущена" [PANEL_STOP]="Панель/нода остановлена" [PANEL_STOPPED]="Панель/нода уже остановлена" [NO_UPDATE]="Нет доступных обновлений для панели/ноды" [UPDATING]="Обновление панели/ноды..." [UPDATE_SUCCESS1]="Панель/нода успешно обновлена" [STARTING_PANEL_NODE]="Запуск панели и ноды" [STARTING_PANEL]="Запуск панели" [STARTING_NODE]="Запуск ноды" [STOPPING_REMNAWAVE]="Остановка панели и ноды" [IMAGES_DETECTED]="Обнаружены новые образы, перезапускаем контейнеры..." #Menu End [INSTALL_COMPLETE]=" УСТАНОВКА ЗАВЕРШЕНА!" [PANEL_ACCESS]="Панель доступна по адресу:" [ADMIN_CREDS]="Для входа в панель используйте следующие данные:" [USERNAME]="Логин:" [PASSWORD]="Пароль:" [RELAUNCH_CMD]="Для повторного запуска менеджера используйте команду:" #RandomHTML [DOWNLOAD_FAIL]="Ошибка загрузки, повторная попытка..." [UNPACK_ERROR]="Ошибка распаковки архива" [RANDOM_TEMPLATE]="Установка случайного шаблона для маскировочного сайта" [TEMPLATE_COPY]="Шаблон скопирован в /var/www/html/" [SELECT_TEMPLATE]="Выбран шаблон:" #Error [ERROR_TOKEN]="Не удалось получить токен." [ERROR_PUBLIC_KEY]="Не удалось получить публичный ключ." [ERROR_EXTRACT_PUBLIC_KEY]="Не удалось извлечь публичный ключ из ответа." [ERROR_GENERATE_KEYS]="Не удалось сгенерировать ключи." [ERROR_NO_CONFIGS]="Не найдены профили конфигураций" [NO_DEFAULT_PROFILE]="Default-Profile не найден" [ERROR_DELETE_PROFILE]="Не удалось удалить профиль" [ERROR_EMPTY_RESPONSE_NODE]="Пустой ответ от сервера при создании ноды." [ERROR_CREATE_NODE]="Не удалось создать ноду." [ERROR_EMPTY_RESPONSE_HOST]="Пустой ответ от сервера при создании хоста." [ERROR_CREATE_HOST]="Не удалось создать хост." [ERROR_EMPTY_RESPONSE_REGISTER]="Ошибка при регистрации - пустой ответ сервера" [ERROR_REGISTER]="Ошибка регистрации" [ERROR_UPDATE_SQUAD]="Ошибка обновления squad" [ERROR_GET_SQUAD_LIST]="Ошибка получения списка squadов" [NO_SQUADS_TO_UPDATE]="Нет сквадов для обновления" #Reinstall Panel/Node [REINSTALL_WARNING]="Все данные панели/ноды будут удалены с сервера. Вы уверены? (y/n):" [REINSTALL_TYPE_TITLE]="Выберите способ переустановки:" [REINSTALL_PROMPT]="Выберите действие (0-3):" [INVALID_REINSTALL_CHOICE]="Неверный выбор. Выберите 0-3." [POST_PANEL_MESSAGE]="Панель успешно установлена!" [POST_PANEL_INSTRUCTION]="Для установки ноды выполните следующие шаги:\n1. Запустите этот скрипт на сервере, где будет установлена нода.\n2. Выберите 'Установка компонентов Remnawave', а затем 'Установить только ноду'." [SELFSTEAL]="Введите selfsteal домен для ноды, который указали при установке панели:" [PANEL_IP_PROMPT]="Введите IP адрес панели, чтобы установить соединение между панелью и ноды:" [IP_ERROR]="Введите корректный IP-адрес в формате X.X.X.X (например, 192.168.1.1)" [CERT_PROMPT]="Введите сертификат, полученный от панели (вставьте содержимое и 2 раза нажмите Enter):" [CERT_CONFIRM]="Вы уверены, что сертификат правильный? (y/n):" [ABORT_MESSAGE]="Установка прервана пользователем" [SUCCESS_MESSAGE]="Нода успешно подключена" #Node Check [NODE_CHECK]="Проверка подключения ноды для %s..." [NODE_ATTEMPT]="Попытка %d из %d..." [NODE_UNAVAILABLE]="Нода недоступна на попытке %d." [NODE_LAUNCHED]="Нода успешно подключена!" [NODE_NOT_CONNECTED]="Нода не подключена после %d попыток!" [CHECK_CONFIG]="Проверьте конфигурацию или перезапустите панель." #Add node to panel [ADD_NODE_TO_PANEL]="Добавить ноду в панель" [ENTER_NODE_NAME]="Введите имя для вашей ноды (например, Germany):" [USING_SAVED_TOKEN]="Используем сохранённый токен..." [INVALID_SAVED_TOKEN]="Сохранённый токен недействителен. Запрашиваем новый..." [ENTER_PANEL_USERNAME]="Введите логин панели: " [ENTER_PANEL_PASSWORD]="Введите пароль панели: " [TOKEN_RECEIVED_AND_SAVED]="Токен успешно получен и сохранён" [TOKEN_USED_SUCCESSFULLY]="Токен успешно использован" [DOMAIN_ALREADY_EXISTS]="Домен уже используется" [TRY_ANOTHER_DOMAIN]="Пожалуйста, используйте другой домен" [ERROR_CHECK_DOMAIN]="Ошибка при проверке домена" [NODE_ADDED_SUCCESS]="Нода успешно добавлена!" [CREATE_NEW_NODE]="Создаём новую ноду для %s" [CF_INVALID_NAME]="Ошибка: Имя конфигурационного профиля %s уже используется.\nПожалуйста, выберите другое имя." [CF_INVALID_LENGTH]="Ошибка: Имя конфигурационного профиля должно содержать от 3 до 20 символов." [CF_INVALID_CHARS]="Ошибка: Имя конфигурационного профиля должно содержать только английские буквы, цифры и дефис." #check [CHECK_UPDATE]="Проверить обновления" [GENERATING_CERTS]="Генерируем сертификаты для %s" [REQUIRED_DOMAINS]="Требуемые домены для сертификатов:" [CHECK_DOMAIN_IP_FAIL]="Не удалось определить IP-адрес домена или сервера." [CHECK_DOMAIN_IP_FAIL_INSTRUCTION]="Убедитесь, что домен %s правильно настроен и указывает на этот сервер (%s)." [CHECK_DOMAIN_CLOUDFLARE]="Домен %s указывает на IP Cloudflare (%s)." [CHECK_DOMAIN_CLOUDFLARE_INSTRUCTION]="Проксирование Cloudflare недопустимо для selfsteal домена. Отключите проксирование (переключите в режим 'DNS Only')." [CHECK_DOMAIN_MISMATCH]="Домен %s указывает на IP-адрес %s, который отличается от IP этого сервера (%s)." [CHECK_DOMAIN_MISMATCH_INSTRUCTION]="Для корректной работы домен должен указывать на текущий сервер." [NO_PANEL_NODE_INSTALLED]="Панель или нода не установлены. Пожалуйста, сначала установите панель или ноду." #update [UPDATE_AVAILABLE]="Доступна новая версия скрипта: %s (текущая версия: %s)." [UPDATE_CONFIRM]="Обновить скрипт? (y/n):" [UPDATE_CANCELLED]="Обновление отменено пользователем." [UPDATE_SUCCESS]="Скрипт успешно обновлён до версии %s!" [UPDATE_FAILED]="Ошибка при скачивании новой версии скрипта." [VERSION_CHECK_FAILED]="Не удалось определить версию удалённого скрипта. Пропускаем обновление." [LATEST_VERSION]="У вас уже установлена последняя версия скрипта (%s)." [RESTART_REQUIRED]="Пожалуйста, перезапустите скрипт для применения изменений." [LOCAL_FILE_NOT_FOUND]="Локальный файл скрипта не найден, загружаем новую версию..." #CLI [RUNNING_CLI]="Запуск Remnawave CLI..." [CLI_SUCCESS]="Remnawave CLI успешно выполнен!" [CLI_FAILED]="Не удалось выполнить Remnawave CLI. Убедитесь, что контейнер 'remnawave' запущен." [CONTAINER_NOT_RUNNING]="Контейнер 'remnawave' не запущен. Пожалуйста, запустите его сначала." #Cert_choise [CERT_METHOD_PROMPT]="Выберите метод генерации сертификатов для всех доменов:" [CERT_METHOD_CF]="Cloudflare API (поддерживает wildcard)" [CERT_METHOD_ACME]="ACME HTTP-01 (один домен, без wildcard)" [CERT_METHOD_GCORE]="Gcore DNS API (поддерживает wildcard)" [CERT_METHOD_CHOOSE]="Выберите действие (0-3):" [EMAIL_PROMPT]="Введите ваш email для регистрации в Let's Encrypt:" [CERTS_SKIPPED]="Все сертификаты уже существуют. Пропускаем генерацию." [ACME_METHOD]="Используем ACME (Let's Encrypt) с HTTP-01 вызовом (без поддержки wildcard)..." [CERT_GENERATION_FAILED]="Не удалось сгенерировать сертификаты. Проверьте введенные данные и настройки DNS." [ADDING_CRON_FOR_EXISTING_CERTS]="Добавление cron-задачи для обновления сертификатов..." [CRON_ALREADY_EXISTS]="Задача cron для обновления сертификатов уже существует." [CERT_NOT_FOUND]="Сертификат для домена не найден." [ERROR_PARSING_CERT]="Ошибка при разборе даты истечения сертификата." [CERT_EXPIRY_SOON]="Сертификаты скоро истекут через" [DAYS]="дней" [UPDATING_CRON]="Обновление задачи cron в соответствии со сроком действия сертификата." [GENERATING_WILDCARD_CERT]="Генерация wildcard-сертификата для" [WILDCARD_CERT_FOUND]="Wildcard-сертификат найден в /etc/letsencrypt/live/" [FOR_DOMAIN]="для" [START_CRON_ERROR]="Не удалось запустить cron. Пожалуйста, запустите его вручную." [DOMAINS_MUST_BE_UNIQUE]="Ошибка: Все домены (панель, подписка, и нода) должны быть уникальными." [CHOOSE_TEMPLATE_SOURCE]="Выберите источник шаблонов:" [SIMPLE_WEB_TEMPLATES]="Simple web templates" [SNI_TEMPLATES]="SNI templates" [NOTHING_TEMPLATES]="Nothing Sni templates" [CHOOSE_TEMPLATE_OPTION]="Выберите действие (0-3):" [INVALID_TEMPLATE_CHOICE]="Неверный выбор. Выберите 0-3." #Manage panel access [PORT_8443_OPEN]="Открыть доступ к панели на порту 8443" [PORT_8443_CLOSE]="Закрыть доступ к панели на порту 8443" [PORT_8443_IN_USE]="Порт 8443 уже занят другим процессом. Проверьте, какие службы используют порт, и освободите его." [NO_PORT_CHECK_TOOLS]="Не найдены инструменты для проверки порта (ss или netstat). Установите один из них." [OPEN_PANEL_LINK]="Ваша ссылка для входа в панель:" [PORT_8443_WARNING]="Не забудьте, что порт 8443 сейчас открыт для внешнего доступа. После восстановления панели выберите пункт закрытия порта 8443." [PORT_8443_CLOSED]="Порт 8443 закрыт." [NGINX_CONF_NOT_FOUND]="Файл nginx.conf не найден в $dir" [NGINX_CONF_ERROR]="Не удалось извлечь необходимые параметры из nginx.conf" [NGINX_CONF_MODIFY_FAILED]="Не удалось изменить конфигурацию Nginx." [PORT_8443_ALREADY_CONFIGURED]="Порт 8443 уже настроен в конфигурации Nginx." [UFW_RELOAD_FAILED]="Не удалось перезагрузить UFW." [PORT_8443_ALREADY_CLOSED]="Порт 8443 уже закрыт в UFW." #Legiz Extensions [LEGIZ_EXTENSIONS_PROMPT]="Выберите действие (0-2):" # Sub Page Upload [UPLOADING_SUB_PAGE]="Загрузка пользовательского шаблона страницы подписки..." [ERROR_FETCH_SUB_PAGE]="Не удалось получить пользовательский шаблон страницы подписки." [SUB_PAGE_UPDATED_SUCCESS]="Пользовательский шаблон страницы подписки успешно обновлён." [SELECT_SUB_PAGE_CUSTOM]="Выберите действие (0-7):" [SELECT_SUB_PAGE_CUSTOM1]="Шаблоны страниц подписки" [SELECT_SUB_PAGE_CUSTOM2]="Шаблоны страниц подписки\nЗапускать только на сервере с панелью" [SELECT_SUB_PAGE_CUSTOM3]="Списки приложений для оригинальной страницы подписки:" [SELECT_SUB_PAGE_CUSTOM4]="Кастомные страницы подписки:" [SUB_PAGE_SELECT_CHOICE]="Недопустимый выбор. Пожалуйста, выберите от 0 до 7." [RESTORE_SUB_PAGE]="Восстановить шаблон страницы подписки по умолчанию" [CONTAINER_NOT_FOUND]="Контейнер %s не найден" [SUB_WITH_APPCONFIG_ASK]="Добавить файл конфигурации app-config.json?" [SUB_WITH_APPCONFIG_OPTION1]="Простой список приложений clash&sing" [SUB_WITH_APPCONFIG_OPTION2]="Множественный список приложений" [SUB_WITH_APPCONFIG_OPTION3]="Список приложений с поддержкой HWID" [SUB_WITH_APPCONFIG_SKIP]="Нет, пропустить добавление конфигурации" [SUB_WITH_APPCONFIG_INVALID]="Неверный выбор, конфигурация не будет добавлена" [SUB_WITH_APPCONFIG_INPUT]="Выберите действие (0–3):" # Custom Branding [BRANDING_SUPPORT_ASK]="Добавить поддержку брендирования страницы подписки?" [BRANDING_SUPPORT_YES]="Да, добавить поддержку брендирования" [BRANDING_SUPPORT_NO]="Нет, пропустить брендирование" [BRANDING_NAME_PROMPT]="Введите название вашего бренда:" [BRANDING_SUPPORT_URL_PROMPT]="Введите ссылку на страницу поддержки:" [BRANDING_LOGO_URL_PROMPT]="Введите ссылку на логотип вашего бренда:" [BRANDING_ADDED_SUCCESS]="Конфигурация брендирования успешно добавлена" [CUSTOM_APP_LIST_MENU]="Редактирование кастомного списка приложений и брендирования" [CUSTOM_APP_LIST_NOT_FOUND]="Кастомный список приложений не найден" [EDIT_BRANDING]="Редактирование брендирования" [EDIT_LOGO]="Изменить логотип" [EDIT_NAME]="Изменить имя в брендировании" [EDIT_SUPPORT_URL]="Изменить ссылку поддержки" [DELETE_APPS]="Удалить определенные приложения" [BRANDING_CURRENT_VALUES]="Текущие значения брендирования:" [BRANDING_LOGO_URL]="URL логотипа:" [BRANDING_NAME]="Имя:" [BRANDING_SUPPORT_URL]="URL поддержки:" [ENTER_NEW_LOGO]="Введите новый URL логотипа:" [ENTER_NEW_NAME]="Введите новое имя бренда:" [ENTER_NEW_SUPPORT]="Введите новую ссылку поддержки:" [CONFIRM_CHANGE]="Подтвердить изменение? (y/n):" [PLATFORM_SELECT]="Выберите платформу:" [APP_SELECT]="Какое приложение вы хотите удалить?" [CONFIRM_DELETE_APP]="Вы точно хотите удалить приложение %s из списка платформы %s? (y/n):" [APP_DELETED_SUCCESS]="Приложение успешно удалено" [NO_APPS_FOUND]="Приложения не найдены в этой платформе" [RENEWAL_CONF_NOT_FOUND]="Файл конфигурации обновления сертификатов не найден." [ARCHIVE_DIR_MISMATCH]="Несоответствие директории архива в конфигурации." [CERT_VERSION_NOT_FOUND]="Не удалось определить версию сертификатов." [RESULTS_CERTIFICATE_UPDATES]="Результаты обновления сертификатов:" [CERTIFICATE_FOR]="Сертификат для " [SUCCESSFULLY_UPDATED]="успешно обновлен" [FAILED_TO_UPDATE_CERTIFICATE_FOR]="Не удалось обновить сертификат для " [ERROR_CHECKING_EXPIRY_FOR]="Ошибка проверки даты истечения для " [DOES_NOT_REQUIRE_UPDATE]="не требует обновления (" [UPDATED]="Обновлен" [REMAINING]="Осталось" [ERROR_UPDATE]="Ошибка обновления" [ALREADY_EXPIRED]="уже истек" [CERT_CLOUDFLARE_FILE_NOT_FOUND]="Файл учетных данных Cloudflare не найден." [TELEGRAM_OAUTH_WARNING]="Включена авторизация через Telegram (TELEGRAM_OAUTH_ENABLED=true)." [CREATE_API_TOKEN_INSTRUCTION]="Зайдите в панель по адресу: https://%s\nПерейдите в раздел 'API токены' -> 'Создать новый токен' и создайте токен.\nСкопируйте созданный токен и введите его ниже." [ENTER_API_TOKEN]="Введите API-токен: " [EMPTY_TOKEN_ERROR]="Токен не введен. Завершение работы." [RATE_LIMIT_EXCEEDED]="Превышен лимит выдачи сертификатов Let's Encrypt" [FAILED_TO_MODIFY_HTML_FILES]="Не удалось изменить HTML файлы" [INSTALLING_YQ]="Установка yq..." [ERROR_SETTING_YQ_PERMISSIONS]="Ошибка установки прав yq!" [YQ_SUCCESSFULLY_INSTALLED]="yq успешно установлен!" [YQ_DOESNT_WORK_AFTER_INSTALLATION]="Ошибка: yq не работает после установки!" [ERROR_DOWNLOADING_YQ]="Ошибка загрузки yq!" [FAST_START]="Быстрый запуск: remnawave_reverse" [CREATING_API_TOKEN]="Создание API токена для Subscription Page..." [API_TOKEN_ADDED]="API токен Subscription Page успешно добавлен в docker-compose.yml" [ERROR_CREATE_API_TOKEN]="Ошибка создания API токена" [ERROR_API_TOKEN]="Не удалось добавить API токен" [STOPPING_REMNAWAVE_SUBSCRIPTION_PAGE]="Остановка Remnawave Subscription Page..." [STARTING_REMNAWAVE_SUBSCRIPTION_PAGE]="Запуск Remnawave Subscription Page..." ) ;; esac } question() { echo -e "${COLOR_GREEN}[?]${COLOR_RESET} ${COLOR_YELLOW}$*${COLOR_RESET}" } reading() { read -rp " $(question "$1")" "$2" } error() { echo -e "${COLOR_RED}$*${COLOR_RESET}" exit 1 } check_os() { if ! grep -q "bullseye" /etc/os-release && ! grep -q "bookworm" /etc/os-release && ! grep -q "jammy" /etc/os-release && ! grep -q "noble" /etc/os-release && ! grep -q "trixie" /etc/os-release; then error "${LANG[ERROR_OS]}" fi } check_root() { if [[ $EUID -ne 0 ]]; then error "${LANG[ERROR_ROOT]}" fi } log_clear() { sed -i -e 's/\x1b\[[0-9;]*[a-zA-Z]//g' "$LOGFILE" } log_entry() { mkdir -p ${DIR_REMNAWAVE} LOGFILE="${DIR_REMNAWAVE}remnawave_reverse.log" exec > >(tee -a "$LOGFILE") 2>&1 } run_remnawave_cli() { if ! docker ps --format '{{.Names}}' | grep -q '^remnawave$'; then echo -e "${COLOR_YELLOW}${LANG[CONTAINER_NOT_RUNNING]}${COLOR_RESET}" return 1 fi exec 3>&1 4>&2 exec > /dev/tty 2>&1 echo -e "${COLOR_YELLOW}${LANG[RUNNING_CLI]}${COLOR_RESET}" if docker exec -it -e TERM=xterm-256color remnawave remnawave; then echo -e "${COLOR_GREEN}${LANG[CLI_SUCCESS]}${COLOR_RESET}" else echo -e "${COLOR_RED}${LANG[CLI_FAILED]}${COLOR_RESET}" exec 1>&3 2>&4 return 1 fi exec 1>&3 2>&4 } start_panel_node() { local dir="" if [ -d "/opt/remnawave" ]; then dir="/opt/remnawave" else echo -e "${COLOR_RED}${LANG[DIR_NOT_FOUND]}${COLOR_RESET}" exit 1 fi cd "$dir" || { echo -e "${COLOR_RED}${LANG[CHANGE_DIR_FAILED]} $dir${COLOR_RESET}"; exit 1; } if docker ps -q --filter "ancestor=remnawave/backend:latest" | grep -q . || docker ps -q --filter "ancestor=remnawave/node:latest" | grep -q . || docker ps -q --filter "ancestor=remnawave/backend:2" | grep -q .; then echo -e "${COLOR_GREEN}${LANG[PANEL_RUNNING]}${COLOR_RESET}" else echo -e "${COLOR_YELLOW}${LANG[STARTING_PANEL_NODE]}...${COLOR_RESET}" sleep 1 docker compose up -d > /dev/null 2>&1 & spinner $! "${LANG[WAITING]}" echo -e "${COLOR_GREEN}${LANG[PANEL_RUN]}${COLOR_RESET}" fi } stop_panel_node() { local dir="" if [ -d "/opt/remnawave" ]; then dir="/opt/remnawave" else echo -e "${COLOR_RED}${LANG[DIR_NOT_FOUND]}${COLOR_RESET}" exit 1 fi cd "$dir" || { echo -e "${COLOR_RED}${LANG[CHANGE_DIR_FAILED]} $dir${COLOR_RESET}"; exit 1; } if ! docker ps -q --filter "ancestor=remnawave/backend:latest" | grep -q . && ! docker ps -q --filter "ancestor=remnawave/node:latest" | grep -q . && ! docker ps -q --filter "ancestor=remnawave/backend:2" | grep -q .; then echo -e "${COLOR_GREEN}${LANG[PANEL_STOPPED]}${COLOR_RESET}" else echo -e "${COLOR_YELLOW}${LANG[STOPPING_REMNAWAVE]}...${COLOR_RESET}" sleep 1 docker compose down > /dev/null 2>&1 & spinner $! "${LANG[WAITING]}" echo -e "${COLOR_GREEN}${LANG[PANEL_STOP]}${COLOR_RESET}" fi } update_panel_node() { local dir="" if [ -d "/opt/remnawave" ]; then dir="/opt/remnawave" else echo -e "${COLOR_RED}${LANG[DIR_NOT_FOUND]}${COLOR_RESET}" exit 1 fi cd "$dir" || { echo -e "${COLOR_RED}${LANG[CHANGE_DIR_FAILED]} $dir${COLOR_RESET}"; exit 1; } echo -e "${COLOR_YELLOW}${LANG[UPDATING]}${COLOR_RESET}" sleep 1 images_before=$(docker compose config --images | sort -u) if [ -n "$images_before" ]; then before=$(echo "$images_before" | xargs -I {} docker images -q {} | sort -u) else before="" fi tmpfile=$(mktemp) docker compose pull > "$tmpfile" 2>&1 & spinner $! "${LANG[WAITING]}" pull_output=$(cat "$tmpfile") rm -f "$tmpfile" images_after=$(docker compose config --images | sort -u) if [ -n "$images_after" ]; then after=$(echo "$images_after" | xargs -I {} docker images -q {} | sort -u) else after="" fi if [ "$before" != "$after" ] || echo "$pull_output" | grep -q "Pull complete"; then echo -e "" echo -e "${COLOR_YELLOW}${LANG[IMAGES_DETECTED]}${COLOR_RESET}" docker compose down > /dev/null 2>&1 & spinner $! "${LANG[WAITING]}" sleep 5 docker compose up -d > /dev/null 2>&1 & spinner $! "${LANG[WAITING]}" sleep 1 docker image prune -f > /dev/null 2>&1 echo -e "${COLOR_GREEN}${LANG[UPDATE_SUCCESS1]}${COLOR_RESET}" else echo -e "${COLOR_YELLOW}${LANG[NO_UPDATE]}${COLOR_RESET}" fi } update_remnawave_reverse() { local remote_version=$(curl -s "$SCRIPT_URL" | grep -m 1 "SCRIPT_VERSION=" | sed -E 's/.*SCRIPT_VERSION="([^"]+)".*/\1/') local update_script="${DIR_REMNAWAVE}remnawave_reverse" local bin_link="/usr/local/bin/remnawave_reverse" if [ -z "$remote_version" ]; then echo -e "${COLOR_YELLOW}${LANG[VERSION_CHECK_FAILED]}${COLOR_RESET}" return 1 fi if [ -f "$update_script" ]; then if [ "$SCRIPT_VERSION" = "$remote_version" ]; then printf "${COLOR_GREEN}${LANG[LATEST_VERSION]}${COLOR_RESET}\n" "$SCRIPT_VERSION" return 0 fi else echo -e "${COLOR_YELLOW}${LANG[LOCAL_FILE_NOT_FOUND]}${COLOR_RESET}" fi printf "${COLOR_YELLOW}${LANG[UPDATE_AVAILABLE]}${COLOR_RESET}\n" "$remote_version" "$SCRIPT_VERSION" reading "${LANG[UPDATE_CONFIRM]}" confirm_update if [[ "$confirm_update" != "y" && "$confirm_update" != "Y" ]]; then echo -e "${COLOR_YELLOW}${LANG[UPDATE_CANCELLED]}${COLOR_RESET}" return 0 fi mkdir -p "${DIR_REMNAWAVE}" local temp_script="${DIR_REMNAWAVE}remnawave_reverse.tmp" if wget -q -O "$temp_script" "$SCRIPT_URL"; then local downloaded_version=$(grep -m 1 "SCRIPT_VERSION=" "$temp_script" | sed -E 's/.*SCRIPT_VERSION="([^"]+)".*/\1/') if [ "$downloaded_version" != "$remote_version" ]; then echo -e "${COLOR_RED}${LANG[UPDATE_FAILED]}${COLOR_RESET}" rm -f "$temp_script" return 1 fi if [ -f "$update_script" ]; then rm -f "$update_script" fi mv "$temp_script" "$update_script" chmod +x "$update_script" if [ -e "$bin_link" ]; then rm -f "$bin_link" fi ln -s "$update_script" "$bin_link" hash -r printf "${COLOR_GREEN}${LANG[UPDATE_SUCCESS]}${COLOR_RESET}\n" "$remote_version" echo -e "" echo -e "${COLOR_YELLOW}${LANG[RESTART_REQUIRED]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}${LANG[RELAUNCH_CMD]}${COLOR_GREEN} remnawave_reverse${COLOR_RESET}" exit 0 else echo -e "${COLOR_RED}${LANG[UPDATE_FAILED]}${COLOR_RESET}" rm -f "$temp_script" return 1 fi } remove_script() { echo -e "" echo -e "${COLOR_GREEN}${LANG[MENU_10]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}1. ${LANG[REMOVE_SCRIPT_ONLY]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}2. ${LANG[REMOVE_SCRIPT_AND_PANEL]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}0. ${LANG[EXIT]}${COLOR_RESET}" echo -e "" reading "${LANG[CERT_PROMPT1]}" SUB_OPTION case $SUB_OPTION in 1) echo -e "${COLOR_RED}${LANG[CONFIRM_REMOVE_SCRIPT]}${COLOR_RESET}" read confirm if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then echo -e "${COLOR_YELLOW}${LANG[EXIT]}${COLOR_RESET}" return 0 fi rm -rf /usr/local/remnawave_reverse 2>/dev/null rm -f /usr/local/bin/remnawave_reverse 2>/dev/null echo -e "${COLOR_GREEN}${LANG[SCRIPT_REMOVED]}${COLOR_RESET}" exit 0 ;; 2) echo -e "${COLOR_RED}${LANG[CONFIRM_REMOVE_ALL]}${COLOR_RESET}" read confirm if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then echo -e "${COLOR_YELLOW}${LANG[EXIT]}${COLOR_RESET}" return 0 fi if [ -d "/opt/remnawave" ]; then cd /opt/remnawave || { echo -e "${COLOR_RED}${LANG[CHANGE_DIR_FAILED]} /opt/remnawave${COLOR_RESET}"; exit 1; } docker compose down -v --rmi all --remove-orphans > /dev/null 2>&1 & spinner $! "${LANG[WAITING]}" rm -rf /opt/remnawave 2>/dev/null fi docker system prune -a --volumes -f > /dev/null 2>&1 & spinner $! "${LANG[WAITING]}" rm -rf /usr/local/remnawave_reverse 2>/dev/null rm -f /usr/local/bin/remnawave_reverse 2>/dev/null echo -e "${COLOR_GREEN}${LANG[ALL_REMOVED]}${COLOR_RESET}" exit 0 ;; 0) echo -e "${COLOR_YELLOW}${LANG[EXIT]}${COLOR_RESET}" return 0 ;; *) echo -e "${COLOR_YELLOW}${LANG[CERT_INVALID_CHOICE]}${COLOR_RESET}" sleep 2 remove_script ;; esac } install_script_if_missing() { if [ ! -f "${DIR_REMNAWAVE}remnawave_reverse" ] || [ ! -f "/usr/local/bin/remnawave_reverse" ]; then mkdir -p "${DIR_REMNAWAVE}" if ! wget -q -O "${DIR_REMNAWAVE}remnawave_reverse" "$SCRIPT_URL"; then exit 1 fi chmod +x "${DIR_REMNAWAVE}remnawave_reverse" ln -sf "${DIR_REMNAWAVE}remnawave_reverse" /usr/local/bin/remnawave_reverse fi local bashrc_file="/etc/bash.bashrc" local alias_line="alias rr='remnawave_reverse'" if [ ! -f "$bashrc_file" ]; then touch "$bashrc_file" chmod 644 "$bashrc_file" fi if [ -s "$bashrc_file" ] && [ "$(tail -c 1 "$bashrc_file")" != "" ]; then echo >> "$bashrc_file" fi if ! grep -E "^[[:space:]]*alias rr='remnawave_reverse'[[:space:]]*$" "$bashrc_file" > /dev/null; then echo "$alias_line" >> "$bashrc_file" printf "${COLOR_GREEN}${LANG[ALIAS_ADDED]}${COLOR_RESET}\n" "$bashrc_file" printf "${COLOR_YELLOW}${LANG[ALIAS_ACTIVATE_GLOBAL]}${COLOR_RESET}\n" "$bashrc_file" else printf "${COLOR_YELLOW}${LANG[ALIAS_EXISTS]}${COLOR_RESET}\n" "$bashrc_file" fi } generate_user() { local length=8 tr -dc 'a-zA-Z' < /dev/urandom | fold -w $length | head -n 1 } generate_password() { local length=24 local password="" local upper_chars='A-Z' local lower_chars='a-z' local digit_chars='0-9' local special_chars='!@#%^&*()_+' local all_chars='A-Za-z0-9!@#%^&*()_+' password+=$(head /dev/urandom | tr -dc "$upper_chars" | head -c 1) password+=$(head /dev/urandom | tr -dc "$lower_chars" | head -c 1) password+=$(head /dev/urandom | tr -dc "$digit_chars" | head -c 1) password+=$(head /dev/urandom | tr -dc "$special_chars" | head -c 3) password+=$(head /dev/urandom | tr -dc "$all_chars" | head -c $(($length - 6))) password=$(echo "$password" | fold -w1 | shuf | tr -d '\n') echo "$password" } #Displaying the availability of the update in the menu check_update_status() { local TEMP_REMOTE_VERSION_FILE TEMP_REMOTE_VERSION_FILE=$(mktemp) if ! curl -fsSL "$SCRIPT_URL" 2>/dev/null | head -n 100 > "$TEMP_REMOTE_VERSION_FILE"; then UPDATE_AVAILABLE=false rm -f "$TEMP_REMOTE_VERSION_FILE" return fi local REMOTE_VERSION REMOTE_VERSION=$(grep -m 1 "^SCRIPT_VERSION=" "$TEMP_REMOTE_VERSION_FILE" | cut -d'"' -f2) rm -f "$TEMP_REMOTE_VERSION_FILE" if [[ -z "$REMOTE_VERSION" ]]; then UPDATE_AVAILABLE=false return fi compare_versions_for_check() { local v1="$1" local v2="$2" local v1_num="${v1//[^0-9.]/}" local v2_num="${v2//[^0-9.]/}" local v1_sfx="${v1//$v1_num/}" local v2_sfx="${v2//$v2_num/}" if [[ "$v1_num" == "$v2_num" ]]; then if [[ -z "$v1_sfx" && -n "$v2_sfx" ]]; then return 0 elif [[ -n "$v1_sfx" && -z "$v2_sfx" ]]; then return 1 elif [[ "$v1_sfx" < "$v2_sfx" ]]; then return 0 else return 1 fi else if printf '%s\n' "$v1_num" "$v2_num" | sort -V | head -n1 | grep -qx "$v1_num"; then return 0 else return 1 fi fi } if compare_versions_for_check "$SCRIPT_VERSION" "$REMOTE_VERSION"; then UPDATE_AVAILABLE=true else UPDATE_AVAILABLE=false fi } show_menu() { echo -e "${COLOR_GREEN}${LANG[MENU_TITLE]}${COLOR_RESET}" if [[ "$UPDATE_AVAILABLE" == true ]]; then echo -e "${COLOR_GRAY}$(printf "${LANG[VERSION_LABEL]}" "$SCRIPT_VERSION ${COLOR_RED}${LANG[AVAILABLE_UPDATE]}${COLOR_RESET}")${COLOR_RESET}" else echo -e "${COLOR_GRAY}$(printf "${LANG[VERSION_LABEL]}" "$SCRIPT_VERSION")${COLOR_RESET}" fi echo -e "" echo -e "${COLOR_YELLOW}1. ${LANG[MENU_1]}${COLOR_RESET}" # Install Remnawave Components echo -e "${COLOR_YELLOW}2. ${LANG[MENU_2]}${COLOR_RESET}" # Reinstall panel/node echo -e "${COLOR_YELLOW}3. ${LANG[MENU_3]}${COLOR_RESET}" # Manage panel/node echo -e "" echo -e "${COLOR_YELLOW}4. ${LANG[MENU_4]}${COLOR_RESET}" # Install random template echo -e "${COLOR_YELLOW}5. ${LANG[MENU_5]}${COLOR_RESET}" # Custom Templates legiz echo -e "${COLOR_YELLOW}6. ${LANG[MENU_6]}${COLOR_RESET}" # Extensions distilium echo -e "" echo -e "${COLOR_YELLOW}7. ${LANG[MENU_7]}${COLOR_RESET}" # Manage IPv6 echo -e "${COLOR_YELLOW}8. ${LANG[MENU_8]}${COLOR_RESET}" # Manage certificates domain echo -e "" echo -e "${COLOR_YELLOW}9. ${LANG[MENU_9]}${COLOR_RESET}" # Check for updates echo -e "${COLOR_YELLOW}10. ${LANG[MENU_10]}${COLOR_RESET}" # Remove script echo -e "" echo -e "${COLOR_YELLOW}0. ${LANG[EXIT]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}- ${LANG[FAST_START]//remnawave_reverse/${COLOR_GREEN}remnawave_reverse${COLOR_RESET}}" echo -e "" } #Manage Install Remnawave Components show_install_menu() { echo -e "" echo -e "${COLOR_GREEN}${LANG[INSTALL_MENU_TITLE]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}1. ${LANG[INSTALL_PANEL_NODE]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}2. ${LANG[INSTALL_PANEL]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}3. ${LANG[INSTALL_ADD_NODE]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}4. ${LANG[INSTALL_NODE]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}0. ${LANG[EXIT]}${COLOR_RESET}" echo -e "" } manage_install() { show_install_menu reading "${LANG[INSTALL_PROMPT]}" INSTALL_OPTION case $INSTALL_OPTION in 1) if [ ! -f "${DIR_REMNAWAVE}install_packages" ] || ! command -v docker >/dev/null 2>&1 || ! docker info >/dev/null 2>&1 || ! command -v certbot >/dev/null 2>&1; then install_packages || { echo -e "${COLOR_RED}${LANG[ERROR_INSTALL_DOCKER]}${COLOR_RESET}" log_clear exit 1 } fi installation sleep 2 log_clear ;; 2) if [ ! -f "${DIR_REMNAWAVE}install_packages" ] || ! command -v docker >/dev/null 2>&1 || ! docker info >/dev/null 2>&1 || ! command -v certbot >/dev/null 2>&1; then install_packages || { echo -e "${COLOR_RED}${LANG[ERROR_INSTALL_DOCKER]}${COLOR_RESET}" log_clear exit 1 } fi installation_panel sleep 2 log_clear ;; 3) add_node_to_panel log_clear ;; 4) if [ ! -f "${DIR_REMNAWAVE}install_packages" ] || ! command -v docker >/dev/null 2>&1 || ! docker info >/dev/null 2>&1 || ! command -v certbot >/dev/null 2>&1; then install_packages || { echo -e "${COLOR_RED}${LANG[ERROR_INSTALL_DOCKER]}${COLOR_RESET}" log_clear exit 1 } fi installation_node sleep 2 log_clear ;; 0) echo -e "${COLOR_YELLOW}${LANG[EXIT]}${COLOR_RESET}" log_clear remnawave_reverse ;; *) echo -e "${COLOR_YELLOW}${LANG[INSTALL_INVALID_CHOICE]}${COLOR_RESET}" sleep 2 log_clear manage_install ;; esac } #Manage Install Remnawave Components #Manage Panel Access show_panel_access() { echo -e "" echo -e "${COLOR_GREEN}${LANG[MENU_9]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}1. ${LANG[PORT_8443_OPEN]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}2. ${LANG[PORT_8443_CLOSE]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}0. ${LANG[EXIT]}${COLOR_RESET}" echo -e "" } manage_panel_access() { show_panel_access reading "${LANG[IPV6_PROMPT]}" ACCESS_OPTION case $ACCESS_OPTION in 1) open_panel_access sleep 2 log_clear manage_panel_access ;; 2) close_panel_access sleep 2 log_clear manage_panel_access ;; 0) echo -e "${COLOR_YELLOW}${LANG[EXIT]}${COLOR_RESET}" log_clear remnawave_reverse ;; *) echo -e "${COLOR_YELLOW}${LANG[IPV6_INVALID_CHOICE]}${COLOR_RESET}" sleep 2 log_clear manage_panel_access ;; esac } open_panel_access() { local dir="" if [ -d "/opt/remnawave" ]; then dir="/opt/remnawave" else echo -e "${COLOR_RED}${LANG[DIR_NOT_FOUND]}${COLOR_RESET}" exit 1 fi cd "$dir" || { echo -e "${COLOR_RED}${LANG[CHANGE_DIR_FAILED]} $dir${COLOR_RESET}"; exit 1; } if [ ! -f "nginx.conf" ]; then echo -e "${COLOR_RED}${LANG[NGINX_CONF_NOT_FOUND]} $dir${COLOR_RESET}" exit 1 fi PANEL_DOMAIN=$(grep -B 20 "proxy_pass http://remnawave" "$dir/nginx.conf" | grep "server_name" | grep -v "server_name _" | awk '{print $2}' | sed 's/;//' | head -n 1) cookie_line=$(grep -A 2 "map \$http_cookie \$auth_cookie" "$dir/nginx.conf" | grep "~*\w\+.*=") cookies_random1=$(echo "$cookie_line" | grep -oP '~*\K\w+(?==)') cookies_random2=$(echo "$cookie_line" | grep -oP '=\K\w+(?=")') if [ -z "$PANEL_DOMAIN" ] || [ -z "$cookies_random1" ] || [ -z "$cookies_random2" ]; then echo -e "${COLOR_RED}${LANG[NGINX_CONF_ERROR]}${COLOR_RESET}" exit 1 fi if command -v ss >/dev/null 2>&1; then if ss -tuln | grep -q ":8443"; then echo -e "${COLOR_RED}${LANG[PORT_8443_IN_USE]}${COLOR_RESET}" exit 1 fi elif command -v netstat >/dev/null 2>&1; then if netstat -tuln | grep -q ":8443"; then echo -e "${COLOR_RED}${LANG[PORT_8443_IN_USE]}${COLOR_RESET}" exit 1 fi else echo -e "${COLOR_RED}${LANG[NO_PORT_CHECK_TOOLS]}${COLOR_RESET}" exit 1 fi sed -i "/server_name $PANEL_DOMAIN;/,/}/{/^[[:space:]]*$/d; s/listen 8443 ssl;//}" "$dir/nginx.conf" sed -i "/server_name $PANEL_DOMAIN;/a \ listen 8443 ssl;" "$dir/nginx.conf" if [ $? -ne 0 ]; then echo -e "${COLOR_RED}${LANG[NGINX_CONF_MODIFY_FAILED]}${COLOR_RESET}" exit 1 fi docker compose down remnawave-nginx > /dev/null 2>&1 & spinner $! "${LANG[WAITING]}" docker compose up -d remnawave-nginx > /dev/null 2>&1 & spinner $! "${LANG[WAITING]}" ufw allow from 0.0.0.0/0 to any port 8443 proto tcp > /dev/null 2>&1 ufw reload > /dev/null 2>&1 sleep 1 local panel_link="https://${PANEL_DOMAIN}:8443/auth/login?${cookies_random1}=${cookies_random2}" echo -e "${COLOR_YELLOW}${LANG[OPEN_PANEL_LINK]}${COLOR_RESET}" echo -e "${COLOR_WHITE}${panel_link}${COLOR_RESET}" echo -e "${COLOR_RED}${LANG[PORT_8443_WARNING]}${COLOR_RESET}" sleep 2 log_clear } close_panel_access() { local dir="" if [ -d "/opt/remnawave" ]; then dir="/opt/remnawave" else echo -e "${COLOR_RED}${LANG[DIR_NOT_FOUND]}${COLOR_RESET}" exit 1 fi cd "$dir" || { echo -e "${COLOR_RED}${LANG[CHANGE_DIR_FAILED]} $dir${COLOR_RESET}"; exit 1; } echo -e "${COLOR_YELLOW}${LANG[PORT_8443_CLOSE]}${COLOR_RESET}" if [ ! -f "nginx.conf" ]; then echo -e "${COLOR_RED}${LANG[NGINX_CONF_NOT_FOUND]} $dir${COLOR_RESET}" exit 1 fi PANEL_DOMAIN=$(grep -B 20 "proxy_pass http://remnawave" "$dir/nginx.conf" | grep "server_name" | grep -v "server_name _" | awk '{print $2}' | sed 's/;//' | head -n 1) if [ -z "$PANEL_DOMAIN" ]; then echo -e "${COLOR_RED}${LANG[NGINX_CONF_ERROR]}${COLOR_RESET}" exit 1 fi if grep -A 10 "server_name $PANEL_DOMAIN;" "$dir/nginx.conf" | grep -q "listen 8443 ssl;"; then sed -i "/server_name $PANEL_DOMAIN;/,/}/{/^[[:space:]]*$/d; s/listen 8443 ssl;//}" "$dir/nginx.conf" if [ $? -ne 0 ]; then echo -e "${COLOR_RED}${LANG[NGINX_CONF_MODIFY_FAILED]}${COLOR_RESET}" exit 1 fi docker compose down remnawave-nginx > /dev/null 2>&1 & spinner $! "${LANG[WAITING]}" docker compose up -d remnawave-nginx > /dev/null 2>&1 & spinner $! "${LANG[WAITING]}" else echo -e "${COLOR_YELLOW}${LANG[PORT_8443_NOT_CONFIGURED]}${COLOR_RESET}" fi if ufw status | grep -q "8443.*ALLOW"; then ufw delete allow from 0.0.0.0/0 to any port 8443 proto tcp > /dev/null 2>&1 ufw reload > /dev/null 2>&1 if [ $? -ne 0 ]; then echo -e "${COLOR_RED}${LANG[UFW_RELOAD_FAILED]}${COLOR_RESET}" exit 1 fi echo -e "${COLOR_GREEN}${LANG[PORT_8443_CLOSED]}${COLOR_RESET}" else echo -e "${COLOR_YELLOW}${LANG[PORT_8443_ALREADY_CLOSED]}${COLOR_RESET}" fi sleep 2 log_clear } view_logs() { local dir="" if [ -d "/opt/remnawave" ]; then dir="/opt/remnawave" else echo -e "${COLOR_RED}${LANG[DIR_NOT_FOUND]}${COLOR_RESET}" exit 1 fi cd "$dir" || { echo -e "${COLOR_RED}${LANG[CHANGE_DIR_FAILED]} $dir${COLOR_RESET}"; exit 1; } if ! docker ps -q --filter "ancestor=remnawave/backend:latest" | grep -q . && ! docker ps -q --filter "ancestor=remnawave/node:latest" | grep -q . && ! docker ps -q --filter "ancestor=remnawave/backend:2" | grep -q .; then echo -e "${COLOR_RED}${LANG[CONTAINER_NOT_RUNNING]}${COLOR_RESET}" exit 1 fi echo -e "${COLOR_YELLOW}${LANG[VIEW_LOGS]}${COLOR_RESET}" docker compose logs -f -t } #Manage Panel Access #Show Reinstall Options show_reinstall_options() { echo -e "" echo -e "${COLOR_GREEN}${LANG[REINSTALL_TYPE_TITLE]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}1. ${LANG[INSTALL_PANEL_NODE]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}2. ${LANG[INSTALL_PANEL]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}3. ${LANG[INSTALL_NODE]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}0. ${LANG[EXIT]}${COLOR_RESET}" echo -e "" } choose_reinstall_type() { show_reinstall_options reading "${LANG[REINSTALL_PROMPT]}" REINSTALL_OPTION case $REINSTALL_OPTION in 1|2|3) echo -e "${COLOR_RED}${LANG[REINSTALL_WARNING]}${COLOR_RESET}" read confirm if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then reinstall_remnawave if [ ! -f ${DIR_REMNAWAVE}install_packages ]; then install_packages fi case $REINSTALL_OPTION in 1) installation ;; 2) installation_panel ;; 3) installation_node ;; esac log_clear else echo -e "${COLOR_YELLOW}${LANG[EXIT]}${COLOR_RESET}" exit 0 fi ;; 0) echo -e "${COLOR_YELLOW}${LANG[EXIT]}${COLOR_RESET}" remnawave_reverse ;; *) echo -e "${COLOR_YELLOW}${LANG[INVALID_REINSTALL_CHOICE]}${COLOR_RESET}" exit 1 ;; esac } reinstall_remnawave() { if [ -d "/opt/remnawave" ]; then cd /opt/remnawave || return docker compose down -v --rmi all --remove-orphans > /dev/null 2>&1 & spinner $! "${LANG[WAITING]}" fi docker system prune -a --volumes -f > /dev/null 2>&1 & spinner $! "${LANG[WAITING]}" rm -rf /opt/remnawave 2>/dev/null } #Show Reinstall Options #Show Panel Node Menu show_panel_node_menu() { echo -e "" echo -e "${COLOR_GREEN}${LANG[MENU_3]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}1. ${LANG[START_PANEL_NODE]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}2. ${LANG[STOP_PANEL_NODE]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}3. ${LANG[UPDATE_PANEL_NODE]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}4. ${LANG[VIEW_LOGS]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}5. ${LANG[REMNAWAVE_CLI]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}6. ${LANG[ACCESS_PANEL]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}0. ${LANG[EXIT]}${COLOR_RESET}" echo -e "" reading "${LANG[MANAGE_PANEL_NODE_PROMPT]}" SUB_OPTION case $SUB_OPTION in 1) start_panel_node sleep 2 log_clear show_panel_node_menu ;; 2) stop_panel_node sleep 2 log_clear show_panel_node_menu ;; 3) update_panel_node sleep 2 log_clear show_panel_node_menu ;; 4) view_logs sleep 2 log_clear show_panel_node_menu ;; 5) run_remnawave_cli sleep 2 log_clear show_panel_node_menu ;; 6) manage_panel_access sleep 2 log_clear show_panel_node_menu ;; 0) remnawave_reverse ;; *) echo -e "${COLOR_YELLOW}${LANG[MANAGE_PANEL_NODE_INVALID_CHOICE]}${COLOR_RESET}" sleep 1 show_panel_node_menu ;; esac } #Manage Panel Node Menu manage_extensions() { echo -e "" echo -e "${COLOR_GREEN}${LANG[EXTENSIONS_MENU_TITLE]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}1. ${LANG[WARP_MENU]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}2. ${LANG[BACKUP_RESTORE]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}0. ${LANG[EXIT]}${COLOR_RESET}" echo -e "" reading "${LANG[EXTENSIONS_PROMPT]}" EXTENSION_OPTION case $EXTENSION_OPTION in 1) manage_warp log_clear manage_extensions ;; 2) if [ -f ~/backup-restore.sh ]; then rw-backup else curl -o ~/backup-restore.sh https://raw.githubusercontent.com/distillium/remnawave-backup-restore/main/backup-restore.sh && chmod +x ~/backup-restore.sh && ~/backup-restore.sh fi log_clear manage_extensions ;; 0) echo -e "${COLOR_YELLOW}${LANG[EXIT]}${COLOR_RESET}" ;; *) echo -e "${COLOR_RED}${LANG[EXTENSIONS_INVALID_CHOICE]}${COLOR_RESET}" ;; esac } manage_warp() { echo -e "" echo -e "${COLOR_GREEN}${LANG[WARP_MENU_TITLE]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}1. ${LANG[WARP_INSTALL]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}2. ${LANG[WARP_UNINSTALL]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}3. ${LANG[WARP_ADD_CONFIG]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}4. ${LANG[WARP_DELETE_WARP_SETTINGS]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}0. ${LANG[EXIT]}${COLOR_RESET}" echo -e "" reading "${LANG[WARP_PROMPT]}" WARP_OPTION case $WARP_OPTION in 1) if ! grep -q "remnanode:" /opt/remnawave/docker-compose.yml; then echo -e "${COLOR_RED}${LANG[WARP_NO_NODE]}${COLOR_RESET}" exit 1 fi bash <(curl -fsSL https://raw.githubusercontent.com/distillium/warp-native/main/install.sh) ;; 2) bash <(curl -fsSL https://raw.githubusercontent.com/distillium/warp-native/main/uninstall.sh) ;; 3) local domain_url="127.0.0.1:3000" echo -e "" echo -e "${COLOR_RED}${LANG[WARNING_LABEL]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}${LANG[WARP_CONFIRM_SERVER_PANEL]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_GREEN}[?]${COLOR_RESET} ${COLOR_YELLOW}${LANG[CONFIRM_PROMPT]}${COLOR_RESET}" read confirm echo if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then echo -e "${COLOR_YELLOW}${LANG[EXIT]}${COLOR_RESET}" exit 0 fi get_panel_token token=$(cat "$TOKEN_FILE") local config_response=$(make_api_request "GET" "${domain_url}/api/config-profiles" "$token") if [ -z "$config_response" ] || ! echo "$config_response" | jq -e '.' > /dev/null 2>&1; then echo -e "${COLOR_RED}${LANG[WARP_NO_CONFIGS]}: Invalid response${COLOR_RESET}" fi if ! echo "$config_response" | jq -e '.response.configProfiles | type == "array"' > /dev/null 2>&1; then echo -e "${COLOR_RED}${LANG[WARP_NO_CONFIGS]}: Response does not contain configProfiles array${COLOR_RESET}" fi local config_count=$(echo "$config_response" | jq '.response.configProfiles | length') if [ "$config_count" -eq 0 ]; then echo -e "${COLOR_RED}${LANG[WARP_NO_CONFIGS]}: Empty configuration list${COLOR_RESET}" fi local configs=$(echo "$config_response" | jq -r '.response.configProfiles[] | select(.uuid and .name) | "\(.name) \(.uuid)"' 2>/dev/null) if [ -z "$configs" ]; then echo -e "${COLOR_RED}${LANG[WARP_NO_CONFIGS]}: No valid configurations found in response${COLOR_RESET}" fi echo -e "" echo -e "${COLOR_YELLOW}${LANG[WARP_SELECT_CONFIG]}${COLOR_RESET}" echo -e "" local i=1 declare -A config_map while IFS=' ' read -r name uuid; do echo -e "${COLOR_YELLOW}$i. $name${COLOR_RESET}" config_map[$i]="$uuid" ((i++)) done <<< "$configs" echo -e "" echo -e "${COLOR_YELLOW}0. ${LANG[EXIT]}${COLOR_RESET}" echo -e "" reading "${LANG[WARP_PROMPT1]}" CONFIG_OPTION if [ "$CONFIG_OPTION" == "0" ]; then echo -e "${COLOR_YELLOW}${LANG[EXIT]}${COLOR_RESET}" return 0 fi if [ -z "${config_map[$CONFIG_OPTION]}" ]; then echo -e "${COLOR_RED}${LANG[WARP_INVALID_CHOICE2]}${COLOR_RESET}" return 1 fi local selected_uuid=${config_map[$CONFIG_OPTION]} local config_data=$(make_api_request "GET" "${domain_url}/api/config-profiles/$selected_uuid" "$token") if [ -z "$config_data" ] || ! echo "$config_data" | jq -e '.' > /dev/null 2>&1; then echo -e "${COLOR_RED}${LANG[WARP_UPDATE_FAIL]}: Invalid response${COLOR_RESET}" fi local config_json if echo "$config_data" | jq -e '.response.config' > /dev/null 2>&1; then config_json=$(echo "$config_data" | jq -r '.response.config') else config_json=$(echo "$config_data" | jq -r '.config // ""') fi if [ -z "$config_json" ] || [ "$config_json" == "null" ]; then echo -e "${COLOR_RED}${LANG[WARP_UPDATE_FAIL]}: No config found in response${COLOR_RESET}" fi if echo "$config_json" | jq -e '.outbounds[] | select(.tag == "warp-out")' > /dev/null 2>&1; then echo -e "${COLOR_YELLOW}${LANG[WARP_WARNING]}${COLOR_RESET}" else local warp_outbound='{ "tag": "warp-out", "protocol": "freedom", "settings": { "domainStrategy": "UseIP" }, "streamSettings": { "sockopt": { "interface": "warp", "tcpFastOpen": true } } }' config_json=$(echo "$config_json" | jq --argjson warp_out "$warp_outbound" '.outbounds += [$warp_out]' 2>/dev/null) fi if echo "$config_json" | jq -e '.routing.rules[] | select(.outboundTag == "warp-out")' > /dev/null 2>&1; then echo -e "${COLOR_YELLOW}${LANG[WARP_WARNING2]}${COLOR_RESET}" else local warp_rule='{ "type": "field", "domain": ["whoer.net", "browserleaks.com", "2ip.io", "2ip.ru"], "outboundTag": "warp-out" }' config_json=$(echo "$config_json" | jq --argjson warp_rule "$warp_rule" '.routing.rules += [$warp_rule]' 2>/dev/null) fi local update_response=$(make_api_request "PATCH" "${domain_url}/api/config-profiles" "$token" "{\"uuid\": \"$selected_uuid\", \"config\": $config_json}") if [ -z "$update_response" ] || ! echo "$update_response" | jq -e '.' > /dev/null 2>&1; then echo -e "${COLOR_RED}${LANG[WARP_UPDATE_FAIL]}: Invalid response${COLOR_RESET}" fi echo -e "${COLOR_GREEN}${LANG[WARP_UPDATE_SUCCESS]}${COLOR_RESET}" log_clear manage_warp ;; 4) local domain_url="127.0.0.1:3000" echo -e "" echo -e "${COLOR_RED}${LANG[WARNING_LABEL]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}${LANG[WARP_CONFIRM_SERVER_PANEL]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_GREEN}[?]${COLOR_RESET} ${COLOR_YELLOW}${LANG[CONFIRM_PROMPT]}${COLOR_RESET}" read confirm echo if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then echo -e "${COLOR_YELLOW}${LANG[EXIT]}${COLOR_RESET}" exit 0 fi get_panel_token token=$(cat "$TOKEN_FILE") local config_response=$(make_api_request "GET" "${domain_url}/api/config-profiles" "$token") if [ -z "$config_response" ] || ! echo "$config_response" | jq -e '.' > /dev/null 2>&1; then echo -e "${COLOR_RED}${LANG[WARP_NO_CONFIGS]}: Invalid response${COLOR_RESET}" return 1 fi if ! echo "$config_response" | jq -e '.response.configProfiles | type == "array"' > /dev/null 2>&1; then echo -e "${COLOR_RED}${LANG[WARP_NO_CONFIGS]}: Response does not contain configProfiles array${COLOR_RESET}" return 1 fi local config_count=$(echo "$config_response" | jq '.response.configProfiles | length') if [ "$config_count" -eq 0 ]; then echo -e "${COLOR_RED}${LANG[WARP_NO_CONFIGS]}: Empty configuration list${COLOR_RESET}" return 1 fi local configs=$(echo "$config_response" | jq -r '.response.configProfiles[] | select(.uuid and .name) | "\(.name) \(.uuid)"' 2>/dev/null) if [ -z "$configs" ]; then echo -e "${COLOR_RED}${LANG[WARP_NO_CONFIGS]}: No valid configurations found in response${COLOR_RESET}" return 1 fi echo -e "" echo -e "${COLOR_YELLOW}${LANG[WARP_SELECT_CONFIG_DELETE]}${COLOR_RESET}" echo -e "" local i=1 declare -A config_map while IFS=' ' read -r name uuid; do echo -e "${COLOR_YELLOW}$i. $name${COLOR_RESET}" config_map[$i]="$uuid" ((i++)) done <<< "$configs" echo -e "" echo -e "${COLOR_YELLOW}0. ${LANG[EXIT]}${COLOR_RESET}" echo -e "" reading "${LANG[WARP_PROMPT1]}" CONFIG_OPTION if [ "$CONFIG_OPTION" == "0" ]; then echo -e "${COLOR_YELLOW}${LANG[EXIT]}${COLOR_RESET}" return 0 fi if [ -z "${config_map[$CONFIG_OPTION]}" ]; then echo -e "${COLOR_RED}${LANG[WARP_INVALID_CHOICE2]}${COLOR_RESET}" return 1 fi local selected_uuid=${config_map[$CONFIG_OPTION]} local config_data=$(make_api_request "GET" "${domain_url}/api/config-profiles/$selected_uuid" "$token") if [ -z "$config_data" ] || ! echo "$config_data" | jq -e '.' > /dev/null 2>&1; then echo -e "${COLOR_RED}${LANG[WARP_UPDATE_FAIL]}: Invalid response${COLOR_RESET}" return 1 fi local config_json if echo "$config_data" | jq -e '.response.config' > /dev/null 2>&1; then config_json=$(echo "$config_data" | jq -r '.response.config') else config_json=$(echo "$config_data" | jq -r '.config // ""') fi if [ -z "$config_json" ] || [ "$config_json" == "null" ]; then echo -e "${COLOR_RED}${LANG[WARP_UPDATE_FAIL]}: No config found in response${COLOR_RESET}" return 1 fi if echo "$config_json" | jq -e '.outbounds[] | select(.tag == "warp-out")' > /dev/null 2>&1; then config_json=$(echo "$config_json" | jq 'del(.outbounds[] | select(.tag == "warp-out"))' 2>/dev/null) echo -e "${COLOR_YELLOW}${LANG[WARP_REMOVED_WARP_SETTINGS1]}${COLOR_RESET}" else echo -e "${COLOR_YELLOW}${LANG[WARP_NO_WARP_SETTINGS1]}${COLOR_RESET}" fi if echo "$config_json" | jq -e '.routing.rules[] | select(.outboundTag == "warp-out")' > /dev/null 2>&1; then config_json=$(echo "$config_json" | jq 'del(.routing.rules[] | select(.outboundTag == "warp-out"))' 2>/dev/null) echo -e "${COLOR_YELLOW}${LANG[WARP_REMOVED_WARP_SETTINGS2]}${COLOR_RESET}" else echo -e "${COLOR_YELLOW}${LANG[WARP_NO_WARP_SETTINGS2]}${COLOR_RESET}" fi local update_response=$(make_api_request "PATCH" "${domain_url}/api/config-profiles" "$token" "{\"uuid\": \"$selected_uuid\", \"config\": $config_json}") if [ -z "$update_response" ] || ! echo "$update_response" | jq -e '.' > /dev/null 2>&1; then echo -e "${COLOR_RED}${LANG[WARP_UPDATE_FAIL]}: Invalid response${COLOR_RESET}" return 1 fi echo -e "${COLOR_GREEN}${LANG[WARP_DELETE_SUCCESS]}${COLOR_RESET}" log_clear manage_warp ;; 0) echo -e "${COLOR_YELLOW}${LANG[EXIT]}${COLOR_RESET}" ;; *) echo -e "${COLOR_RED}${LANG[WARP_INVALID_CHOICE]}${COLOR_RESET}" ;; esac } #Manage IPv6 show_ipv6_menu() { echo -e "" echo -e "${COLOR_GREEN}${LANG[IPV6_MENU_TITLE]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}1. ${LANG[IPV6_ENABLE]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}2. ${LANG[IPV6_DISABLE]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}0. ${LANG[EXIT]}${COLOR_RESET}" echo -e "" } manage_ipv6() { show_ipv6_menu reading "${LANG[IPV6_PROMPT]}" IPV6_OPTION case $IPV6_OPTION in 1) enable_ipv6 sleep 2 log_clear manage_ipv6 ;; 2) disable_ipv6 sleep 2 log_clear manage_ipv6 ;; 0) echo -e "${COLOR_YELLOW}${LANG[EXIT]}${COLOR_RESET}" log_clear remnawave_reverse ;; *) echo -e "${COLOR_YELLOW}${LANG[IPV6_INVALID_CHOICE]}${COLOR_RESET}" sleep 2 log_clear manage_ipv6 ;; esac } enable_ipv6() { if [ "$(sysctl -n net.ipv6.conf.all.disable_ipv6)" -eq 0 ]; then echo -e "${COLOR_YELLOW}${LANG[IPV6_ALREADY_ENABLED]}${COLOR_RESET}" return 0 fi echo -e "${COLOR_YELLOW}${LANG[ENABLE_IPV6]}${COLOR_RESET}" interface_name=$(ip -o link show | awk -F': ' '{print $2}' | grep -v lo | head -n 1) sed -i '/net.ipv6.conf.all.disable_ipv6/d' /etc/sysctl.conf sed -i '/net.ipv6.conf.default.disable_ipv6/d' /etc/sysctl.conf sed -i '/net.ipv6.conf.lo.disable_ipv6/d' /etc/sysctl.conf sed -i "/net.ipv6.conf.$interface_name.disable_ipv6/d" /etc/sysctl.conf echo "net.ipv6.conf.all.disable_ipv6 = 0" >> /etc/sysctl.conf echo "net.ipv6.conf.default.disable_ipv6 = 0" >> /etc/sysctl.conf echo "net.ipv6.conf.lo.disable_ipv6 = 0" >> /etc/sysctl.conf echo "net.ipv6.conf.$interface_name.disable_ipv6 = 0" >> /etc/sysctl.conf sysctl -p > /dev/null 2>&1 echo -e "${COLOR_GREEN}${LANG[IPV6_ENABLED]}${COLOR_RESET}" } disable_ipv6() { if [ "$(sysctl -n net.ipv6.conf.all.disable_ipv6)" -eq 1 ]; then echo -e "${COLOR_YELLOW}${LANG[IPV6_ALREADY_DISABLED]}${COLOR_RESET}" return 0 fi echo -e "${COLOR_YELLOW}${LANG[DISABLING_IPV6]}${COLOR_RESET}" interface_name=$(ip -o link show | awk -F': ' '{print $2}' | grep -v lo | head -n 1) sed -i '/net.ipv6.conf.all.disable_ipv6/d' /etc/sysctl.conf sed -i '/net.ipv6.conf.default.disable_ipv6/d' /etc/sysctl.conf sed -i '/net.ipv6.conf.lo.disable_ipv6/d' /etc/sysctl.conf sed -i "/net.ipv6.conf.$interface_name.disable_ipv6/d" /etc/sysctl.conf echo "net.ipv6.conf.all.disable_ipv6 = 1" >> /etc/sysctl.conf echo "net.ipv6.conf.default.disable_ipv6 = 1" >> /etc/sysctl.conf echo "net.ipv6.conf.lo.disable_ipv6 = 1" >> /etc/sysctl.conf echo "net.ipv6.conf.$interface_name.disable_ipv6 = 1" >> /etc/sysctl.conf sysctl -p > /dev/null 2>&1 echo -e "${COLOR_GREEN}${LANG[IPV6_DISABLED]}${COLOR_RESET}" } #Manage IPv6 #Extensions by legiz show_custom_legiz_menu() { echo -e "" echo -e "${COLOR_GREEN}${LANG[MENU_5]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}1. ${LANG[SELECT_SUB_PAGE_CUSTOM1]}${COLOR_RESET}" # Custom sub page echo -e "${COLOR_YELLOW}2. ${LANG[CUSTOM_APP_LIST_MENU]}${COLOR_RESET}" # Edit custom app list and branding echo -e "" echo -e "${COLOR_YELLOW}0. ${LANG[EXIT]}${COLOR_RESET}" echo -e "" } manage_custom_legiz() { show_custom_legiz_menu reading "${LANG[LEGIZ_EXTENSIONS_PROMPT]}" LEGIZ_OPTION case $LEGIZ_OPTION in 1) if ! command -v yq >/dev/null 2>&1; then echo -e "${COLOR_YELLOW}${LANG[INSTALLING_YQ]}${COLOR_RESET}" if ! wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq >/dev/null 2>&1; then echo -e "${COLOR_RED}${LANG[ERROR_DOWNLOADING_YQ]}${COLOR_RESET}" sleep 2 log_clear manage_custom_legiz return 1 fi if ! chmod +x /usr/bin/yq; then echo -e "${COLOR_RED}${LANG[ERROR_SETTING_YQ_PERMISSIONS]}${COLOR_RESET}" sleep 2 log_clear manage_custom_legiz return 1 fi echo -e "${COLOR_GREEN}${LANG[YQ_SUCCESSFULLY_INSTALLED]}${COLOR_RESET}" sleep 1 fi if ! /usr/bin/yq --version >/dev/null 2>&1; then echo -e "${COLOR_RED}${LANG[YQ_DOESNT_WORK_AFTER_INSTALLATION]}${COLOR_RESET}" sleep 2 log_clear manage_custom_legiz return 1 fi manage_sub_page_upload log_clear manage_custom_legiz ;; 2) manage_custom_app_list log_clear manage_custom_legiz ;; 0) echo -e "${COLOR_YELLOW}${LANG[EXIT]}${COLOR_RESET}" return 0 ;; *) echo -e "${COLOR_YELLOW}${LANG[IPV6_INVALID_CHOICE]}${COLOR_RESET}" sleep 2 log_clear manage_custom_legiz ;; esac } show_sub_page_menu() { echo -e "" echo -e "${COLOR_GREEN}${LANG[SELECT_SUB_PAGE_CUSTOM2]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_GREEN}${LANG[SELECT_SUB_PAGE_CUSTOM3]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}1. Simple custom app list (clash and sing)${COLOR_RESET}" echo -e "${COLOR_YELLOW}2. Multiapp custom app list${COLOR_RESET}" echo -e "${COLOR_YELLOW}3. HWID only custom app list${COLOR_RESET}" echo -e "" echo -e "${COLOR_GREEN}${LANG[SELECT_SUB_PAGE_CUSTOM4]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}4. Orion web page template (support custom app list)${COLOR_RESET}" echo -e "${COLOR_YELLOW}5. Material web page template (support custom app list)${COLOR_RESET}" echo -e "${COLOR_YELLOW}6. Marzbanify web page template (clash and sing)${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}7. ${LANG[RESTORE_SUB_PAGE]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}0. ${LANG[EXIT]}${COLOR_RESET}" echo -e "" } download_with_fallback() { local primary_url="$1" local fallback_url="$2" local output_file="$3" if curl -s -L -f -o "$output_file" "$primary_url"; then return 0 else echo -e "${COLOR_YELLOW}${LANG[DOWNLOAD_FALLBACK]}${COLOR_RESET}" if curl -s -L -f -o "$output_file" "$fallback_url"; then return 0 else return 1 fi fi } branding_add_to_appconfig() { local config_file="$1" if [ ! -f "$config_file" ]; then echo -e "${COLOR_RED}Config file not found: $config_file${COLOR_RESET}" return 1 fi echo -e "${COLOR_GREEN}${LANG[BRANDING_SUPPORT_ASK]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}1. ${LANG[BRANDING_SUPPORT_YES]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}2. ${LANG[BRANDING_SUPPORT_NO]}${COLOR_RESET}" echo -e "" reading "${LANG[EXTENSIONS_PROMPT]}" BRANDING_OPTION case $BRANDING_OPTION in 1) reading "${LANG[BRANDING_NAME_PROMPT]}" BRAND_NAME reading "${LANG[BRANDING_SUPPORT_URL_PROMPT]}" SUPPORT_URL reading "${LANG[BRANDING_LOGO_URL_PROMPT]}" LOGO_URL jq --arg name "$BRAND_NAME" \ --arg supportUrl "$SUPPORT_URL" \ --arg logoUrl "$LOGO_URL" \ '.config.branding = { "name": $name, "supportUrl": $supportUrl, "logoUrl": $logoUrl }' "$config_file" > "${config_file}.tmp" && mv "${config_file}.tmp" "$config_file" echo -e "" echo -e "${COLOR_GREEN}${LANG[BRANDING_ADDED_SUCCESS]}${COLOR_RESET}" ;; 2) echo -e "${COLOR_YELLOW}${LANG[BRANDING_SUPPORT_NO]}${COLOR_RESET}" ;; *) echo -e "${COLOR_YELLOW}${LANG[INVALID_CHOICE]}${COLOR_RESET}" ;; esac } manage_sub_page_upload() { if [ -d "/opt/remnawave/index.html" ] || [ -d "/opt/remnawave/app-config.json" ]; then rm -rf "/opt/remnawave/index.html" "/opt/remnawave/app-config.json" fi if ! docker ps -a --filter "name=remnawave-subscription-page" --format '{{.Names}}' | grep -q "^remnawave-subscription-page$"; then printf "${COLOR_RED}${LANG[CONTAINER_NOT_FOUND]}${COLOR_RESET}\n" "remnawave-subscription-page" sleep 2 log_clear exit 1 fi show_sub_page_menu reading "${LANG[SELECT_SUB_PAGE_CUSTOM]}" SUB_PAGE_OPTION local config_file="/opt/remnawave/app-config.json" local index_file="/opt/remnawave/index.html" local docker_compose_file="/opt/remnawave/docker-compose.yml" case $SUB_PAGE_OPTION in 1|2|3) [ -f "$index_file" ] && rm -f "$index_file" echo -e "${COLOR_YELLOW}${LANG[UPLOADING_SUB_PAGE]}${COLOR_RESET}" echo -e "" local primary_url="https://raw.githubusercontent.com/legiz-ru/my-remnawave/refs/heads/main/sub-page/app-config.json" local fallback_url="https://cdn.jsdelivr.net/gh/legiz-ru/my-remnawave@main/sub-page/app-config.json" if [ "$SUB_PAGE_OPTION" == "2" ]; then primary_url="https://raw.githubusercontent.com/legiz-ru/my-remnawave/refs/heads/main/sub-page/multiapp/app-config.json" fallback_url="https://cdn.jsdelivr.net/gh/legiz-ru/my-remnawave@main/sub-page/multiapp/app-config.json" elif [ "$SUB_PAGE_OPTION" == "3" ]; then primary_url="https://raw.githubusercontent.com/legiz-ru/my-remnawave/refs/heads/main/sub-page/hwid/app-config.json" fallback_url="https://cdn.jsdelivr.net/gh/legiz-ru/my-remnawave@main/sub-page/hwid/app-config.json" fi if ! download_with_fallback "$primary_url" "$fallback_url" "$config_file"; then echo -e "${COLOR_RED}${LANG[ERROR_FETCH_SUB_PAGE]}${COLOR_RESET}" sleep 2 log_clear return 1 fi branding_add_to_appconfig "$config_file" /usr/bin/yq eval 'del(.services."remnawave-subscription-page".volumes)' -i "$docker_compose_file" /usr/bin/yq eval '.services."remnawave-subscription-page".volumes += ["./app-config.json:/opt/app/frontend/assets/app-config.json"]' -i "$docker_compose_file" ;; 4) [ -f "$config_file" ] && rm -f "$config_file" [ -f "$index_file" ] && rm -f "$index_file" echo -e "${COLOR_YELLOW}${LANG[UPLOADING_SUB_PAGE]}${COLOR_RESET}" echo -e "" local primary_index_url="https://raw.githubusercontent.com/legiz-ru/Orion/refs/heads/main/index.html" local fallback_index_url="https://cdn.jsdelivr.net/gh/legiz-ru/Orion@main/index.html" if ! download_with_fallback "$primary_index_url" "$fallback_index_url" "$index_file"; then echo -e "${COLOR_RED}${LANG[ERROR_FETCH_SUB_PAGE]}${COLOR_RESET}" sleep 2 log_clear return 1 fi echo -e "${COLOR_GREEN}${LANG[SUB_WITH_APPCONFIG_ASK]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}1. ${LANG[SUB_WITH_APPCONFIG_OPTION1]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}2. ${LANG[SUB_WITH_APPCONFIG_OPTION2]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}3. ${LANG[SUB_WITH_APPCONFIG_OPTION3]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}0. ${LANG[SUB_WITH_APPCONFIG_SKIP]}${COLOR_RESET}" echo -e "" reading "${LANG[SUB_WITH_APPCONFIG_INPUT]}" SUB_WITH_APPCONFIG case $SUB_WITH_APPCONFIG in 1|2|3) local primary_config_url="https://raw.githubusercontent.com/legiz-ru/my-remnawave/refs/heads/main/sub-page/app-config.json" local fallback_config_url="https://cdn.jsdelivr.net/gh/legiz-ru/my-remnawave@main/sub-page/app-config.json" if [ "$SUB_WITH_APPCONFIG" == "2" ]; then primary_config_url="https://raw.githubusercontent.com/legiz-ru/my-remnawave/refs/heads/main/sub-page/multiapp/app-config.json" fallback_config_url="https://cdn.jsdelivr.net/gh/legiz-ru/my-remnawave@main/sub-page/multiapp/app-config.json" elif [ "$SUB_WITH_APPCONFIG" == "3" ]; then primary_config_url="https://raw.githubusercontent.com/legiz-ru/my-remnawave/refs/heads/main/sub-page/hwid/app-config.json" fallback_config_url="https://cdn.jsdelivr.net/gh/legiz-ru/my-remnawave@main/sub-page/hwid/app-config.json" fi if ! download_with_fallback "$primary_config_url" "$fallback_config_url" "$config_file"; then echo -e "${COLOR_RED}${LANG[ERROR_FETCH_SUB_PAGE]}${COLOR_RESET}" sleep 2 log_clear return 1 fi branding_add_to_appconfig "$config_file" ;; 0) [ -f "$config_file" ] && rm -f "$config_file" [ -f "$index_file" ] && rm -f "$index_file" ;; *) echo -e "${COLOR_RED}${LANG[SUB_WITH_APPCONFIG_INVALID]}${COLOR_RESET}" [ -f "$config_file" ] && rm -f "$config_file" ;; esac /usr/bin/yq eval 'del(.services."remnawave-subscription-page".volumes)' -i "$docker_compose_file" /usr/bin/yq eval '.services."remnawave-subscription-page".volumes += ["./index.html:/opt/app/frontend/index.html"]' -i "$docker_compose_file" if [ -f "$config_file" ]; then /usr/bin/yq eval '.services."remnawave-subscription-page".volumes += ["./app-config.json:/opt/app/frontend/assets/app-config.json"]' -i "$docker_compose_file" fi ;; 5) [ -f "$config_file" ] && rm -f "$config_file" [ -f "$index_file" ] && rm -f "$index_file" echo -e "${COLOR_YELLOW}${LANG[UPLOADING_SUB_PAGE]}${COLOR_RESET}" echo -e "" local primary_index_url="https://raw.githubusercontent.com/legiz-ru/material-remnawave-subscription-page/refs/heads/main/index.html" local fallback_index_url="https://cdn.jsdelivr.net/gh/legiz-ru/material-remnawave-subscription-page@main/index.html" if ! download_with_fallback "$primary_index_url" "$fallback_index_url" "$index_file"; then echo -e "${COLOR_RED}${LANG[ERROR_FETCH_SUB_PAGE]}${COLOR_RESET}" sleep 2 log_clear return 1 fi echo -e "${COLOR_GREEN}${LANG[SUB_WITH_APPCONFIG_ASK]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}1. ${LANG[SUB_WITH_APPCONFIG_OPTION1]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}2. ${LANG[SUB_WITH_APPCONFIG_OPTION2]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}3. ${LANG[SUB_WITH_APPCONFIG_OPTION3]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}0. ${LANG[SUB_WITH_APPCONFIG_SKIP]}${COLOR_RESET}" echo -e "" reading "${LANG[SUB_WITH_APPCONFIG_INPUT]}" SUB_WITH_APPCONFIG case $SUB_WITH_APPCONFIG in 1|2|3) local primary_config_url="https://raw.githubusercontent.com/legiz-ru/my-remnawave/refs/heads/main/sub-page/app-config.json" local fallback_config_url="https://cdn.jsdelivr.net/gh/legiz-ru/my-remnawave@main/sub-page/app-config.json" if [ "$SUB_WITH_APPCONFIG" == "2" ]; then primary_config_url="https://raw.githubusercontent.com/legiz-ru/my-remnawave/refs/heads/main/sub-page/multiapp/app-config.json" fallback_config_url="https://cdn.jsdelivr.net/gh/legiz-ru/my-remnawave@main/sub-page/multiapp/app-config.json" elif [ "$SUB_WITH_APPCONFIG" == "3" ]; then primary_config_url="https://raw.githubusercontent.com/legiz-ru/my-remnawave/refs/heads/main/sub-page/hwid/app-config.json" fallback_config_url="https://cdn.jsdelivr.net/gh/legiz-ru/my-remnawave@main/sub-page/hwid/app-config.json" fi if ! download_with_fallback "$primary_config_url" "$fallback_config_url" "$config_file"; then echo -e "${COLOR_RED}${LANG[ERROR_FETCH_SUB_PAGE]}${COLOR_RESET}" sleep 2 log_clear return 1 fi branding_add_to_appconfig "$config_file" ;; 0) [ -f "$config_file" ] && rm -f "$config_file" [ -f "$index_file" ] && rm -f "$index_file" ;; *) echo -e "${COLOR_RED}${LANG[SUB_WITH_APPCONFIG_INVALID]}${COLOR_RESET}" [ -f "$config_file" ] && rm -f "$config_file" ;; esac /usr/bin/yq eval 'del(.services."remnawave-subscription-page".volumes)' -i "$docker_compose_file" /usr/bin/yq eval '.services."remnawave-subscription-page".volumes += ["./index.html:/opt/app/frontend/index.html"]' -i "$docker_compose_file" if [ -f "$config_file" ]; then /usr/bin/yq eval '.services."remnawave-subscription-page".volumes += ["./app-config.json:/opt/app/frontend/assets/app-config.json"]' -i "$docker_compose_file" fi ;; 6) [ -f "$config_file" ] && rm -f "$config_file" echo -e "${COLOR_YELLOW}${LANG[UPLOADING_SUB_PAGE]}${COLOR_RESET}" echo -e "" local primary_url="https://raw.githubusercontent.com/legiz-ru/my-remnawave/refs/heads/main/sub-page/customweb/clash-sing/index.html" local fallback_url="https://cdn.jsdelivr.net/gh/legiz-ru/my-remnawave@main/sub-page/customweb/clash-sing/index.html" if ! download_with_fallback "$primary_url" "$fallback_url" "$index_file"; then echo -e "${COLOR_RED}${LANG[ERROR_FETCH_SUB_PAGE]}${COLOR_RESET}" sleep 2 log_clear return 1 fi /usr/bin/yq eval 'del(.services."remnawave-subscription-page".volumes)' -i "$docker_compose_file" /usr/bin/yq eval '.services."remnawave-subscription-page".volumes += ["./index.html:/opt/app/frontend/index.html"]' -i "$docker_compose_file" ;; 7) [ -f "$config_file" ] && rm -f "$config_file" [ -f "$index_file" ] && rm -f "$index_file" /usr/bin/yq eval 'del(.services."remnawave-subscription-page".volumes)' -i "$docker_compose_file" ;; 0) echo -e "${COLOR_YELLOW}${LANG[EXIT]}${COLOR_RESET}" log_clear manage_custom_legiz ;; *) echo -e "${COLOR_YELLOW}${LANG[SUB_PAGE_SELECT_CHOICE]}${COLOR_RESET}" sleep 2 log_clear manage_sub_page_upload return 1 ;; esac /usr/bin/yq eval -i '... comments=""' "$docker_compose_file" sed -i -e '/^ [a-zA-Z-]\+:$/ { x; p; x; }' "$docker_compose_file" sed -i '/./,$!d' "$docker_compose_file" sed -i -e '/^networks:/i\' -e '' "$docker_compose_file" sed -i -e '/^volumes:/i\' -e '' "$docker_compose_file" cd /opt/remnawave || return 1 docker compose down remnawave-subscription-page > /dev/null 2>&1 & spinner $! "${LANG[WAITING]}" docker compose up -d remnawave-subscription-page > /dev/null 2>&1 & spinner $! "${LANG[WAITING]}" echo -e "${COLOR_GREEN}${LANG[SUB_PAGE_UPDATED_SUCCESS]}${COLOR_RESET}" } show_custom_app_menu() { echo -e "" echo -e "${COLOR_GREEN}${LANG[CUSTOM_APP_LIST_MENU]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}1. ${LANG[EDIT_BRANDING]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}2. ${LANG[DELETE_APPS]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}0. ${LANG[EXIT]}${COLOR_RESET}" echo -e "" } manage_custom_app_list() { local config_file="/opt/remnawave/app-config.json" if [ ! -f "$config_file" ]; then echo -e "${COLOR_RED}${LANG[CUSTOM_APP_LIST_NOT_FOUND]}${COLOR_RESET}" sleep 2 return 1 fi show_custom_app_menu reading "${LANG[IPV6_PROMPT]}" APP_OPTION case $APP_OPTION in 1) edit_branding "$config_file" ;; 2) delete_applications "$config_file" ;; 0) echo -e "${COLOR_YELLOW}${LANG[EXIT]}${COLOR_RESET}" return 0 ;; *) echo -e "${COLOR_YELLOW}${LANG[INVALID_CHOICE]}${COLOR_RESET}" sleep 2 manage_custom_app_list ;; esac } edit_branding() { local config_file="$1" local needs_restart=false # Check if branding exists if jq -e '.config.branding' "$config_file" > /dev/null 2>&1; then echo -e "" echo -e "${COLOR_GREEN}${LANG[BRANDING_CURRENT_VALUES]}${COLOR_RESET}" local logo_url=$(jq -r '.config.branding.logoUrl // "N/A"' "$config_file") local name=$(jq -r '.config.branding.name // "N/A"' "$config_file") local support_url=$(jq -r '.config.branding.supportUrl // "N/A"' "$config_file") echo -e "${COLOR_YELLOW}${LANG[BRANDING_LOGO_URL]} ${COLOR_WHITE}$logo_url${COLOR_RESET}" echo -e "${COLOR_YELLOW}${LANG[BRANDING_NAME]} ${COLOR_WHITE}$name${COLOR_RESET}" echo -e "${COLOR_YELLOW}${LANG[BRANDING_SUPPORT_URL]} ${COLOR_WHITE}$support_url${COLOR_RESET}" fi echo -e "" echo -e "${COLOR_GREEN}${LANG[EDIT_BRANDING]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}1. ${LANG[EDIT_LOGO]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}2. ${LANG[EDIT_NAME]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}3. ${LANG[EDIT_SUPPORT_URL]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}0. ${LANG[EXIT]}${COLOR_RESET}" echo -e "" reading "${LANG[IPV6_PROMPT]}" BRANDING_OPTION case $BRANDING_OPTION in 1) reading "${LANG[ENTER_NEW_LOGO]}" new_logo reading "${LANG[CONFIRM_CHANGE]}" confirm if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then jq --arg logo "$new_logo" '.config.branding.logoUrl = $logo' "$config_file" > "${config_file}.tmp" && mv "${config_file}.tmp" "$config_file" needs_restart=true fi ;; 2) reading "${LANG[ENTER_NEW_NAME]}" new_name reading "${LANG[CONFIRM_CHANGE]}" confirm if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then jq --arg name "$new_name" '.config.branding.name = $name' "$config_file" > "${config_file}.tmp" && mv "${config_file}.tmp" "$config_file" needs_restart=true fi ;; 3) reading "${LANG[ENTER_NEW_SUPPORT]}" new_support reading "${LANG[CONFIRM_CHANGE]}" confirm if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then jq --arg support "$new_support" '.config.branding.supportUrl = $support' "$config_file" > "${config_file}.tmp" && mv "${config_file}.tmp" "$config_file" needs_restart=true fi ;; 0) echo -e "${COLOR_YELLOW}${LANG[EXIT]}${COLOR_RESET}" return 0 ;; *) echo -e "${COLOR_YELLOW}${LANG[INVALID_CHOICE]}${COLOR_RESET}" sleep 2 edit_branding "$config_file" ;; esac # Restart container if changes were made if [ "$needs_restart" = true ]; then echo -e "" echo -e "${COLOR_GREEN}${LANG[BRANDING_ADDED_SUCCESS]}${COLOR_RESET}" # Restart subscription page container cd /opt/remnawave || return 1 docker compose down remnawave-subscription-page > /dev/null 2>&1 & spinner $! "${LANG[WAITING]}" docker compose up -d remnawave-subscription-page > /dev/null 2>&1 & spinner $! "${LANG[WAITING]}" fi } delete_applications() { local config_file="$1" # Get platforms with non-empty arrays local platforms=$(jq -r '.platforms | to_entries[] | select(.value | length > 0) | .key' "$config_file" 2>/dev/null) if [ -z "$platforms" ]; then echo -e "${COLOR_RED}${LANG[NO_APPS_FOUND]}${COLOR_RESET}" sleep 2 return 1 fi echo -e "" echo -e "${COLOR_GREEN}${LANG[PLATFORM_SELECT]}${COLOR_RESET}" echo -e "" local i=1 declare -A platform_map while IFS= read -r platform; do echo -e "${COLOR_YELLOW}$i. $platform${COLOR_RESET}" platform_map[$i]="$platform" ((i++)) done <<< "$platforms" echo -e "" echo -e "${COLOR_YELLOW}0. ${LANG[EXIT]}${COLOR_RESET}" echo -e "" reading "${LANG[IPV6_PROMPT]}" PLATFORM_OPTION if [ "$PLATFORM_OPTION" == "0" ]; then echo -e "${COLOR_YELLOW}${LANG[EXIT]}${COLOR_RESET}" return 0 fi if [ -z "${platform_map[$PLATFORM_OPTION]}" ]; then echo -e "${COLOR_RED}${LANG[INVALID_CHOICE]}${COLOR_RESET}" sleep 2 delete_applications "$config_file" return 1 fi local selected_platform=${platform_map[$PLATFORM_OPTION]} # Get applications from selected platform local apps=$(jq -r --arg platform "$selected_platform" '.platforms[$platform][] | .name // .id' "$config_file" 2>/dev/null) if [ -z "$apps" ]; then echo -e "${COLOR_RED}${LANG[NO_APPS_FOUND]}${COLOR_RESET}" sleep 2 return 1 fi echo -e "" echo -e "${COLOR_GREEN}${LANG[APP_SELECT]}${COLOR_RESET}" echo -e "" local j=1 declare -A app_map while IFS= read -r app; do echo -e "${COLOR_YELLOW}$j. $app${COLOR_RESET}" app_map[$j]="$app" ((j++)) done <<< "$apps" echo -e "" echo -e "${COLOR_YELLOW}0. ${LANG[EXIT]}${COLOR_RESET}" echo -e "" reading "${LANG[IPV6_PROMPT]}" APP_DELETE_OPTION if [ "$APP_DELETE_OPTION" == "0" ]; then echo -e "${COLOR_YELLOW}${LANG[EXIT]}${COLOR_RESET}" return 0 fi if [ -z "${app_map[$APP_DELETE_OPTION]}" ]; then echo -e "${COLOR_RED}${LANG[INVALID_CHOICE]}${COLOR_RESET}" sleep 2 delete_applications "$config_file" return 1 fi local selected_app=${app_map[$APP_DELETE_OPTION]} printf "${COLOR_YELLOW}${LANG[CONFIRM_DELETE_APP]}${COLOR_RESET}\n" "$selected_app" "$selected_platform" read confirm if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then # Remove the application from the platform array jq --arg platform "$selected_platform" --arg app_name "$selected_app" ' .platforms[$platform] = [.platforms[$platform][] | select((.name // .id) != $app_name)] ' "$config_file" > "${config_file}.tmp" && mv "${config_file}.tmp" "$config_file" echo -e "${COLOR_GREEN}${LANG[APP_DELETED_SUCCESS]}${COLOR_RESET}" # Restart subscription page container cd /opt/remnawave || return 1 docker compose down remnawave-subscription-page > /dev/null 2>&1 & spinner $! "${LANG[WAITING]}" docker compose up -d remnawave-subscription-page > /dev/null 2>&1 & spinner $! "${LANG[WAITING]}" else echo -e "${COLOR_YELLOW}${LANG[EXIT]}${COLOR_RESET}" fi } #Extensions by legiz add_cron_rule() { local rule="$1" local logged_rule="${rule} >> ${DIR_REMNAWAVE}cron_jobs.log 2>&1" if ! crontab -u root -l > /dev/null 2>&1; then crontab -u root -l 2>/dev/null | crontab -u root - fi if ! crontab -u root -l | grep -Fxq "$logged_rule"; then (crontab -u root -l 2>/dev/null; echo "$logged_rule") | crontab -u root - fi } spinner() { local pid=$1 local text=$2 export LC_ALL=C.UTF-8 export LANG=C.UTF-8 local spinstr='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏' local text_code="$COLOR_GREEN" local bg_code="" local effect_code="\033[1m" local delay=0.1 local reset_code="$COLOR_RESET" printf "${effect_code}${text_code}${bg_code}%s${reset_code}" "$text" > /dev/tty while kill -0 "$pid" 2>/dev/null; do for (( i=0; i<${#spinstr}; i++ )); do printf "\r${effect_code}${text_code}${bg_code}[%s] %s${reset_code}" "$(echo -n "${spinstr:$i:1}")" "$text" > /dev/tty sleep $delay done done printf "\r\033[K" > /dev/tty } #Manage Template for steal show_template_source_options() { echo -e "" echo -e "${COLOR_GREEN}${LANG[CHOOSE_TEMPLATE_SOURCE]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}1. ${LANG[SIMPLE_WEB_TEMPLATES]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}2. ${LANG[SNI_TEMPLATES]}${COLOR_RESET}" echo -e "${COLOR_YELLOW}3. ${LANG[NOTHING_TEMPLATES]}${COLOR_RESET}" echo -e "" echo -e "${COLOR_YELLOW}0. ${LANG[EXIT]}${COLOR_RESET}" echo -e "" } randomhtml() { local template_source="$1" cd /opt/ || { echo "${LANG[UNPACK_ERROR]}"; exit 1; } rm -f main.zip 2>/dev/null rm -rf simple-web-templates-main/ sni-templates-main/ nothing-sni-main/ 2>/dev/null echo -e "${COLOR_YELLOW}${LANG[RANDOM_TEMPLATE]}${COLOR_RESET}" sleep 1 spinner $$ "${LANG[WAITING]}" & spinner_pid=$! template_urls=( "https://github.com/eGamesAPI/simple-web-templates/archive/refs/heads/main.zip" "https://github.com/distillium/sni-templates/archive/refs/heads/main.zip" "https://github.com/prettyleaf/nothing-sni/archive/refs/heads/main.zip" ) if [ -z "$template_source" ]; then selected_url=${template_urls[$RANDOM % ${#template_urls[@]}]} else if [ "$template_source" = "simple" ]; then selected_url=${template_urls[0]} # Simple web templates elif [ "$template_source" = "sni" ]; then selected_url=${template_urls[1]} # Sni templates elif [ "$template_source" = "nothing" ]; then selected_url=${template_urls[2]} # Nothing templates else selected_url=${template_urls[1]} # Default to Sni templates fi fi while ! wget -q --timeout=30 --tries=10 --retry-connrefused "$selected_url"; do echo "${LANG[DOWNLOAD_FAIL]}" sleep 3 done unzip -o main.zip &>/dev/null || { echo "${LANG[UNPACK_ERROR]}"; exit 0; } rm -f main.zip if [[ "$selected_url" == *"eGamesAPI"* ]]; then cd simple-web-templates-main/ || { echo "${LANG[UNPACK_ERROR]}"; exit 0; } rm -rf assets ".gitattributes" "README.md" "_config.yml" 2>/dev/null elif [[ "$selected_url" == *"nothing-sni"* ]]; then cd nothing-sni-main/ || { echo "${LANG[UNPACK_ERROR]}"; exit 0; } rm -rf .github README.md 2>/dev/null else cd sni-templates-main/ || { echo "${LANG[UNPACK_ERROR]}"; exit 0; } rm -rf assets "README.md" "index.html" 2>/dev/null fi # Special handling for nothing-sni - select random HTML file if [[ "$selected_url" == *"nothing-sni"* ]]; then # Randomly select one HTML file from 1-8.html selected_number=$((RANDOM % 8 + 1)) RandomHTML="${selected_number}.html" else mapfile -t templates < <(find . -maxdepth 1 -type d -not -path . | sed 's|./||') RandomHTML="${templates[$RANDOM % ${#templates[@]}]}" fi if [[ "$selected_url" == *"distillium"* && "$RandomHTML" == "503 error pages" ]]; then cd "$RandomHTML" || { echo "${LANG[UNPACK_ERROR]}"; exit 0; } versions=("v1" "v2") RandomVersion="${versions[$RANDOM % ${#versions[@]}]}" RandomHTML="$RandomHTML/$RandomVersion" cd .. fi local random_meta_id=$(openssl rand -hex 16) local random_comment=$(openssl rand -hex 8) local random_class_suffix=$(openssl rand -hex 4) local random_title_prefix="Page_" local random_title_suffix=$(openssl rand -hex 4) local random_footer_text="Designed by RandomSite_${random_title_suffix}" local random_id_suffix=$(openssl rand -hex 4) local meta_names=("viewport-id" "session-id" "track-id" "render-id" "page-id" "config-id") local meta_usernames=("Payee6296" "UserX1234" "AlphaBeta" "GammaRay" "DeltaForce" "EchoZulu" "Foxtrot99" "HotelCalifornia" "IndiaInk" "JulietBravo") local random_meta_name=${meta_names[$RANDOM % ${#meta_names[@]}]} local random_username=${meta_usernames[$RANDOM % ${#meta_usernames[@]}]} local class_prefixes=("style" "data" "ui" "layout" "theme" "view") local random_class_prefix=${class_prefixes[$RANDOM % ${#class_prefixes[@]}]} local random_class="$random_class_prefix-$random_class_suffix" local random_title="${random_title_prefix}${random_title_suffix}" find "./$RandomHTML" -type f -name "*.html" -exec sed -i \ -e "s|||" \ -e "s|||" \ -e "s|Free Website Templates|${random_footer_text}|" \ -e "s|WebThemez.com|${random_footer_text}|" \ -e "s|id=\"Content\"|id=\"rnd_${random_id_suffix}\"|" \ -e "s|id=\"subscribe\"|id=\"sub_${random_id_suffix}\"|" \ -e "s|