diff --git a/README.md b/README.md index 2280a00..fbd3c66 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ Features: * Supports Cloudflare warp+ * Install with a single command * Telegram bot for user management +* Create backup from users and configuration +* Restore users and configuration from backup Supported OS: * Ubuntu 22.04 @@ -76,7 +78,7 @@ Help message of the script: Usage: reality-ezpz.sh [-t|--transport=tcp|http|grpc|ws|tuic|hysteria2] [-d|--domain=<domain>] [--server=<server>] [--regenerate] [--default] [-r|--restart] [--enable-safenet=true|false] [--port=<port>] [-c|--core=xray|sing-box] [--enable-warp=true|false] [--warp-license=<license>] [--security=reality|letsencrypt|selfsigned] [-m|--menu] [--show-server-config] - [--add-user=<username>] [--lists-users] [--show-user=<username>] [--delete-user=<username>] [-u|--uninstall] + [--add-user=<username>] [--lists-users] [--show-user=<username>] [--delete-user=<username>] [--backup] [--restore=<url|file>] [-u|--uninstall] -t, --transport <tcp|http|grpc|ws|tuic|hysteria2> Transport protocol (tcp, http, grpc, ws, tuic, hysteria2, default: tcp) -d, --domain <domain> Domain to use as SNI (default: www.google.com) @@ -100,6 +102,8 @@ Usage: reality-ezpz.sh [-t|--transport=tcp|http|grpc|ws|tuic|hysteria2] [-d|--do --list-users List all users --show-user <username> Shows the config and QR code of the user --delete-user <username> Delete the user + --backup Backup users and configuration and upload it to keep.sh + --restore <url|file> Restore backup from URL or file -h, --help Display this help message ``` @@ -280,6 +284,30 @@ bash <(curl -sL https://bit.ly/realityez) -c xray ``` Valid options are `xray` and `sing-box`. +### Create backup +You can create a backup from users and configuration and upload it to free.keep.sh by using `--backup` option: +``` +bash <(curl -sL https://bit.ly/realityez) --backup +``` +This command will give you a URL to download you backup file. The URL is only valid for 24h. + +### Restore backup +You can restore a previously created backup file with `--restore` option. + +You need to give the path or URL of the backup file to restore: +``` +bash <(curl -sL https://bit.ly/realityez) --restore /path/to/backup.tar.gz +``` +or +``` +bash <(curl -sL https://bit.ly/realityez) --restore "https://www.example.com/backup.tar.gz" +``` + +You can migrate users and configuration from one server to another by: + +1. Create backup in the old server and copy the URL of backup file +1. Restore the URL of backup file in the new server + ### Text-based user interface (TUI) You can also use the TUI for changing the configuration of the service. diff --git a/reality-ezpz.sh b/reality-ezpz.sh index 2dba7c2..ea4553c 100644 --- a/reality-ezpz.sh +++ b/reality-ezpz.sh @@ -98,13 +98,15 @@ regex[ip]="^([0-9]{1,3}\.){3}[0-9]{1,3}$" regex[tgbot_token]="^[0-9]{8,10}:[a-zA-Z0-9_-]{35}$" regex[tgbot_admins]="^[a-zA-Z][a-zA-Z0-9_]{4,31}(,[a-zA-Z][a-zA-Z0-9_]{4,31})*$" regex[domain_port]="^[a-zA-Z0-9]+([-.][a-zA-Z0-9]+)*\.[a-zA-Z]{2,}(:[1-9][0-9]*)?$" +regex[file_path]="^[a-zA-Z0-9_/.-]+$" +regex[url]="^(http|https)://([a-zA-Z0-9.-]+\.[a-zA-Z]{2,}|[0-9]{1,3}(\.[0-9]{1,3}){3})(:[0-9]{1,5})?(/.*)?$" function show_help { echo "" echo "Usage: reality-ezpz.sh [-t|--transport=tcp|http|grpc|ws|tuic|hysteria2] [-d|--domain=<domain>] [--server=<server>] [--regenerate] [--default] [-r|--restart] [--enable-safenet=true|false] [--port=<port>] [-c|--core=xray|sing-box] [--enable-warp=true|false] [--warp-license=<license>] [--security=reality|letsencrypt|selfsigned] [-m|--menu] [--show-server-config] - [--add-user=<username>] [--lists-users] [--show-user=<username>] [--delete-user=<username>] [-u|--uninstall]" + [--add-user=<username>] [--lists-users] [--show-user=<username>] [--delete-user=<username>] [--backup] [--restore=<url|file>] [-u|--uninstall]" echo "" echo " -t, --transport <tcp|http|grpc|ws|tuic|hysteria2> Transport protocol (tcp, http, grpc, ws, tuic, hysteria2, default: ${defaults[transport]})" echo " -d, --domain <domain> Domain to use as SNI (default: ${defaults[domain]})" @@ -128,13 +130,15 @@ function show_help { echo " --list-users List all users" echo " --show-user <username> Shows the config and QR code of the user" echo " --delete-user <username> Delete the user" + echo " --backup Backup users and configuration and upload it to keep.sh" + echo " --restore <url|file> Restore backup from URL or file" echo " -h, --help Display this help message" return 1 } function parse_args { local opts - opts=$(getopt -o t:d:ruc:mh --long transport:,domain:,server:,regenerate,default,restart,uninstall,enable-safenet:,port:,warp-license:,enable-warp:,core:,security:,menu,show-server-config,add-user:,list-users,show-user:,delete-user:,enable-tgbot:,tgbot-token:,tgbot-admins:,help -- "$@") + opts=$(getopt -o t:d:ruc:mh --long transport:,domain:,server:,regenerate,default,restart,uninstall,enable-safenet:,port:,warp-license:,enable-warp:,core:,security:,menu,show-server-config,add-user:,list-users,show-user:,delete-user:,backup,restore:,enable-tgbot:,tgbot-token:,tgbot-admins:,help -- "$@") if [[ $? -ne 0 ]]; then return 1 fi @@ -312,6 +316,18 @@ function parse_args { args[delete_user]="$2" shift 2 ;; + --backup) + args[backup]=true + shift + ;; + --restore) + args[restore]="$2" + if [[ ! ${args[restore]} =~ ${regex[file_path]} ]] && [[ ! ${args[restore]} =~ ${regex[url]} ]]; then + echo "Invalid: Backup file path or URL is not valid." + return 1 + fi + shift 2 + ;; -h|--help) return 1 ;; @@ -333,7 +349,49 @@ function parse_args { if [[ -n ${args[warp_license]} ]]; then args[warp]=ON fi +} +function backup { + local backup_name + local backup_file_url + local exit_code + backup_name=reality-ezpz-backup-$(date +%Y-%m-%d_%H-%M-%S).tar.gz + tar -czf "/tmp/${backup_name}" -C "${config_path}" ./ + if ! backup_file_url=$(curl -fsS --upload-file "/tmp/${backup_name}" https://free.keep.sh); then + rm -f "/tmp/${backup_name}" + echo "Error in uploading backup file" >&2 + return 1 + fi + rm -f "/tmp/${backup_name}" + echo "${backup_file_url}" + return +} + +function restore { + local backup_file=$1 + local temp_file + if [[ ! -r ${backup_file} ]]; then + temp_file=$(mktemp -u) + if ! curl -fSsL "${backup_file}" -o "${temp_file}"; then + echo "Cannot download or find backup file" >&2 + return 1 + fi + backup_file="${temp_file}" + fi + if ! tar -tzf "${backup_file}" | grep -q config; then + echo "The provided file is not a reality-ezpz backup file." >&2 + rm -f "${temp_file}" + return 1 + fi + rm -rf "${config_path}" + mkdir -p "${config_path}" + if ! tar -xzf "${backup_file}" -C "${config_path}"; then + echo "Error in backup restore." >&2 + rm -f "${temp_file}" + return 1 + fi + rm -f "${temp_file}" + return } function dict_expander { @@ -707,7 +765,7 @@ services: BOT_ADMIN: ${config[tgbot_admins]} volumes: - /var/run/docker.sock:/var/run/docker.sock - - ../:/opt/reality-ezpz + - ../:${config_path} - /etc/docker/:/etc/docker/ networks: - tgbot @@ -853,7 +911,7 @@ EOF function generate_tgbot_dockerfile { cat >"${path[tgbot_dockerfile]}" << EOF FROM ${image[python]} -WORKDIR /opt/reality-ezpz/tgbot +WORKDIR ${config_path}/tgbot RUN apk add --no-cache docker-cli-compose curl bash newt libqrencode sudo openssl jq RUN pip install --no-cache-dir python-telegram-bot==13.5 CMD [ "python", "./tgbot.py" ] @@ -1336,7 +1394,7 @@ function upgrade { local warp_id if [[ -e "${HOME}/reality/config" ]]; then ${docker_cmd} --project-directory "${HOME}/reality" down --remove-orphans --timeout 2 - mv -f "${HOME}/reality" /opt/reality-ezpz + mv -f "${HOME}/reality" ${config_path} fi uuid=$(grep '^uuid=' "${path[config]}" 2>/dev/null | cut -d= -f2 || true) if [[ -n $uuid ]]; then @@ -1442,7 +1500,7 @@ function add_user_menu { break fi view_user_menu "${username}" - done + done } function delete_user_menu { @@ -1654,6 +1712,8 @@ function configuration_menu { "10" "Restart Services" \ "11" "Regenerate Keys" \ "12" "Restore Defaults" \ + "13" "Create Backup" \ + "14" "Restore Backup" \ 3>&1 1>&2 2>&3) if [[ $? -ne 0 ]]; then break @@ -1695,6 +1755,12 @@ function configuration_menu { 12 ) restore_defaults_menu ;; + 13 ) + backup_menu + ;; + 14 ) + restore_backup_menu + ;; esac done } @@ -2030,6 +2096,64 @@ function config_tgbot_menu { config[tgbot_admins]=$old_tgbot_admins } +function backup_menu { + local result + whiptail \ + --clear \ + --backtitle "$BACKTITLE" \ + --title "Backup" \ + --yesno "Do you want to create a backup from users and configuration?" \ + $HEIGHT $WIDTH \ + 3>&1 1>&2 2>&3 + if [[ $? -ne 0 ]]; then + return + fi + if result=$(backup 2>&1); then + echo "Backup has been create and uploaded successfully." + echo "You can download the backup file from here:" + echo "${result}" + echo "" + echo "The URL is valid for 24h." + echo + echo "Press Enter to return ..." + read + else + message_box "Backup Failed" "${result}" + fi +} + +function restore_backup_menu { + local backup_file + local result + while true; do + backup_file=$(whiptail \ + --clear \ + --backtitle "$BACKTITLE" \ + --title "Restore Backup" \ + --inputbox "Enter backup file path or URL" \ + $HEIGHT $WIDTH \ + 3>&1 1>&2 2>&3) + if [[ $? -ne 0 ]]; then + break + fi + if [[ ! $backup_file =~ ${regex[file_path]} ]] && [[ ! $backup_file =~ ${regex[url]} ]]; then + message_box "Invalid Backup path of URL" "Backup file path or URL is not valid." + continue + fi + if result=$(restore ${backup_file} 2>&1); then + parse_config_file + parse_users_file + build_config + update_config_file + update_users_file + message_box "Backup Restore Successful" "Backup has been restored successfully." + break + else + message_box "Backup Restore Failed" "${result}" + fi + done +} + function restart_docker_compose { ${docker_cmd} --project-directory ${config_path} -p ${compose_project} down --remove-orphans --timeout 2 || true ${docker_cmd} --project-directory ${config_path} -p ${compose_project} up --build -d --remove-orphans --build @@ -2294,6 +2418,19 @@ if [[ $EUID -ne 0 ]]; then echo "This script must be run as root." exit 1 fi +if [[ ${args[backup]} == true ]]; then + if backup_url=$(backup); then + echo "Backup created successfully. You can download the backup file from this address:" + echo "${backup_url}" + echo "The URL is valid for 24h." + fi +fi +if [[ -n ${args[restore]} ]]; then + if restore ${args[restore]}; then + args[restart]=true + echo "Backup has been restored successfully." + fi +fi generate_file_list install_packages install_docker