From 8add078e7ca5967fd9d702bc16f858af0d8f9f41 Mon Sep 17 00:00:00 2001 From: Alexey Berezhok Date: Sun, 3 May 2026 00:54:14 +0300 Subject: [PATCH] Added update module --- bin/v-add-web-domain-ssl | 19 ++++ bin/v-bunkerweb-module | 35 ++++++ bin/v-delete-web-domain-ssl | 10 ++ bin/v-update-web-domain-ssl | 19 ++++ func_ruby/HestiaBunkerWebApi.rb | 56 ++++++++-- func_ruby/ext-modules/bunkerweb_module.mod | 20 +++- func_ruby/ext-modules/last_free_id | 1 + func_ruby/ext-modules/update_module.mod | 119 +++++++++++++++++++++ install/hst-install.sh | 23 +++- 9 files changed, 292 insertions(+), 10 deletions(-) create mode 100644 func_ruby/ext-modules/last_free_id create mode 100644 func_ruby/ext-modules/update_module.mod diff --git a/bin/v-add-web-domain-ssl b/bin/v-add-web-domain-ssl index 15f4954..3c04291 100755 --- a/bin/v-add-web-domain-ssl +++ b/bin/v-add-web-domain-ssl @@ -136,6 +136,25 @@ if [ -n "$UPDATE_SSL_SCRIPT" ]; then eval "$UPDATE_SSL_SCRIPT $user $domain" fi +# Execute bunkerweb_module if it's enabled +module_state=$($BIN/v-ext-modules state bunkerweb_module json | jq -r '.[0].STATE') +if [ "$module_state" = "enabled" ] && [ "$BUNKERWEB" = "yes" ]; then + BUNKW_DIR=$HOMEDIR/$user/conf/web/$domain/ssl/bunkerweb + if [ ! -d "$BUNKW_DIR" ]; then + mkdir -p "$BUNKW_DIR" + chmod 0755 "$BUNKW_DIR" + fi + cp -f "$HOMEDIR/$user/conf/web/$domain/ssl/$domain.crt" "$BUNKW_DIR/" + cp -f "$HOMEDIR/$user/conf/web/$domain/ssl/$domain.key" "$BUNKW_DIR/" + cp -f "$HOMEDIR/$user/conf/web/$domain/ssl/$domain.pem" "$BUNKW_DIR/" + if [ -e "$HOMEDIR/$user/conf/web/$domain/ssl/$domain.ca" ]; then + cp -f "$HOMEDIR/$user/conf/web/$domain/ssl/$domain.ca" "$BUNKW_DIR/" + fi + chown root:nginx "$BUNKW_DIR"/* + chmod 0640 "$BUNKW_DIR"/* + $BIN/v-ext-modules-run bunkerweb_module addssl "$domain" "$BUNKW_DIR/$domain.pem" "$BUNKW_DIR/$domain.key" +fi + # Logging $BIN/v-log-action "$user" "Info" "Web" "Added certificate and enabled SSL (Domain: $domain)." log_event "$OK" "$ARGUMENTS" diff --git a/bin/v-bunkerweb-module b/bin/v-bunkerweb-module index 64a452f..4e55eb2 100755 --- a/bin/v-bunkerweb-module +++ b/bin/v-bunkerweb-module @@ -165,6 +165,41 @@ when :addssl, :updssl exit 1 end end +when :deletessl + + v_domain = ARGV[1].strip + v_format = ARGV[2] unless ARGV[2].nil? + + if v_domain.nil? || v_domain == "" + hestia_print_error_message_to_cli "domain should not be empty" + log_event E_ARGS, $ARGUMENTS + exit 1 + else + + begin + api = HestiaBunkerWebApi.new("http://127.0.0.1:8888") + existing_services = api.list_services() + if existing_services.nil? + result_arr = [] + else + if existing_services["services"] + result_arr = existing_services["services"] + else + result_arr = [] + end + end + unless result_arr.any? { |s| s["id"] == v_domain } + hestia_print_error_message_to_cli "domain does not exist" + log_event E_NOTEXIST, $ARGUMENTS + exit 1 + end + api.delete_service_ssl(v_domain) + rescue BunkerWebApiError => e + hestia_print_error_message_to_cli "[ERROR] Ошибка API: #{e.message}" + log_event E_INVALID, $ARGUMENTS + exit 1 + end + end when :list v_format = ARGV[1] unless ARGV[1].nil? diff --git a/bin/v-delete-web-domain-ssl b/bin/v-delete-web-domain-ssl index 5430959..f8140a2 100755 --- a/bin/v-delete-web-domain-ssl +++ b/bin/v-delete-web-domain-ssl @@ -90,6 +90,16 @@ check_result $? "Web restart failed" > /dev/null $BIN/v-restart-proxy "$restart" check_result $? "Proxy restart failed" > /dev/null +# Execute bunkerweb_module if it's enabled +module_state=$($BIN/v-ext-modules state bunkerweb_module json | jq -r '.[0].STATE') +if [ "$module_state" = "enabled" ] && [ "$BUNKERWEB" = "yes" ]; then + BUNKW_DIR=$HOMEDIR/$user/conf/web/$domain/ssl/bunkerweb + if [ -d "$BUNKW_DIR" ]; then + $BIN/v-ext-modules-run bunkerweb_module deletessl "$domain" + rm -f $BUNKW_DIR/$domain.* + fi +fi + # Logging $BIN/v-log-action "$user" "Warning" "Web" "SSL disabled (Domain: $domain)." log_event "$OK" "$ARGUMENTS" diff --git a/bin/v-update-web-domain-ssl b/bin/v-update-web-domain-ssl index 9cf2c39..e109d88 100755 --- a/bin/v-update-web-domain-ssl +++ b/bin/v-update-web-domain-ssl @@ -90,6 +90,25 @@ check_result $? "Web restart failed" > /dev/null $BIN/v-restart-proxy "$restart" check_result $? "Proxy restart failed" > /dev/null +# Execute bunkerweb_module if it's enabled +module_state=$($BIN/v-ext-modules state bunkerweb_module json | jq -r '.[0].STATE') +if [ "$module_state" = "enabled" ] && [ "$BUNKERWEB" = "yes" ]; then + BUNKW_DIR=$HOMEDIR/$user/conf/web/$domain/ssl/bunkerweb + if [ ! -d "$BUNKW_DIR" ]; then + mkdir -p "$BUNKW_DIR" + chmod 0755 "$BUNKW_DIR" + fi + cp -f "$HOMEDIR/$user/conf/web/$domain/ssl/$domain.crt" "$BUNKW_DIR/" + cp -f "$HOMEDIR/$user/conf/web/$domain/ssl/$domain.key" "$BUNKW_DIR/" + cp -f "$HOMEDIR/$user/conf/web/$domain/ssl/$domain.pem" "$BUNKW_DIR/" + if [ -e "$HOMEDIR/$user/conf/web/$domain/ssl/$domain.ca" ]; then + cp -f "$HOMEDIR/$user/conf/web/$domain/ssl/$domain.ca" "$BUNKW_DIR/" + fi + chown root:nginx "$BUNKW_DIR"/* + chmod 0640 "$BUNKW_DIR"/* + $BIN/v-ext-modules-run bunkerweb_module updssl "$domain" "$BUNKW_DIR/$domain.pem" "$BUNKW_DIR/$domain.key" +fi + # Logging $BIN/v-log-action "$user" "Info" "Web" "SSL certificate updated (Domain: $domain)." log_event "$OK" "$EVENT" diff --git a/func_ruby/HestiaBunkerWebApi.rb b/func_ruby/HestiaBunkerWebApi.rb index 80e20ee..96640bb 100644 --- a/func_ruby/HestiaBunkerWebApi.rb +++ b/func_ruby/HestiaBunkerWebApi.rb @@ -189,11 +189,12 @@ class HestiaBunkerWebApi variables = { "USE_TEMPLATE" => options[:use_template] || "high", "USE_REVERSE_PROXY" => options[:reverse_proxy_host].nil? ? "no" : "yes", + "LIMIT_REQ_RATE" => options[:limit_req_rate] || "10r/s", } # SSL configuration - only if USE_SSL != "no" ssl_enabled = options[:ssl] && options[:ssl] != "no" - variables["USE_SSL"] = ssl_enabled ? "yes" : "no" + variables["USE_CUSTOM_SSL"] = ssl_enabled ? "yes" : "no" if ssl_enabled unless options[:certificate_path] && options[:key_path] @@ -201,8 +202,8 @@ class HestiaBunkerWebApi end # Set certificate paths in variables - variables["SSL_CERTIFICATE_FILE_PATH"] = options[:certificate_path] - variables["SSL_KEY_FILE_PATH"] = options[:key_path] + variables["CUSTOM_SSL_CERT"] = options[:certificate_path] + variables["CUSTOM_SSL_KEY"] = options[:key_path] variables["LISTEN_HTTPS_PORT"] = (options[:https_port] || "443").to_s variables["USE_REVERSE_PROXY_SSL"] = options[:reverse_proxy_ssl] || "yes" @@ -216,7 +217,7 @@ class HestiaBunkerWebApi # Reverse proxy configuration if specified if options[:reverse_proxy_host] variables["REVERSE_PROXY_HOST"] = options[:reverse_proxy_host] - variables["REVERSE_PROXY_URL"] = options[:reverse_proxy_url] || "~ ^/(.*)$" + variables["REVERSE_PROXY_URL"] = options[:reverse_proxy_url] || "~ ^(?!/challenge)(.*)$" # Real IP settings for reverse proxy unless options[:real_ip_from].nil? @@ -273,9 +274,9 @@ class HestiaBunkerWebApi # Update SSL settings updated_vars = { - "USE_SSL" => "yes", - "SSL_CERTIFICATE_FILE_PATH" => certificate_path, - "SSL_KEY_FILE_PATH" => key_path, + "USE_CUSTOM_SSL" => "yes", + "CUSTOM_SSL_CERT" => certificate_path, + "CUSTOM_SSL_KEY" => key_path, "LISTEN_HTTPS_PORT" => (https_port || "443").to_s, "USE_REVERSE_PROXY_SSL" => "yes" } @@ -300,6 +301,47 @@ class HestiaBunkerWebApi return response[:body] || {} end + def delete_service_ssl(service_name) + + @extra_info = "" + + # First get current service configuration to preserve existing settings + get_service_response = api_call("GET", "/services/#{service_name}", {}) + + if get_service_response[:status] != 200 + raise BunkerWebApiError.new("Service '#{service_name}' not found") + end + + # Extract current variables from the service configuration + current_vars = get_service_response[:body]["variables"] || {} + + # Update SSL settings + updated_vars = { + "USE_CUSTOM_SSL" => "no", + "CUSTOM_SSL_CERT" => "", + "CUSTOM_SSL_KEY" => "" + } + + # Merge with existing variables (keep non-SSL settings) + final_vars = current_vars.merge(updated_vars) + + service_body = { + server_name: nil, # Not changing name + is_draft: false, # Keep as online + variables: final_vars + } + + response = api_call("PATCH", "/services/#{service_name}", {}, JSON.generate(service_body)) + + if response[:status] == 200 + puts "[INFO] SSL configuration updated for service '#{service_name}'" + else + raise BunkerWebApiError.new("Failed to update SSL configuration: status=#{response[:status]}, body=#{response[:raw_body]}") + end + + return response[:body] || {} + end + def delete_service(service_name) # Delete a service by its name. # diff --git a/func_ruby/ext-modules/bunkerweb_module.mod b/func_ruby/ext-modules/bunkerweb_module.mod index 69098f1..a1bb536 100644 --- a/func_ruby/ext-modules/bunkerweb_module.mod +++ b/func_ruby/ext-modules/bunkerweb_module.mod @@ -1,5 +1,7 @@ #!/opt/brepo/ruby33/bin/ruby +require 'shellwords' + class BunkerwebWorker < Kernel::ModuleCoreWorker MODULE_ID = "bunkerweb_module" @@ -95,7 +97,7 @@ class BunkerwebWorker < Kernel::ModuleCoreWorker if m_domain.nil? || m_ssl_cert.nil? || m_ssl_key.nil? || m_ssl_cert.empty? || m_ssl_key.empty? log_return("Domain, SSL cert and SSL key must be specified. #{args}") else - log("add ssl cert to bunkerweb protection") + log("update ssl cert to bunkerweb protection") output = `/usr/local/hestia/bin/v-bunkerweb-module updssl #{m_domain} #{m_ssl_cert} #{m_ssl_key} shell` exit_status = $?.exitstatus if exit_status != 0 @@ -104,6 +106,21 @@ class BunkerwebWorker < Kernel::ModuleCoreWorker ACTION_OK end end + when "deletessl" + m_domain = args[1].strip unless args[1].nil? + if m_domain.nil? + log_return("Domain should be specified. #{args}") + else + + log("delete ssl cert to bunkerweb protection") + output = `/usr/local/hestia/bin/v-bunkerweb-module deletessl #{m_domain} shell` + exit_status = $?.exitstatus + if exit_status != 0 + log_return("Command failed with status #{exit_status}") + else + ACTION_OK + end + end when "list" format = (args[1].nil? ? "shell" : args[1].strip) log("list of services") @@ -157,7 +174,6 @@ class BunkerwebWorker < Kernel::ModuleCoreWorker hestia_print_array_of_hashes(result, format, "API_USERNAME,API_PASSWORD,ADMIN_USERNAME,ADMIN_PASSWORD") ACTION_OK when "configure" - require 'shellwords' param1 = args[1] param2 = args[2] if param1 && param2 && !param1.strip.empty? && !param2.strip.empty? diff --git a/func_ruby/ext-modules/last_free_id b/func_ruby/ext-modules/last_free_id new file mode 100644 index 0000000..7f8f011 --- /dev/null +++ b/func_ruby/ext-modules/last_free_id @@ -0,0 +1 @@ +7 diff --git a/func_ruby/ext-modules/update_module.mod b/func_ruby/ext-modules/update_module.mod new file mode 100644 index 0000000..3383dbe --- /dev/null +++ b/func_ruby/ext-modules/update_module.mod @@ -0,0 +1,119 @@ +#!/opt/brepo/ruby33/bin/ruby + +require 'pathname' +require 'fileutils' + +class UpdateWorker < Kernel::ModuleCoreWorker + MODULE_ID = "update_module" + + def info + { + ID: 6, + NAME: MODULE_ID, + DESCR: "Module for updating HestiaCP data and templates", + REQ: "", + CONF: "yes", + } + end + + def file_changed?(new_file, old_file) + require 'digest/sha256' + return true unless File.exist?(old_file) + new_hash = Digest::SHA256.file(new_file).hexdigest + old_hash = Digest::SHA256.file(old_file).hexdigest + new_hash != old_hash + end + + def get_templates_map() + { :templates=> + [ + {:new=>"/usr/local/hestia/install/rpm/templates/web/awstats", :old=>"/usr/local/hestia/data/templates/web/awstats"}, + {:new=>"/usr/local/hestia/install/rpm/templates/web/httpd", :old=>"/usr/local/hestia/data/templates/web/httpd"}, + {:new=>"/usr/local/hestia/install/rpm/templates/web/nginx", :old=>"/usr/local/hestia/data/templates/web/nginx"}, + {:new=>"/usr/local/hestia/install/rpm/templates/web/php-fpm", :old=>"/usr/local/hestia/data/templates/web/php-fpm"} + ] + } + end + + # New helper method to get list of changed template files + def get_changed_template_files + templates_map = get_templates_map()[:templates] + result = [] + templates_map.each do |tpl| + new_dir = tpl[:new] + old_dir = tpl[:old] + Dir.glob(File.join(new_dir, '**', '*')).each do |new_file| + next if File.directory?(new_file) + rel_path = Pathname.new(new_file).relative_path_from(Pathname.new(new_dir)).to_s + old_file = File.join(old_dir, rel_path) + result << [new_dir, new_file, old_file] if file_changed?(new_file, old_file) + end + end + result + end + + def command(args) + return log_return("Not enough arguments. Needed command") if args.length < 1 + log_file = get_log + + m_command = args[0].strip + case m_command + when "synctemplates" + result = get_changed_template_files + result.each do |new_dir, new_file, old_file| + if !File.exist?(old_file) + FileUtils.cp(new_file, old_file) + else + stat = File.stat(old_file) + uid = stat.uid + gid = stat.gid + mode = stat.mode & 0o7777 + FileUtils.cp(new_file, old_file) + File.chown(uid, gid, old_file) + File.chmod(mode, old_file) + end + end + ACTION_OK + when "listsynctemplates" + format = (args[1].nil? ? "shell" : args[1].strip) + list = get_changed_template_files + result = [] + result = list.map do |new_dir, new_file, old_file| + file_name = Pathname.new(new_file).relative_path_from(Pathname.new(new_dir)).to_s + { + "FILE_NAME" => file_name, + "NEW_SIZE" => File.size(new_file), + "OLD_SIZE" => File.exist?(old_file) ? File.size(old_file) : "-" + } + end + + hestia_print_array_of_hashes(result, format, "FILE_NAME,NEW_SIZE,OLD_SIZE") + ACTION_OK + when "help" + puts "#{$0} update_module COMMAND [json|csv|plain]" + puts "COMMANDS:" + puts " synctemplates - sync web templates" + puts " listsynctemplates - show changed web templates" + puts " help - help" + ACTION_OK + else + log_return("Unknown command. #{args}") + end + end + + implements IPluginInterface +end + +module UpdateModule + def get_object + Proc.new { UpdateWorker.new } + end + + module_function :get_object +end + +class Kernel::PluginConfiguration + include UpdateModule + + @@loaded_plugins[UpdateWorker::MODULE_ID] = UpdateModule.get_object +end diff --git a/install/hst-install.sh b/install/hst-install.sh index 14468e9..4ad696f 100755 --- a/install/hst-install.sh +++ b/install/hst-install.sh @@ -380,7 +380,7 @@ set_default_value 'quota' 'no' set_default_value 'interactive' 'yes' set_default_value 'api' 'yes' set_default_value 'nopublicip' 'no' - set_default_value 'bunkerweb' 'no' +set_default_value 'bunkerweb' 'no' set_default_port '8083' set_default_lang 'en' set_default_value 'uselocalphp' 'no' @@ -1314,6 +1314,13 @@ write_config_value "RELEASE_BRANCH" "release" write_config_value "UPGRADE_SEND_EMAIL" "true" write_config_value "UPGRADE_SEND_EMAIL_LOG" "false" +#bunkerweb +if [ "$bunkerweb" = 'yes' ]; then + write_config_value "BUNKERWEB" "yes" +else + write_config_value "BUNKERWEB" "no" +fi + # Installing hosting packages cp -rf $HESTIA_COMMON_DIR/packages $HESTIA/data/ @@ -2253,6 +2260,20 @@ echo 'if [ "${PATH#*/usr/local/hestia/bin*}" = "$PATH" ]; then . /etc/profile.d/hestia.sh fi' >> /root/.bashrc +#----------------------------------------------------------# +# Bunkerweb # +# ---------------------------------------------------------# + +if [ "$bunkerweb" = 'yes' ]; then + echo "Bunkerweb installation" + $HESTIA/bin/v-ext-modules enable bunkerweb_module 2>&1 + module_state=$($HESTIA/bin/v-ext-modules state bunkerweb_module json | jq -r '.[0].STATE') + if [ "$module_state" = "enabled" ]; then + echo "Bunkerweb configuration" + $HESTIA/bin/v-ext-modules-run bunkerweb_module addssl configure + fi +fi + #----------------------------------------------------------# # Hestia Access Info # #----------------------------------------------------------#