Compare commits
26 Commits
d76624ff43
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f99c8f3b31 | ||
|
|
5914f54e64 | ||
|
|
b31dce3817 | ||
|
|
09aa301dd1 | ||
|
|
317e3e215e | ||
|
|
4f999094f3 | ||
|
|
5e32af0148 | ||
|
|
6ff9d67909 | ||
|
|
af9e6bfb82 | ||
|
|
e0419fea4b | ||
|
|
3cc428df43 | ||
|
|
9d1afcdf28 | ||
|
|
adef1a98bb | ||
|
|
3263dbecab | ||
|
|
3c5a5924d6 | ||
|
|
8b911372af | ||
|
|
a3888d10b4 | ||
|
|
8add078e7c | ||
|
|
54f549db62 | ||
|
|
fd67d9c546 | ||
|
|
86bf9fccf5 | ||
|
|
f64f034b7b | ||
|
|
dae9aca295 | ||
|
|
592b954a9f | ||
|
|
4b023ea671 | ||
|
|
4cb55905cc |
@@ -63,7 +63,7 @@ All notable changes to this project will be documented in this file.
|
|||||||
## [1.8.1] - Service release
|
## [1.8.1] - Service release
|
||||||
|
|
||||||
- Fixed Debian 10 not working with IP addresses check
|
- Fixed Debian 10 not working with IP addresses check
|
||||||
- Fixed Exim4 update config via patch was unreliable added few safety checks and add notice if failed.
|
- Fixed Exim update config via patch was unreliable added few safety checks and add notice if failed.
|
||||||
- Fixed hestia-nginx not loading with custom port
|
- Fixed hestia-nginx not loading with custom port
|
||||||
|
|
||||||
## [1.8.0] - Feature / Major release
|
## [1.8.0] - Feature / Major release
|
||||||
@@ -630,8 +630,8 @@ A bug in v-update-sys-hestia caused auto update to be not working. Please run: `
|
|||||||
|
|
||||||
## [1.6.2] - Service release
|
## [1.6.2] - Service release
|
||||||
|
|
||||||
- Fixed an issue with rate limits in Exim4 and make it more bullet proof (#2703)
|
- Fixed an issue with rate limits in Exim and make it more bullet proof (#2703)
|
||||||
- Fixed an issue with system-filter not properly loaded in Exim4 for Exim 4.94 and Ugrades from 1.5.x
|
- Fixed an issue with system-filter not properly loaded in Exim for Exim 4.94 and Ugrades from 1.5.x
|
||||||
|
|
||||||
## [1.6.1] - Service release
|
## [1.6.1] - Service release
|
||||||
|
|
||||||
@@ -1238,7 +1238,7 @@ After that run apt update && apt upgrade
|
|||||||
- Improve the calculated disk size of a new backup estimated by excluding the exclude folders, mail accounts and database in backups (#1616) @Myself5
|
- Improve the calculated disk size of a new backup estimated by excluding the exclude folders, mail accounts and database in backups (#1616) @Myself5
|
||||||
- Improve v-update-firewall / v-stop-firewall to make it self healing (#1892) @myrevery
|
- Improve v-update-firewall / v-stop-firewall to make it self healing (#1892) @myrevery
|
||||||
- Update phpMyAdmin version to 1.5.1 (See <https://www.phpmyadmin.net/news/2021/6/4/phpmyadmin-511-released/>)
|
- Update phpMyAdmin version to 1.5.1 (See <https://www.phpmyadmin.net/news/2021/6/4/phpmyadmin-511-released/>)
|
||||||
- Fixed a bug after rebuilding mail with Exim4 and suspended domains (#1886)
|
- Fixed a bug after rebuilding mail with Exim and suspended domains (#1886)
|
||||||
- Fixed "Allowed IP addresses for API" field with strange behaviour #1866
|
- Fixed "Allowed IP addresses for API" field with strange behaviour #1866
|
||||||
- Fixed an issue where the "Saved confirmation" was not set due to a redirect #1879
|
- Fixed an issue where the "Saved confirmation" was not set due to a redirect #1879
|
||||||
- Increased minimal memory requirements for ClamD / ClamAV. #1840
|
- Increased minimal memory requirements for ClamD / ClamAV. #1840
|
||||||
|
|||||||
@@ -79,9 +79,9 @@ EOF
|
|||||||
chown root:$user $fastcgi
|
chown root:$user $fastcgi
|
||||||
chmod 640 $fastcgi
|
chmod 640 $fastcgi
|
||||||
|
|
||||||
str="fastcgi_cache_path /var/cache/nginx/micro/$domain levels=1:2"
|
str="fastcgi_cache_path /usr/local/hestia/nginx-system/var/cache/nginx/micro/$domain levels=1:2"
|
||||||
str="$str keys_zone=$domain:10m max_size=512m inactive=30m use_temp_path=off;"
|
str="$str keys_zone=$domain:10m max_size=512m inactive=30m use_temp_path=off;"
|
||||||
conf='/etc/nginx/conf.d/fastcgi_cache_pool.conf'
|
conf='/usr/local/hestia/nginx-system/etc/nginx/conf.d/fastcgi_cache_pool.conf'
|
||||||
if [ -f "$conf" ]; then
|
if [ -f "$conf" ]; then
|
||||||
if [ -z "$(grep "=${domain}:" $conf)" ]; then
|
if [ -z "$(grep "=${domain}:" $conf)" ]; then
|
||||||
echo "$str" >> $conf
|
echo "$str" >> $conf
|
||||||
@@ -90,7 +90,7 @@ else
|
|||||||
echo "$str" >> $conf
|
echo "$str" >> $conf
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p /var/cache/nginx/micro/$domain
|
mkdir -p /usr/local/hestia/nginx-system/var/cache/nginx/micro/$domain
|
||||||
|
|
||||||
#----------------------------------------------------------#
|
#----------------------------------------------------------#
|
||||||
# Hestia #
|
# Hestia #
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ if [ -n "$user_rate_limit" ]; then
|
|||||||
sed -i "/^$account@$domain_idn:/ d" $HOMEDIR/$user/conf/mail/$domain/limits
|
sed -i "/^$account@$domain_idn:/ d" $HOMEDIR/$user/conf/mail/$domain/limits
|
||||||
echo "$account@$domain_idn:$user_rate_limit" >> $HOMEDIR/$user/conf/mail/$domain/limits
|
echo "$account@$domain_idn:$user_rate_limit" >> $HOMEDIR/$user/conf/mail/$domain/limits
|
||||||
else
|
else
|
||||||
system=$(cat /etc/exim4/limit.conf)
|
system=$(cat /etc/exim/limit.conf)
|
||||||
sed -i "/^$account@$domain_idn:/ d" $HOMEDIR/$user/conf/mail/$domain/limits
|
sed -i "/^$account@$domain_idn:/ d" $HOMEDIR/$user/conf/mail/$domain/limits
|
||||||
echo "$account@$domain_idn:$system" >> $HOMEDIR/$user/conf/mail/$domain/limits
|
echo "$account@$domain_idn:$system" >> $HOMEDIR/$user/conf/mail/$domain/limits
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -28,11 +28,8 @@ source $HESTIA/func/domain.sh
|
|||||||
source_conf "$HESTIA/conf/hestia.conf"
|
source_conf "$HESTIA/conf/hestia.conf"
|
||||||
|
|
||||||
# Define mail user
|
# Define mail user
|
||||||
if [ "$MAIL_SYSTEM" = 'exim4' ]; then
|
MAIL_USER=exim
|
||||||
MAIL_USER=Debian-exim
|
|
||||||
else
|
|
||||||
MAIL_USER=exim
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Additional argument formatting
|
# Additional argument formatting
|
||||||
format_domain
|
format_domain
|
||||||
|
|||||||
@@ -27,11 +27,8 @@ source $HESTIA/func/domain.sh
|
|||||||
source_conf "$HESTIA/conf/hestia.conf"
|
source_conf "$HESTIA/conf/hestia.conf"
|
||||||
|
|
||||||
# Define mail user
|
# Define mail user
|
||||||
if [ "$MAIL_SYSTEM" = 'exim4' ]; then
|
MAIL_USER=exim
|
||||||
MAIL_USER=Debian-exim
|
|
||||||
else
|
|
||||||
MAIL_USER=exim
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Additional argument formatting
|
# Additional argument formatting
|
||||||
format_domain
|
format_domain
|
||||||
|
|||||||
@@ -35,11 +35,7 @@ source $HESTIA/func/syshealth.sh
|
|||||||
source_conf "$HESTIA/conf/hestia.conf"
|
source_conf "$HESTIA/conf/hestia.conf"
|
||||||
|
|
||||||
# Define mail user
|
# Define mail user
|
||||||
if [ "$MAIL_SYSTEM" = 'exim4' ]; then
|
MAIL_USER=exim
|
||||||
MAIL_USER=Debian-exim
|
|
||||||
else
|
|
||||||
MAIL_USER=exim
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Additional argument formatting
|
# Additional argument formatting
|
||||||
format_domain
|
format_domain
|
||||||
|
|||||||
@@ -27,11 +27,8 @@ source $HESTIA/func/domain.sh
|
|||||||
source_conf "$HESTIA/conf/hestia.conf"
|
source_conf "$HESTIA/conf/hestia.conf"
|
||||||
|
|
||||||
# Define mail user
|
# Define mail user
|
||||||
if [ "$MAIL_SYSTEM" = 'exim4' ]; then
|
MAIL_USER=exim
|
||||||
MAIL_USER=Debian-exim
|
|
||||||
else
|
|
||||||
MAIL_USER=exim
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Additional argument formatting
|
# Additional argument formatting
|
||||||
format_domain
|
format_domain
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ check_hestia_demo_mode
|
|||||||
# Action #
|
# Action #
|
||||||
#----------------------------------------------------------#
|
#----------------------------------------------------------#
|
||||||
|
|
||||||
cat > /etc/exim4/domains/${domain}/smtp_relay.conf << EOL
|
cat > /etc/exim/domains/${domain}/smtp_relay.conf << EOL
|
||||||
host:$host
|
host:$host
|
||||||
port:$port
|
port:$port
|
||||||
user:$username
|
user:$username
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ change_sys_value 'SMTP_RELAY_HOST' "$host"
|
|||||||
change_sys_value 'SMTP_RELAY_PORT' "$port"
|
change_sys_value 'SMTP_RELAY_PORT' "$port"
|
||||||
change_sys_value 'SMTP_RELAY_USER' "$username"
|
change_sys_value 'SMTP_RELAY_USER' "$username"
|
||||||
|
|
||||||
cat > /etc/exim4/smtp_relay.conf << EOL
|
cat > /etc/exim/smtp_relay.conf << EOL
|
||||||
host:$host
|
host:$host
|
||||||
port:$port
|
port:$port
|
||||||
user:$username
|
user:$username
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
# www.domain.tld type will be automatically assigned to the domain unless
|
# www.domain.tld type will be automatically assigned to the domain unless
|
||||||
# "none" is transmited as argument. If ip have associated dns name, this
|
# "none" is transmited as argument. If ip have associated dns name, this
|
||||||
# domain will also get the alias domain-tpl.$ipname. An alias with the ip
|
# domain will also get the alias domain-tpl.$ipname. An alias with the ip
|
||||||
# name is useful during the site testing while dns isn't moved to server yet.
|
# name is useful during the site testing while dns isn't moved to server yaliet.
|
||||||
|
|
||||||
#----------------------------------------------------------#
|
#----------------------------------------------------------#
|
||||||
# Variables & Functions #
|
# Variables & Functions #
|
||||||
@@ -251,6 +251,15 @@ check_result $? "Web restart failed" > /dev/null
|
|||||||
$BIN/v-restart-proxy "$restart"
|
$BIN/v-restart-proxy "$restart"
|
||||||
check_result $? "Proxy restart failed" > /dev/null
|
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" ]; then
|
||||||
|
$BIN/v-ext-modules-run bunkerweb_module add "$domain" "$ip"
|
||||||
|
if [ -n "%$ALIAS" ]; then
|
||||||
|
$BIN/v-ext-modules-run bunkerweb_module alias "$domain" "$ALIAS"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
$BIN/v-log-action "$user" "Info" "Web" "Added new web domain (Name: $domain)."
|
$BIN/v-log-action "$user" "Info" "Web" "Added new web domain (Name: $domain)."
|
||||||
log_event "$OK" "$ARGUMENTS"
|
log_event "$OK" "$ARGUMENTS"
|
||||||
|
|||||||
@@ -108,6 +108,13 @@ check_result $? "Web restart failed" > /dev/null
|
|||||||
$BIN/v-restart-proxy "$restart"
|
$BIN/v-restart-proxy "$restart"
|
||||||
check_result $? "Proxy restart failed" > /dev/null
|
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" ]; then
|
||||||
|
get_domain_values 'web'
|
||||||
|
$BIN/v-ext-modules-run bunkerweb_module alias "$domain" "$ALIAS"
|
||||||
|
fi
|
||||||
|
|
||||||
$BIN/v-log-action "$user" "Info" "Web" "Added new web domain alias (Alias: $aliases, Domain: $domain)."
|
$BIN/v-log-action "$user" "Info" "Web" "Added new web domain alias (Alias: $aliases, Domain: $domain)."
|
||||||
log_event "$OK" "$ARGUMENTS"
|
log_event "$OK" "$ARGUMENTS"
|
||||||
|
|
||||||
|
|||||||
@@ -136,6 +136,25 @@ if [ -n "$UPDATE_SSL_SCRIPT" ]; then
|
|||||||
eval "$UPDATE_SSL_SCRIPT $user $domain"
|
eval "$UPDATE_SSL_SCRIPT $user $domain"
|
||||||
fi
|
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
|
# Logging
|
||||||
$BIN/v-log-action "$user" "Info" "Web" "Added certificate and enabled SSL (Domain: $domain)."
|
$BIN/v-log-action "$user" "Info" "Web" "Added certificate and enabled SSL (Domain: $domain)."
|
||||||
log_event "$OK" "$ARGUMENTS"
|
log_event "$OK" "$ARGUMENTS"
|
||||||
|
|||||||
574
bin/v-bunkerweb-migrate
Executable file
574
bin/v-bunkerweb-migrate
Executable file
@@ -0,0 +1,574 @@
|
|||||||
|
#!/opt/brepo/ruby33/bin/ruby
|
||||||
|
# info: utility to prepare existing server with hestiacp to use bunkerweb
|
||||||
|
# do not run this script n the server, where bunkerweb was installed with hestiacp
|
||||||
|
# installation
|
||||||
|
# options: COMMAND
|
||||||
|
#
|
||||||
|
# example: v-bunkerweb-migrate migrate-nginx
|
||||||
|
#
|
||||||
|
# Commands:
|
||||||
|
# migratenginx - move old nginx configs to the new port and path
|
||||||
|
# migratetobunkerweb - create items of sites in the bunkerweb database
|
||||||
|
#
|
||||||
|
|
||||||
|
#------------------------------------------#
|
||||||
|
# Variables & Functions #
|
||||||
|
#------------------------------------------#
|
||||||
|
|
||||||
|
# Argument definition
|
||||||
|
v_command = ARGV[0]
|
||||||
|
|
||||||
|
require "/usr/local/hestia/func_ruby/global_options"
|
||||||
|
|
||||||
|
load_ruby_options_defaults
|
||||||
|
$HESTIA = load_hestia_default_path_from_env
|
||||||
|
|
||||||
|
require "main"
|
||||||
|
require "modules"
|
||||||
|
require "HestiaBunkerWebApi"
|
||||||
|
|
||||||
|
require 'json' unless defined?(JSON)
|
||||||
|
require 'fileutils'
|
||||||
|
require 'time'
|
||||||
|
require 'pathname'
|
||||||
|
|
||||||
|
|
||||||
|
def copy_nginx_files(src_root, dest_root)
|
||||||
|
FileUtils.mkdir_p(dest_root)
|
||||||
|
Dir.foreach(src_root) do |entry|
|
||||||
|
next if entry == '.' || entry == '..'
|
||||||
|
next if entry == 'modules' || entry == 'modules-enabled'
|
||||||
|
src_path = File.join(src_root, entry)
|
||||||
|
dest_path = File.join(dest_root, entry)
|
||||||
|
if File.directory?(src_path)
|
||||||
|
FileUtils.mkdir_p(dest_path)
|
||||||
|
copy_nginx_files(src_path, dest_path)
|
||||||
|
else
|
||||||
|
FileUtils.cp(src_path, dest_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Function to log and execute migration stages
|
||||||
|
def log_migrate_stage(stage)
|
||||||
|
log_file = '/usr/local/hestia/log/bunkerweb_migrate_stages.log'
|
||||||
|
# Check if the stage has already been recorded
|
||||||
|
if File.exist?(log_file) && File.readlines(log_file).any? { |line| line.strip == stage }
|
||||||
|
hestia_print_error_message_to_cli "Stage #{stage} already completed, skipping."
|
||||||
|
return
|
||||||
|
end
|
||||||
|
# Execute the stage block
|
||||||
|
begin
|
||||||
|
yield
|
||||||
|
# Record the successful stage
|
||||||
|
File.open(log_file, 'a') { |f| f.puts stage }
|
||||||
|
hestia_print_info_message_to_cli "Stage #{stage} completed."
|
||||||
|
rescue => e
|
||||||
|
hestia_print_error_message_to_cli "Stage #{stage} failed: #{e.message}"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Function to parse and migrate nginx.conf from /usr/local/hestia/nginx-system/etc/nginx/nginx.conf
|
||||||
|
def migrate_nginx_config_from_file(source_path)
|
||||||
|
return false unless File.exist?(source_path)
|
||||||
|
|
||||||
|
hestia_print_info_message_to_cli "Processing nginx config from: #{source_path}"
|
||||||
|
|
||||||
|
content = File.read(source_path)
|
||||||
|
original_content = content.dup
|
||||||
|
|
||||||
|
modified = false
|
||||||
|
|
||||||
|
# Replace all paths starting with /var/ to /usr/local/hestia/nginx-system/var/
|
||||||
|
# This pattern matches any absolute path that starts with /var/ anywhere in the line
|
||||||
|
content = content.gsub(/\/var\//, '/usr/local/hestia/nginx-system/var/')
|
||||||
|
|
||||||
|
# Replace pid path from /run/nginx.pid to /run/nginx-system.pid
|
||||||
|
content = content.gsub(/pid\s+\S+/) { |match| match.gsub('/run/nginx.pid', '/run/nginx-system.pid') }
|
||||||
|
|
||||||
|
if content != original_content
|
||||||
|
File.write(source_path, content)
|
||||||
|
hestia_print_info_message_to_cli "Updated config: #{source_path}"
|
||||||
|
modified = true
|
||||||
|
end
|
||||||
|
|
||||||
|
modified
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_listen(line)
|
||||||
|
# Попытка найти IP:port
|
||||||
|
m = line.match(/^\s*listen\s+([^\s:]+):(\d+)/i)
|
||||||
|
return [m[1], m[2]] if m
|
||||||
|
# Если только порт после listen
|
||||||
|
m = line.match(/^\s*listen\s+(\d+);?\s*$/i)
|
||||||
|
return [nil, m[1]] if m
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
# Helper function to parse and replace ports in listen directives using temp placeholders
|
||||||
|
def parse_and_replace_listen_directive(line, proxy_port, proxy_ssl_port)
|
||||||
|
return line unless line.match?(/\blisten\b/i)
|
||||||
|
|
||||||
|
new_line = line.dup
|
||||||
|
|
||||||
|
# Define target ports (always migrate to these values regardless of input)
|
||||||
|
target_http_port = '8078'
|
||||||
|
target_ssl_port = '8079'
|
||||||
|
|
||||||
|
result = parse_listen(line)
|
||||||
|
return line unless result
|
||||||
|
ip, port = result
|
||||||
|
|
||||||
|
if port == proxy_port
|
||||||
|
if ip
|
||||||
|
new_line.gsub!("#{ip}:#{port}", "#{ip}:#{target_http_port}")
|
||||||
|
else
|
||||||
|
new_line.gsub!(port, target_http_port)
|
||||||
|
end
|
||||||
|
elsif port == proxy_ssl_port
|
||||||
|
if ip
|
||||||
|
new_line.gsub!("#{ip}:#{port}", "#{ip}:#{target_ssl_port}")
|
||||||
|
else
|
||||||
|
new_line.gsub!(port, target_ssl_port)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
new_line
|
||||||
|
end
|
||||||
|
|
||||||
|
hestia_check_privileged_user
|
||||||
|
|
||||||
|
load_global_bash_variables "/etc/hestiacp/hestia.conf"
|
||||||
|
if $HESTIA.nil?
|
||||||
|
hestia_print_error_message_to_cli "Can't find HESTIA base path"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
load_global_bash_variables "#{$HESTIA}/conf/hestia.conf"
|
||||||
|
|
||||||
|
#------------------------------------------#
|
||||||
|
# Verifications #
|
||||||
|
#------------------------------------------#
|
||||||
|
|
||||||
|
check_args 1, ARGV, "COMMAND"
|
||||||
|
|
||||||
|
# Perform verification if read-only mode is enabled
|
||||||
|
check_hestia_demo_mode
|
||||||
|
|
||||||
|
#------------------------------------------#
|
||||||
|
# Action #
|
||||||
|
#------------------------------------------#
|
||||||
|
|
||||||
|
case v_command.to_sym
|
||||||
|
when :migratenginx
|
||||||
|
log_migrate_stage('stage0') do
|
||||||
|
# Create backup of /etc/nginx with timestamp
|
||||||
|
|
||||||
|
timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
|
||||||
|
backup_root = "/etc/nginx_backup_#{timestamp}"
|
||||||
|
FileUtils.mkdir_p(backup_root)
|
||||||
|
|
||||||
|
src_root = '/etc/nginx'
|
||||||
|
dest_root = backup_root
|
||||||
|
|
||||||
|
# Custom copy function to handle symlinks in conf.d/domains
|
||||||
|
def copy_with_symlinks(src, dest)
|
||||||
|
Dir.foreach(src) do |entry|
|
||||||
|
next if entry == '.' || entry == '..'
|
||||||
|
src_path = File.join(src, entry)
|
||||||
|
dest_path = File.join(dest, entry)
|
||||||
|
|
||||||
|
if File.symlink?(src_path)
|
||||||
|
# Check if symlink is inside conf.d/domains
|
||||||
|
if src_path.include?(File.join('conf.d', 'domains'))
|
||||||
|
# Resolve the target of the symlink
|
||||||
|
target_path = File.readlink(src_path)
|
||||||
|
# Resolve relative symlink paths
|
||||||
|
unless Pathname.new(target_path).absolute?
|
||||||
|
target_path = File.expand_path(target_path, File.dirname(src_path))
|
||||||
|
end
|
||||||
|
if File.exist?(target_path) && File.file?(target_path)
|
||||||
|
content = File.read(target_path)
|
||||||
|
new_file_name = "#{entry}_content.conf"
|
||||||
|
new_file_path = File.join(dest, new_file_name)
|
||||||
|
File.write(new_file_path, content)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
# Preserve the symlink as is
|
||||||
|
FileUtils.mkdir_p(File.dirname(dest_path))
|
||||||
|
target = File.readlink(src_path)
|
||||||
|
FileUtils.ln_s(target, dest_path)
|
||||||
|
end
|
||||||
|
elsif File.directory?(src_path)
|
||||||
|
FileUtils.mkdir_p(dest_path)
|
||||||
|
copy_with_symlinks(src_path, dest_path)
|
||||||
|
else
|
||||||
|
FileUtils.cp(src_path, dest_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
copy_with_symlinks(src_root, dest_root)
|
||||||
|
end
|
||||||
|
log_migrate_stage('stage1') do
|
||||||
|
if $BUNKERWEB.nil?
|
||||||
|
hestia_change_sys_config_value("BUNKERWEB", "yes")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
log_migrate_stage('stage2') do
|
||||||
|
unless system('yum install -y nginx-system')
|
||||||
|
hestia_print_error_message_to_cli "Failed to install nginx-system via yum"
|
||||||
|
log_event E_ARGS, $ARGUMENTS
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
log_migrate_stage('stage3') do
|
||||||
|
FileUtils.rm_f Dir.glob('/usr/local/hestia/nginx-system/etc/nginx/conf.d/*.conf')
|
||||||
|
src_root = '/etc/nginx'
|
||||||
|
dest_root = '/usr/local/hestia/nginx-system/etc/nginx'
|
||||||
|
copy_nginx_files(src_root, dest_root)
|
||||||
|
end
|
||||||
|
log_migrate_stage('stage4') do
|
||||||
|
# Find all files under the nginx-system directory
|
||||||
|
nginx_conf_dir = '/usr/local/hestia/nginx-system/etc/nginx'
|
||||||
|
Dir.glob(File.join(nginx_conf_dir, '**', '*')).each do |path|
|
||||||
|
hestia_print_info_message_to_cli "stage 4 processing file #{path}"
|
||||||
|
next if File.directory?(path)
|
||||||
|
content = File.read(path)
|
||||||
|
new_content = content.gsub(/(?<!\/usr\/local\/hestia\/nginx-system)\/etc\/nginx/, '/usr/local/hestia/nginx-system/etc/nginx')
|
||||||
|
if new_content != content
|
||||||
|
hestia_print_info_message_to_cli "Changed path to config in file #{path}"
|
||||||
|
File.open(path, 'w') { |f| f.write(new_content) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
log_migrate_stage('stage4.1') do
|
||||||
|
# Parse nginx.conf file to replace paths
|
||||||
|
if migrate_nginx_config_from_file("/usr/local/hestia/nginx-system/etc/nginx/nginx.conf")
|
||||||
|
hestia_print_info_message_to_cli "Completed migration of nginx.conf paths"
|
||||||
|
else
|
||||||
|
hestia_print_error_message_to_cli "Warning: Could not migrate nginx.conf from #{File.expand_path('/usr/local/hestia/nginx-system/etc/nginx/nginx.conf')}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
log_migrate_stage('stage5') do
|
||||||
|
nginx_conf_dir = '/usr/local/hestia/nginx-system/etc/nginx'
|
||||||
|
|
||||||
|
# Read proxy ports from configuration
|
||||||
|
#proxy_port = $PROXY_PORT.nil? || $PROXY_PORT.empty? ? '80' : $PROXY_PORT
|
||||||
|
#proxy_ssl_port = $PROXY_SSL_PORT.nil? || $PROXY_SSL_PORT.empty? ? '443' : $PROXY_SSL_PORT
|
||||||
|
|
||||||
|
proxy_port = '80'
|
||||||
|
proxy_ssl_port = '443'
|
||||||
|
|
||||||
|
hestia_print_info_message_to_cli "Migrating ports: #{proxy_port} -> 8078, #{proxy_ssl_port} -> 8079"
|
||||||
|
|
||||||
|
# Find and replace port in all .conf files
|
||||||
|
Dir.glob(File.join(nginx_conf_dir, '**', '*.conf')).each do |conf_file|
|
||||||
|
hestia_print_info_message_to_cli "stage 5 processing file #{conf_file}"
|
||||||
|
content = File.read(conf_file)
|
||||||
|
modified = false
|
||||||
|
|
||||||
|
# Process line by line - only replace ports in listen directives
|
||||||
|
new_lines = []
|
||||||
|
|
||||||
|
content.each_line do |line|
|
||||||
|
# Check if line is a listen directive (starts with optional whitespace then 'listen')
|
||||||
|
if /^\s*listen\s+/i.match?(line) || /^listen\s+/i.match?(line)
|
||||||
|
# This is a listen line - process it
|
||||||
|
new_line = parse_and_replace_listen_directive(line, proxy_port, proxy_ssl_port)
|
||||||
|
modified = true unless new_line == line
|
||||||
|
new_lines << new_line
|
||||||
|
else
|
||||||
|
# Not a listen line, keep as is
|
||||||
|
new_lines << line
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Write changes back if modified
|
||||||
|
if modified
|
||||||
|
File.open(conf_file, 'w') { |f| f.write(new_lines.join) }
|
||||||
|
hestia_print_info_message_to_cli " Updated: #{conf_file}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
log_migrate_stage('stage6') do
|
||||||
|
hestia_change_sys_config_value("PROXY_PORT", "8078")
|
||||||
|
hestia_change_sys_config_value("PROXY_SSL_PORT", "8079")
|
||||||
|
end
|
||||||
|
log_migrate_stage('stage7') do
|
||||||
|
if system('/usr/local/hestia/bin/v-ext-modules enable update_module')
|
||||||
|
output = IO.popen("/usr/local/hestia/bin/v-ext-modules state update_module json").read
|
||||||
|
begin
|
||||||
|
parsed = JSON.parse(output)
|
||||||
|
if parsed.is_a?(Array) && parsed.first && parsed.first['STATE'] == 'enabled'
|
||||||
|
system('/usr/local/hestia/bin/v-ext-modules-run update_module synctemplates')
|
||||||
|
else
|
||||||
|
hestia_print_error_message_to_cli "update_module not enabled after enable command"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
rescue JSON::ParserError => e
|
||||||
|
hestia_print_error_message_to_cli "Failed to parse JSON from state command: #{e.message}"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
else
|
||||||
|
hestia_print_error_message_to_cli "Failed to enable update_module"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
log_migrate_stage('stage8') do
|
||||||
|
hestia_change_sys_config_value("PROXY_PORT", "8078")
|
||||||
|
hestia_change_sys_config_value("PROXY_SSL_PORT", "8079")
|
||||||
|
end
|
||||||
|
#stage8 активация из запуск nginx-system
|
||||||
|
log_migrate_stage('stage9') do
|
||||||
|
# Delete all contents inside /etc/nginx
|
||||||
|
FileUtils.rm_rf Dir.glob('/etc/nginx/*')
|
||||||
|
# Stop nginx service
|
||||||
|
system('systemctl stop nginx')
|
||||||
|
# Start nginx-system service
|
||||||
|
system('systemctl enable nginx-system')
|
||||||
|
system('systemctl start nginx-system')
|
||||||
|
end
|
||||||
|
when :migratetobunkerweb
|
||||||
|
log_migrate_stage('stage10') do
|
||||||
|
if system('/usr/local/hestia/bin/v-ext-modules enable bunkerweb_module')
|
||||||
|
output = IO.popen("/usr/local/hestia/bin/v-ext-modules state bunkerweb_module json").read
|
||||||
|
begin
|
||||||
|
parsed = JSON.parse(output)
|
||||||
|
if parsed.is_a?(Array) && parsed.first && parsed.first['STATE'] == 'enabled'
|
||||||
|
result = system('/usr/local/hestia/bin/v-ext-modules-run bunkerweb_module configure')
|
||||||
|
unless result
|
||||||
|
hestia_print_error_message_to_cli "bunkerweb_module configure command failed"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
else
|
||||||
|
hestia_print_error_message_to_cli "bunkerweb_module not enabled after enable command"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
rescue JSON::ParserError => e
|
||||||
|
hestia_print_error_message_to_cli "Failed to parse JSON from state command: #{e.message}"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
else
|
||||||
|
hestia_print_error_message_to_cli "Failed to enable bunkerweb_module"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
hestia_print_info_message_to_cli "Ожидаем минуту для перезапуска сервиса bunkerweb..."
|
||||||
|
sleep 60
|
||||||
|
end
|
||||||
|
LIST_DOMAINS=[]
|
||||||
|
|
||||||
|
log_migrate_stage('stage11') do
|
||||||
|
hestia_print_info_message_to_cli "Stage 11: Migrating users and domains to BunkerWeb..."
|
||||||
|
|
||||||
|
# Get all users from HestiaCP in JSON format
|
||||||
|
user_list_output = IO.popen("/usr/local/hestia/bin/v-list-users json").read
|
||||||
|
begin
|
||||||
|
user_data = JSON.parse(user_list_output)
|
||||||
|
rescue JSON::ParserError => e
|
||||||
|
hestia_print_error_message_to_cli "Failed to parse users JSON: #{e.message}"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
# Iterate over each user
|
||||||
|
user_data.each do |username, user_info|
|
||||||
|
next unless user_info.is_a?(Hash)
|
||||||
|
|
||||||
|
web_domains_count = user_info['U_WEB_DOMAINS']
|
||||||
|
next unless web_domains_count && web_domains_count.to_i > 0
|
||||||
|
|
||||||
|
hestia_print_info_message_to_cli "Processing user: #{username} (#{web_domains_count} domains)"
|
||||||
|
|
||||||
|
# Get web domains for this user in JSON format
|
||||||
|
domain_list_output = IO.popen("/usr/local/hestia/bin/v-list-web-domains #{username} json").read
|
||||||
|
begin
|
||||||
|
domain_data = JSON.parse(domain_list_output)
|
||||||
|
rescue JSON::ParserError => e
|
||||||
|
hestia_print_error_message_to_cli "Failed to parse domains JSON for user #{username}: #{e.message}"
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
# Process each domain
|
||||||
|
domain_data.each do |domain_name, domain_info|
|
||||||
|
next unless domain_info.is_a?(Hash)
|
||||||
|
|
||||||
|
ssl_status = domain_info['SSL'] || 'no'
|
||||||
|
is_ssl = ssl_status == 'yes'
|
||||||
|
|
||||||
|
# Get IP from domain info - should be available in the parsed JSON
|
||||||
|
proxy_host = domain_info['IP'] || (domain_info['IP6'].present? ? domain_info['IP6'].strip : "127.0.0.1")
|
||||||
|
proxy_host = proxy_host.nil? || proxy_host.empty? ? "127.0.0.1" : proxy_host
|
||||||
|
|
||||||
|
# Create alias list from domain info (ALIAS field contains comma-separated aliases)
|
||||||
|
raw_aliases = domain_info['ALIAS'] || ''
|
||||||
|
if raw_aliases && !raw_aliases.empty?
|
||||||
|
# Hestia uses comma-separated, pass as-is to v-bunkerweb-module (it handles conversion internally)
|
||||||
|
aliases_list = raw_aliases
|
||||||
|
else
|
||||||
|
aliases_list = domain_name # No aliases, use domain name only
|
||||||
|
end
|
||||||
|
|
||||||
|
hestia_print_info_message_to_cli "Processing domain: #{domain_name} (SSL: #{is_ssl}, IP: #{proxy_host})"
|
||||||
|
|
||||||
|
begin
|
||||||
|
# Add domain to BunkerWeb via module script (without SSL)
|
||||||
|
cmd = "/usr/local/hestia/bin/v-bunkerweb-module add #{domain_name} #{proxy_host}"
|
||||||
|
puts_cmd = " Added #{domain_name}: Executing command: #{cmd}"
|
||||||
|
hestia_print_info_message_to_cli puts_cmd
|
||||||
|
|
||||||
|
result = system(cmd)
|
||||||
|
|
||||||
|
if result
|
||||||
|
# Команда успешно выполнена
|
||||||
|
hestia_print_info_message_to_cli " Status: Success"
|
||||||
|
else
|
||||||
|
hestia_print_error_message_to_cli " Failed to add domain #{domain_name}"
|
||||||
|
next
|
||||||
|
end
|
||||||
|
rescue => e
|
||||||
|
hestia_print_error_message_to_cli "Failed to add domain #{domain_name} to BunkerWeb: #{e.message}"
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
# Add aliases to the domain via module script
|
||||||
|
begin
|
||||||
|
cmd = "/usr/local/hestia/bin/v-bunkerweb-module alias #{domain_name} \"#{aliases_list}\""
|
||||||
|
puts_cmd = " Added aliases for #{domain_name}: Executing command: #{cmd}"
|
||||||
|
hestia_print_info_message_to_cli puts_cmd
|
||||||
|
|
||||||
|
result = system(cmd)
|
||||||
|
|
||||||
|
if result
|
||||||
|
hestia_print_info_message_to_cli " Status: Success"
|
||||||
|
else
|
||||||
|
hestia_print_error_message_to_cli " Failed to set aliases for #{domain_name}"
|
||||||
|
next
|
||||||
|
end
|
||||||
|
rescue => e
|
||||||
|
hestia_print_error_message_to_cli "Failed to set aliases for #{domain_name}: #{e.message}"
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
# Handle SSL configuration if enabled
|
||||||
|
if is_ssl
|
||||||
|
original_ssl_dir = "/home/#{username}/conf/web/#{domain_name}/ssl"
|
||||||
|
|
||||||
|
# Check if SSL directory and files exist
|
||||||
|
unless Dir.exist?(original_ssl_dir) || File.exist?("#{original_ssl_dir}/#{domain_name}.pem")
|
||||||
|
hestia_print_error_message_to_cli "Warning: SSL files not found for #{domain_name}"
|
||||||
|
LIST_DOMAINS << {
|
||||||
|
user: username,
|
||||||
|
domain: domain_name,
|
||||||
|
is_ssl: true,
|
||||||
|
path_to_ssl: nil
|
||||||
|
}
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
# Define bunkerweb directory for this domain's certificates
|
||||||
|
bunkerweb_ssl_dir = "/home/#{username}/conf/web/#{domain_name}/ssl/bunkerweb"
|
||||||
|
|
||||||
|
begin
|
||||||
|
# Create bunkerweb SSL directory if it doesn't exist
|
||||||
|
FileUtils.mkdir_p(bunkerweb_ssl_dir)
|
||||||
|
FileUtils.chmod(0755, bunkerweb_ssl_dir)
|
||||||
|
|
||||||
|
# Define paths in bunkerweb directory
|
||||||
|
crt_path_bunkerweb = "#{bunkerweb_ssl_dir}/#{domain_name}.crt"
|
||||||
|
cert_path_bunkerweb = "#{bunkerweb_ssl_dir}/#{domain_name}.pem"
|
||||||
|
key_path_bunkerweb = "#{bunkerweb_ssl_dir}/#{domain_name}.key"
|
||||||
|
|
||||||
|
# Copy all SSL files if they exist (matching v-add-web-domain-ssl behavior)
|
||||||
|
original_crt = "#{original_ssl_dir}/#{domain_name}.crt"
|
||||||
|
original_pem = "#{original_ssl_dir}/#{domain_name}.pem"
|
||||||
|
original_key = "#{original_ssl_dir}/#{domain_name}.key"
|
||||||
|
original_ca = "#{original_ssl_dir}/#{domain_name}.ca"
|
||||||
|
|
||||||
|
# Copy .crt file if exists
|
||||||
|
if File.exist?(original_crt)
|
||||||
|
FileUtils.cp(original_crt, crt_path_bunkerweb)
|
||||||
|
end
|
||||||
|
|
||||||
|
if File.exist?(original_pem)
|
||||||
|
# Only use .pem if .crt doesn't exist (fallback like the backup script)
|
||||||
|
FileUtils.cp(original_pem, cert_path_bunkerweb)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Copy .key file if exists
|
||||||
|
if File.exist?(original_key)
|
||||||
|
FileUtils.cp(original_key, key_path_bunkerweb)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Copy .ca file if exists
|
||||||
|
if File.exist?(original_ca)
|
||||||
|
FileUtils.cp(original_ca, "#{bunkerweb_ssl_dir}/#{domain_name}.ca")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Set ownership and permissions (nginx user can read, others cannot)
|
||||||
|
FileUtils.chown('root', 'nginx', bunkerweb_ssl_dir)
|
||||||
|
FileUtils.chmod(0755, bunkerweb_ssl_dir)
|
||||||
|
|
||||||
|
FileUtils.chown('root', 'nginx', cert_path_bunkerweb) if File.exist?(cert_path_bunkerweb)
|
||||||
|
FileUtils.chmod(0640, cert_path_bunkerweb) if File.exist?(cert_path_bunkerweb)
|
||||||
|
|
||||||
|
FileUtils.chown('root', 'nginx', key_path_bunkerweb) if File.exist?(key_path_bunkerweb)
|
||||||
|
FileUtils.chmod(0640, key_path_bunkerweb) if File.exist?(key_path_bunkerweb)
|
||||||
|
|
||||||
|
FileUtils.chown('root', 'nginx', crt_path_bunkerweb) if File.exist?(crt_path_bunkerweb)
|
||||||
|
FileUtils.chmod(0640, crt_path_bunkerweb) if File.exist?(crt_path_bunkerweb)
|
||||||
|
|
||||||
|
# Add SSL configuration to BunkerWeb via module script (use proxy_host from earlier)
|
||||||
|
|
||||||
|
cmd = "/usr/local/hestia/bin/v-bunkerweb-module addssl #{domain_name} #{crt_path_bunkerweb} #{key_path_bunkerweb}"
|
||||||
|
puts_cmd = " Added SSL for #{domain_name}: Executing command: #{cmd}"
|
||||||
|
hestia_print_info_message_to_cli puts_cmd
|
||||||
|
|
||||||
|
result = system(cmd)
|
||||||
|
|
||||||
|
if result
|
||||||
|
hestia_print_info_message_to_cli " Status: Success"
|
||||||
|
else
|
||||||
|
hestia_print_error_message_to_cli " Failed to configure SSL for #{domain_name}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Update path_to_ssl to point to bunkerweb directory
|
||||||
|
ssl_cert_path = crt_path_bunkerweb
|
||||||
|
rescue => e
|
||||||
|
hestia_print_error_message_to_cli "Failed to configure SSL for #{domain_name}: #{e.message}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Populate LIST_DOMAINS array with domain info
|
||||||
|
LIST_DOMAINS << {
|
||||||
|
user: username,
|
||||||
|
domain: domain_name,
|
||||||
|
proxy_host: proxy_host.to_s,
|
||||||
|
is_ssl: is_ssl == true ? "yes" : "no", # Convert boolean/string to proper string format
|
||||||
|
path_to_ssl: ssl_cert_path ? ssl_cert_path : nil
|
||||||
|
}
|
||||||
|
|
||||||
|
hestia_print_info_message_to_cli "Successfully migrated #{domain_name} for user #{username}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Output the populated LIST_DOMAINS array
|
||||||
|
if !LIST_DOMAINS.empty?
|
||||||
|
hestia_print_info_message_to_cli "\n=== Populated LIST_DOMAINS ==="
|
||||||
|
LIST_DOMAINS.each_with_index do |entry, idx|
|
||||||
|
hestia_print_info_message_to_cli "#{idx + 1}. user: #{entry[:user]}, domain: #{entry[:domain]}, is_ssl: #{entry[:is_ssl].to_s}, path_to_ssl: #{entry[:path_to_ssl]}"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
hestia_print_error_message_to_cli "No domains were migrated to BunkerWeb"
|
||||||
|
end
|
||||||
|
|
||||||
|
hestia_print_info_message_to_cli "Stage 11 completed."
|
||||||
|
end
|
||||||
|
else
|
||||||
|
hestia_print_error_message_to_cli "unknown command (use migratetobunkerweb or migratenginx)"
|
||||||
|
log_event E_ARGS, $ARGUMENTS
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
exit 0
|
||||||
267
bin/v-bunkerweb-module
Executable file
267
bin/v-bunkerweb-module
Executable file
@@ -0,0 +1,267 @@
|
|||||||
|
#!/opt/brepo/ruby33/bin/ruby
|
||||||
|
# info: action with bunkerweb API
|
||||||
|
# options: COMMAND [SERVICE_NAME | SSL_CERT | SSL_KEY | FORMAT]
|
||||||
|
#
|
||||||
|
# example: v-bunkerweb-module list json
|
||||||
|
#
|
||||||
|
# This function enables and disables additional modules
|
||||||
|
#
|
||||||
|
# Commands:
|
||||||
|
# add [domain_name] [ip]
|
||||||
|
# addssl [domain_name] [SSL_CERT_PATH] [SSL_KEY_PATH]
|
||||||
|
# delete [domain_name]
|
||||||
|
# updssl [domain] [SSL_CERT_PATH] [SSL_KEY_PATH]
|
||||||
|
# list
|
||||||
|
|
||||||
|
#----------------------------------------------------------#
|
||||||
|
# Variables & Functions #
|
||||||
|
#----------------------------------------------------------#
|
||||||
|
|
||||||
|
# Argument definition
|
||||||
|
v_command = ARGV[0]
|
||||||
|
v_format = nil
|
||||||
|
|
||||||
|
require "/usr/local/hestia/func_ruby/global_options"
|
||||||
|
|
||||||
|
load_ruby_options_defaults
|
||||||
|
$HESTIA = load_hestia_default_path_from_env
|
||||||
|
|
||||||
|
require "main"
|
||||||
|
require "modules"
|
||||||
|
require "HestiaBunkerWebApi"
|
||||||
|
|
||||||
|
require 'json' unless defined?(JSON)
|
||||||
|
|
||||||
|
hestia_check_privileged_user
|
||||||
|
|
||||||
|
load_global_bash_variables "/etc/hestiacp/hestia.conf"
|
||||||
|
if $HESTIA.nil?
|
||||||
|
hestia_print_error_message_to_cli "Can't find HESTIA base path"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
load_global_bash_variables "#{$HESTIA}/conf/hestia.conf"
|
||||||
|
|
||||||
|
#----------------------------------------------------------#
|
||||||
|
# Verifications #
|
||||||
|
#----------------------------------------------------------#
|
||||||
|
|
||||||
|
check_args 1, ARGV, "COMMAND [COMMAND_OPTIONS] [ACTION]"
|
||||||
|
|
||||||
|
# Perform verification if read-only mode is enabled
|
||||||
|
check_hestia_demo_mode
|
||||||
|
|
||||||
|
#----------------------------------------------------------#
|
||||||
|
# Action #
|
||||||
|
#----------------------------------------------------------#
|
||||||
|
|
||||||
|
case v_command.to_sym
|
||||||
|
when :add
|
||||||
|
|
||||||
|
v_domain = ARGV[1].strip
|
||||||
|
v_ip = ARGV[2] ? ARGV[2].strip : nil
|
||||||
|
v_format = ARGV[3] unless ARGV[2].nil?
|
||||||
|
|
||||||
|
if v_domain.nil? || v_domain == "" || v_ip.nil? || v_ip == ""
|
||||||
|
hestia_print_error_message_to_cli "domain and ip 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"]
|
||||||
|
if existing_services["services"].any? { |s| s["id"] == v_domain }
|
||||||
|
hestia_print_error_message_to_cli "domain already exists"
|
||||||
|
log_event E_EXISTS, $ARGUMENTS
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
result_arr = existing_services["services"]
|
||||||
|
else
|
||||||
|
result_arr = []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
api.create_service(v_domain, {
|
||||||
|
ssl: "no",
|
||||||
|
reverse_proxy_host: "http://#{v_ip}:#{$PROXY_PORT}"
|
||||||
|
})
|
||||||
|
rescue BunkerWebApiError => e
|
||||||
|
hestia_print_error_message_to_cli "[ERROR] Ошибка API: #{e.message}"
|
||||||
|
log_event E_INVALID, $ARGUMENTS
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
when :alias
|
||||||
|
|
||||||
|
v_domain = ARGV[1].strip
|
||||||
|
v_alias = ARGV[2].strip
|
||||||
|
v_format = ARGV[3] unless ARGV[3].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.set_alias(v_domain, v_alias)
|
||||||
|
rescue BunkerWebApiError => e
|
||||||
|
hestia_print_error_message_to_cli "[ERROR] Ошибка API: #{e.message}"
|
||||||
|
log_event E_INVALID, $ARGUMENTS
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
when :delete
|
||||||
|
|
||||||
|
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(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 :addssl, :updssl
|
||||||
|
|
||||||
|
v_domain = ARGV[1].strip
|
||||||
|
v_ssl_cert = ARGV[2]
|
||||||
|
v_ssl_key = ARGV[3]
|
||||||
|
v_format = ARGV[4] unless ARGV[4].nil?
|
||||||
|
|
||||||
|
if v_domain.nil? || v_domain == "" || v_ssl_cert.nil? || v_ssl_key.nil? || !File.exist?(v_ssl_cert) || !File.exist?(v_ssl_key)
|
||||||
|
hestia_print_error_message_to_cli "domain, SSL cert and key must be provided and must exist"
|
||||||
|
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.update_service_ssl(v_domain, v_ssl_cert, v_ssl_key)
|
||||||
|
rescue BunkerWebApiError => e
|
||||||
|
hestia_print_error_message_to_cli "[ERROR] Ошибка API: #{e.message}"
|
||||||
|
log_event E_INVALID, $ARGUMENTS
|
||||||
|
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?
|
||||||
|
|
||||||
|
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
|
||||||
|
hestia_print_array_of_hashes(result_arr, v_format, "id, method, is_draft, creation_date, last_update, template, security_mode")
|
||||||
|
rescue BunkerWebApiError => e
|
||||||
|
hestia_print_error_message_to_cli "[ERROR] Ошибка API: #{e.message}"
|
||||||
|
log_event E_INVALID, $ARGUMENTS
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
else
|
||||||
|
hestia_print_error_message_to_cli "unknown command"
|
||||||
|
log_event E_ARGS, $ARGUMENTS
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
exit 0
|
||||||
488
bin/v-bunkerweb-module-install
Executable file
488
bin/v-bunkerweb-module-install
Executable file
@@ -0,0 +1,488 @@
|
|||||||
|
#!/opt/brepo/ruby33/bin/ruby
|
||||||
|
# info: action with bunkerweb API
|
||||||
|
# options: [SSL_CERT_PATH SSL_KEY_PATH]
|
||||||
|
#
|
||||||
|
# example: v-bunkerweb-module-install
|
||||||
|
#
|
||||||
|
# This function enables and disables additional modules
|
||||||
|
#
|
||||||
|
#------------------------------------------#
|
||||||
|
# Variables & Functions #
|
||||||
|
#------------------------------------------#
|
||||||
|
|
||||||
|
# Argument definition
|
||||||
|
|
||||||
|
require "/usr/local/hestia/func_ruby/global_options"
|
||||||
|
|
||||||
|
load_ruby_options_defaults
|
||||||
|
$HESTIA = load_hestia_default_path_from_env
|
||||||
|
|
||||||
|
require "main"
|
||||||
|
require "modules"
|
||||||
|
require "HestiaBunkerWebApi"
|
||||||
|
require "securerandom"
|
||||||
|
require "socket"
|
||||||
|
require 'json' unless defined?(JSON)
|
||||||
|
require 'etc'
|
||||||
|
|
||||||
|
hestia_check_privileged_user
|
||||||
|
|
||||||
|
load_global_bash_variables "/etc/hestiacp/hestia.conf"
|
||||||
|
if $HESTIA.nil?
|
||||||
|
hestia_print_error_message_to_cli "Can't find HESTIA base path"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
load_global_bash_variables "#{$HESTIA}/conf/hestia.conf"
|
||||||
|
|
||||||
|
# Perform verification if read-only mode is enabled
|
||||||
|
check_hestia_demo_mode
|
||||||
|
|
||||||
|
#------------------------------------------#
|
||||||
|
# Action #
|
||||||
|
#------------------------------------------#
|
||||||
|
|
||||||
|
puts "========================================================"
|
||||||
|
puts "=== AUTOMATED BUNKERWEB SETUP SCRIPT ==="
|
||||||
|
puts "========================================================"
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
# Parse command line arguments for SSL cert and key paths
|
||||||
|
SSL_CERT_PATH = ARGV[0] || nil # First argument: SSL certificate path
|
||||||
|
SSL_KEY_PATH = ARGV[1] || nil # Second argument: SSL key path
|
||||||
|
|
||||||
|
# Generate secure passwords (meeting BunkerWeb password policy requirements)
|
||||||
|
api_env_file = "/etc/bunkerweb/api.env"
|
||||||
|
api_password = nil
|
||||||
|
if File.exist?(api_env_file)
|
||||||
|
File.foreach(api_env_file) do |line|
|
||||||
|
if line =~ /^\s*API_PASSWORD=(.*)/
|
||||||
|
val = $1.strip
|
||||||
|
api_password = val unless val.empty?
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
specials = '!@#$%^&*()-_=+[]{}|;:,.<>?'
|
||||||
|
API_PASSWORD = api_password || (SecureRandom.alphanumeric(24) + specials.chars.sample(3).join).chars.shuffle.join
|
||||||
|
|
||||||
|
ui_env_file = "/etc/bunkerweb/ui.env"
|
||||||
|
admin_password = nil
|
||||||
|
if File.exist?(ui_env_file)
|
||||||
|
File.foreach(ui_env_file) do |line|
|
||||||
|
if line =~ /^\s*ADMIN_PASSWORD=(.*)/
|
||||||
|
val = $1.strip
|
||||||
|
admin_password = val unless val.empty?
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
ADMIN_PASSWORD = admin_password || (SecureRandom.alphanumeric(24) + specials.chars.sample(3).join).chars.shuffle.join
|
||||||
|
|
||||||
|
puts "[INFO] Get server IP address"
|
||||||
|
|
||||||
|
server_ip_addr = "127.0.0.1" # default fallback
|
||||||
|
# Attempt to retrieve server IP via Hestia utility
|
||||||
|
begin
|
||||||
|
cmd = "/usr/local/hestia/bin/v-list-sys-ips json"
|
||||||
|
ips_output = `#{cmd}`.strip
|
||||||
|
unless ips_output.empty?
|
||||||
|
parsed_ips = JSON.parse(ips_output)
|
||||||
|
parsed_ips.each do |ip, details|
|
||||||
|
if details["OWNER"] == "admin"
|
||||||
|
server_ip_addr = ip
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rescue JSON::ParserError, Errno::ENOENT
|
||||||
|
# If the command fails or output is invalid, keep default fallback
|
||||||
|
end
|
||||||
|
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
# Compute nginx group ID once
|
||||||
|
nginx_gid = Etc.getgrnam('nginx').gid
|
||||||
|
|
||||||
|
# Step 1: Create /etc/bunkerweb/api.env configuration file
|
||||||
|
puts "[INFO] Creating API configuration at /etc/bunkerweb/api.env..."
|
||||||
|
api_env_content = <<~APIENV
|
||||||
|
# ==============================
|
||||||
|
# BunkerWeb API Configuration
|
||||||
|
# This file lists all supported API environment variables with their defaults.
|
||||||
|
# Uncomment and adjust as needed. Lines starting with # are ignored.
|
||||||
|
# ==============================
|
||||||
|
|
||||||
|
# --- Network & Proxy ---
|
||||||
|
# Listen address/port for the API
|
||||||
|
LISTEN_ADDR=127.0.0.1
|
||||||
|
LISTEN_PORT=8888
|
||||||
|
# Trusted proxy IPs for X-Forwarded-* headers (comma-separated).
|
||||||
|
# Default is restricted to loopback for security.
|
||||||
|
FORWARDED_ALLOW_IPS=127.0.0.1,::1
|
||||||
|
# Trusted proxy IPs for PROXY protocol (comma-separated).
|
||||||
|
# Defaults to FORWARDED_ALLOW_IPS when unset.
|
||||||
|
PROXY_ALLOW_IPS=127.0.0.1,::1
|
||||||
|
|
||||||
|
# --- Logging & Runtime ---
|
||||||
|
# LOG_LEVEL affects most components; CUSTOM_LOG_LEVEL overrides when provided.
|
||||||
|
# LOG_LEVEL=info
|
||||||
|
LOG_TYPES=file
|
||||||
|
LOG_FILE_PATH=/var/log/bunkerweb/api.log
|
||||||
|
# Number of workers/threads (auto if unset).
|
||||||
|
# MAX_WORKERS=<auto>
|
||||||
|
# MAX_THREADS=<auto>
|
||||||
|
|
||||||
|
# --- Authentication & Authorization ---
|
||||||
|
# Optional admin Bearer token (grants full access when provided).
|
||||||
|
# API_TOKEN=#{API_PASSWORD}
|
||||||
|
# Bootstrap admin user (created/validated on startup if provided).
|
||||||
|
API_USERNAME=admin
|
||||||
|
API_PASSWORD=#{API_PASSWORD}
|
||||||
|
# Force re-applying bootstrap admin credentials on startup (use with care).
|
||||||
|
# OVERRIDE_API_CREDS=no
|
||||||
|
# Fine-grained ACLs can be enabled/disabled here.
|
||||||
|
# API_ACL_BOOTSTRAP_FILE=
|
||||||
|
|
||||||
|
# --- IP allowlist ---
|
||||||
|
# Enable and shape inbound IP allowlist for the API.
|
||||||
|
API_WHITELIST_ENABLED=yes
|
||||||
|
WHITELIST_IPS=127.0.0.1
|
||||||
|
|
||||||
|
# --- FastAPI surface ---
|
||||||
|
# Customize or disable documentation endpoints. Use 'disabled' to turn off.
|
||||||
|
# API_TITLE=BunkerWeb API
|
||||||
|
# API_DOCS_URL=/docs
|
||||||
|
# API_REDOC_URL=/redoc
|
||||||
|
# API_OPENAPI_URL=/openapi.json
|
||||||
|
# Mount the API under a subpath (useful behind reverse proxies).
|
||||||
|
# API_ROOT_PATH=
|
||||||
|
|
||||||
|
# --- TLS/SSL ---
|
||||||
|
# Enable TLS for the API listener (requires cert and key).
|
||||||
|
# API_SSL_ENABLED=no
|
||||||
|
# Path to PEM-encoded certificate and private key.
|
||||||
|
# API_SSL_CERTFILE=/etc/ssl/certs/bunkerweb-api.crt
|
||||||
|
# API_SSL_KEYFILE=/etc/ssl/private/bunkerweb-api.key
|
||||||
|
# Optional chain/CA bundle and cipher suite.
|
||||||
|
# API_SSL_CA_CERTS=
|
||||||
|
# API_SSL_CIPHERS_CUSTOM=
|
||||||
|
# API_SSL_CIPHERS_LEVEL=modern # choices: modern|intermediate
|
||||||
|
|
||||||
|
# --- Biscuit keys & policy ---
|
||||||
|
# Bind token to client IP (except private ranges).
|
||||||
|
# CHECK_PRIVATE_IP=yes
|
||||||
|
# Biscuit token lifetime in seconds (0 disables expiry).
|
||||||
|
# API_BISCUIT_TTL_SECONDS=3600
|
||||||
|
# Provide Biscuit keys via env (hex) instead of files.
|
||||||
|
# BISCUIT_PUBLIC_KEY=
|
||||||
|
# BISCUIT_PRIVATE_KEY=
|
||||||
|
|
||||||
|
# --- Rate limiting ---
|
||||||
|
# Enable/disable and shape rate limiting.
|
||||||
|
API_RATE_LIMIT_ENABLED=no
|
||||||
|
API_RATE_LIMIT_HEADERS_ENABLED=no
|
||||||
|
# Global default limit (times per seconds).
|
||||||
|
# API_RATE_LIMIT_TIMES=100
|
||||||
|
# API_RATE_LIMIT_SECONDS=60
|
||||||
|
# Authentication endpoint limit.
|
||||||
|
# API_RATE_LIMIT_AUTH_TIMES=10
|
||||||
|
# API_RATE_LIMIT_AUTH_SECONDS=60
|
||||||
|
# Advanced limits and rules (CSV/JSON/YAML).
|
||||||
|
# API_RATE_LIMIT_DEFAULTS="200/minute"
|
||||||
|
# API_RATE_LIMIT_APPLICATION_LIMITS=
|
||||||
|
# API_RATE_LIMIT_RULES=
|
||||||
|
# Strategy: fixed-window | moving-window | sliding-window-counter
|
||||||
|
# API_RATE_LIMIT_STRATEGY=fixed-window
|
||||||
|
# Key selector: ip | user | path | method | header:<Name>
|
||||||
|
# API_RATE_LIMIT_KEY=ip
|
||||||
|
# Exempt IPs (space or comma-separated CIDRs).
|
||||||
|
# API_RATE_LIMIT_EXEMPT_IPS=
|
||||||
|
# Storage options in JSON (merged with Redis settings if USE_REDIS=yes).
|
||||||
|
# API_RATE_LIMIT_STORAGE_OPTIONS=
|
||||||
|
|
||||||
|
# --- Redis (optional, for rate limiting storage) ---
|
||||||
|
# USE_REDIS=no
|
||||||
|
# REDIS_HOST=
|
||||||
|
# REDIS_PORT=6379
|
||||||
|
# REDIS_DATABASE=0
|
||||||
|
# REDIS_USERNAME=
|
||||||
|
# REDIS_PASSWORD=
|
||||||
|
# REDIS_SSL=no
|
||||||
|
# REDIS_SSL_VERIFY=yes
|
||||||
|
# REDIS_TIMEOUT=1000
|
||||||
|
# REDIS_KEEPALIVE_POOL=10
|
||||||
|
# REDIS_SENTINEL_HOSTS=sentinel1:26379 sentinel2:26379
|
||||||
|
# REDIS_SENTINEL_MASTER=mymaster
|
||||||
|
# REDIS_SENTINEL_USERNAME=
|
||||||
|
# REDIS_SENTINEL_PASSWORD=
|
||||||
|
APIENV
|
||||||
|
|
||||||
|
File.write("/etc/bunkerweb/api.env", api_env_content)
|
||||||
|
File.chmod(0o660, "/etc/bunkerweb/api.env")
|
||||||
|
File.chown(0, nginx_gid, "/etc/bunkerweb/api.env")
|
||||||
|
puts "[SUCCESS] API configuration file created at /etc/bunkerweb/api.env"
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
variables_env_content = <<~VENV
|
||||||
|
DNS_RESOLVERS=9.9.9.9 149.112.112.112 8.8.8.8 8.8.4.4
|
||||||
|
HTTP_PORT=80
|
||||||
|
HTTPS_PORT=443
|
||||||
|
API_LISTEN_IP=127.0.0.1
|
||||||
|
MULTISITE=yes
|
||||||
|
UI_HOST=http://127.0.0.1:7000
|
||||||
|
SERVER_NAME=
|
||||||
|
|
||||||
|
API_WHITELIST_IP=127.0.0.0/8
|
||||||
|
USE_SERVE_FILES=no
|
||||||
|
VENV
|
||||||
|
|
||||||
|
File.write("/etc/bunkerweb/variables.env", variables_env_content)
|
||||||
|
File.chmod(0o660, "/etc/bunkerweb/variables.env")
|
||||||
|
File.chown(0, nginx_gid, "/etc/bunkerweb/variables.env")
|
||||||
|
puts "[SUCCESS] Variables configuration file created at /etc/bunkerweb/variables.env"
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
# Step 2: Enable and start bunkerweb-api service, wait for it to be running
|
||||||
|
puts "[INFO] Enabling bunkerweb-api service..."
|
||||||
|
system("systemctl enable bunkerweb-api")
|
||||||
|
puts "[INFO] Starting bunkerweb-api service..."
|
||||||
|
system("systemctl start bunkerweb-api")
|
||||||
|
|
||||||
|
sleep(30)
|
||||||
|
|
||||||
|
# Wait for the service to be ready (max 30 seconds)
|
||||||
|
puts "[INFO] Waiting for bunkerweb-api service to be running..."
|
||||||
|
max_attempts = 60 # Wait up to 30 seconds (check every half second)
|
||||||
|
attempt = 0
|
||||||
|
|
||||||
|
while attempt < max_attempts
|
||||||
|
status_output = `systemctl is-active bunkerweb-api 2>&1`
|
||||||
|
status = status_output.strip
|
||||||
|
|
||||||
|
if status == "active" || status == "running"
|
||||||
|
puts "[SUCCESS] bunkerweb-api service is running!"
|
||||||
|
break
|
||||||
|
elsif status == "failed"
|
||||||
|
puts "[ERROR] bunkerweb-api service failed to start!"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
print "."
|
||||||
|
sleep(0.5)
|
||||||
|
attempt += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if attempt >= max_attempts
|
||||||
|
puts ""
|
||||||
|
puts "[ERROR] bunkerweb-api service did not become active within timeout"
|
||||||
|
puts "[INFO] Current status: #{status_output.strip}"
|
||||||
|
log_event E_INVALID, $ARGUMENTS
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
# Step 3: Configure UI settings based on documentation at https://docs.bunkerweb.io/latest/web-ui/
|
||||||
|
puts "[INFO] Configuring Web UI..."
|
||||||
|
|
||||||
|
# Determine SSL settings for UI
|
||||||
|
ui_ssl_enabled = "no"
|
||||||
|
if SSL_CERT_PATH && SSL_KEY_PATH && File.exist?(SSL_CERT_PATH) && File.exist?(SSL_KEY_PATH)
|
||||||
|
ui_ssl_enabled = "yes"
|
||||||
|
end
|
||||||
|
|
||||||
|
ui_env_content = <<~UIENV
|
||||||
|
# ==============================
|
||||||
|
# BunkerWeb UI Configuration
|
||||||
|
# This file configures the Web UI settings.
|
||||||
|
# ==============================
|
||||||
|
|
||||||
|
# --- Listener & TLS ---
|
||||||
|
# Bind address for the UI (use server IP for external access)
|
||||||
|
UI_LISTEN_ADDR=127.0.0.1
|
||||||
|
# Bind port for the UI
|
||||||
|
UI_LISTEN_PORT=7000
|
||||||
|
# Enable TLS in the UI container
|
||||||
|
UI_SSL_ENABLED=#{ui_ssl_enabled}
|
||||||
|
UIENV
|
||||||
|
|
||||||
|
# Add SSL cert/key paths if provided
|
||||||
|
if ui_ssl_enabled == "yes"
|
||||||
|
ui_env_content += <<~SSLCONF
|
||||||
|
|
||||||
|
# SSL Certificate and Key paths
|
||||||
|
UI_SSL_CERTFILE=#{SSL_CERT_PATH}
|
||||||
|
UI_SSL_KEYFILE=#{SSL_KEY_PATH}
|
||||||
|
SSLCONF
|
||||||
|
end
|
||||||
|
|
||||||
|
ui_env_content += <<~UIENV2
|
||||||
|
|
||||||
|
# --- Admin Authentication ---
|
||||||
|
# Seed admin account
|
||||||
|
ADMIN_USERNAME=admin
|
||||||
|
ADMIN_PASSWORD=#{ADMIN_PASSWORD}
|
||||||
|
|
||||||
|
# --- Proxy settings ---
|
||||||
|
# Trusted proxy IPs for X-Forwarded-* headers
|
||||||
|
# UI_FORWARDED_ALLOW_IPS=127.0.0.1,::1
|
||||||
|
|
||||||
|
UIENV2
|
||||||
|
|
||||||
|
File.write("/etc/bunkerweb/ui.env", ui_env_content)
|
||||||
|
File.chmod(0o660, "/etc/bunkerweb/ui.env")
|
||||||
|
File.chown(0, nginx_gid, "/etc/bunkerweb/ui.env")
|
||||||
|
puts "[SUCCESS] UI configuration file created at /etc/bunkerweb/ui.env"
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
# Step 4: Reload the bunkerweb-ui service to apply new configuration
|
||||||
|
puts "[INFO] Reloading bunkerweb-ui service..."
|
||||||
|
system("systemctl restart bunkerweb-ui")
|
||||||
|
|
||||||
|
# Wait for UI to be ready (max 10 seconds)
|
||||||
|
sleep(2)
|
||||||
|
|
||||||
|
if system("systemctl is-active bunkerweb-ui >/dev/null 2>&1")
|
||||||
|
puts "[SUCCESS] bunkerweb-ui service is running!"
|
||||||
|
else
|
||||||
|
puts "[WARN] bunkerweb-ui service status could not be verified"
|
||||||
|
end
|
||||||
|
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
# Step 5: Now proceed with the original service creation logic
|
||||||
|
puts "[INFO] Connecting to BunkerWeb API..."
|
||||||
|
|
||||||
|
API_URL = "http://127.0.0.1:8888"
|
||||||
|
|
||||||
|
USERNAME = "admin"
|
||||||
|
PASSWORD = API_PASSWORD
|
||||||
|
|
||||||
|
# Default services to create after setup
|
||||||
|
DEFAULT_SERVICES = [
|
||||||
|
{
|
||||||
|
name: "#{server_ip_addr}",
|
||||||
|
options: {
|
||||||
|
ssl: "no",
|
||||||
|
reverse_proxy_host: "http://127.0.0.1:7000",
|
||||||
|
use_template: "ui",
|
||||||
|
reverse_proxy_url: "/kormilo",
|
||||||
|
use_reverse_proxy: "yes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
# Add more services here if needed:
|
||||||
|
# {
|
||||||
|
# name: "secure.example.com",
|
||||||
|
# options: {
|
||||||
|
# ssl: "yes",
|
||||||
|
# certificate_path: "/etc/ssl/certs/example.crt",
|
||||||
|
# key_path: "/etc/ssl/private/example.key"
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
]
|
||||||
|
|
||||||
|
begin
|
||||||
|
api = HestiaBunkerWebApi.new(API_URL, USERNAME, PASSWORD)
|
||||||
|
|
||||||
|
puts ""
|
||||||
|
puts "[SUCCESS] API connected successfully!"
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
# List existing services
|
||||||
|
services = api.list_services()
|
||||||
|
services = api.list_services()
|
||||||
|
if services && services.is_a?(Hash) && services.key?('services')
|
||||||
|
services = services['services']
|
||||||
|
services = nil if services.is_a?(Array) && services.empty?
|
||||||
|
else
|
||||||
|
services = nil
|
||||||
|
end
|
||||||
|
if services.nil?
|
||||||
|
puts "[INFO] No services found - creating default configuration..."
|
||||||
|
|
||||||
|
DEFAULT_SERVICES.each do |service_config|
|
||||||
|
begin
|
||||||
|
puts "[INFO] Creating service: #{service_config[:name]}"
|
||||||
|
result = api.create_service(service_config[:name], service_config[:options])
|
||||||
|
puts " ✓ Service '#{service_config[:name]}' created"
|
||||||
|
rescue BunkerWebApiError => e
|
||||||
|
if e.message.include?("already exists")
|
||||||
|
puts " ℹ Service '#{service_config[:name]}' already exists, skipping..."
|
||||||
|
else
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "[SUCCESS] Default services created!"
|
||||||
|
|
||||||
|
else
|
||||||
|
puts "[INFO] Existing services:"
|
||||||
|
services.each { |s| puts " - #{s['server_name']}" }
|
||||||
|
puts ""
|
||||||
|
end
|
||||||
|
|
||||||
|
# Reload configuration on all instances
|
||||||
|
puts "[INFO] Reloading configuration..."
|
||||||
|
api.reload_instance()
|
||||||
|
|
||||||
|
puts "[INFO] Restarting bunkerweb service..."
|
||||||
|
system("systemctl restart bunkerweb")
|
||||||
|
puts "[INFO] Restarting bunkerweb-scheduler service..."
|
||||||
|
system("systemctl restart bunkerweb-scheduler")
|
||||||
|
|
||||||
|
puts ""
|
||||||
|
puts "======================================================"
|
||||||
|
puts "=== SETUP COMPLETED SUCCESSFULLY ==="
|
||||||
|
puts "======================================================"
|
||||||
|
puts ""
|
||||||
|
puts "Web UI is now accessible at:"
|
||||||
|
if ui_ssl_enabled == "yes"
|
||||||
|
puts " https://#{server_ip_addr}/kormilo"
|
||||||
|
else
|
||||||
|
puts " http://#{server_ip_addr}/kormilo"
|
||||||
|
end
|
||||||
|
puts ""
|
||||||
|
puts "API URL: #{API_URL}"
|
||||||
|
puts ""
|
||||||
|
puts "API Credentials:"
|
||||||
|
puts " Username: admin"
|
||||||
|
puts " Password: #{PASSWORD}"
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
puts "UI Credentials:"
|
||||||
|
puts " Username: admin"
|
||||||
|
puts " Password: #{ADMIN_PASSWORD}"
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
rescue BunkerWebApiError => e
|
||||||
|
|
||||||
|
if e.message.include?("Authentication") || e.message.include?("Connection refused")
|
||||||
|
puts "[ERROR] Could not connect to BunkerWeb API"
|
||||||
|
puts "[INFO] This means the setup has NOT been completed correctly"
|
||||||
|
puts ""
|
||||||
|
puts "Please verify that:"
|
||||||
|
puts " 1. bunkerweb-api service is running: systemctl status bunkerweb-api"
|
||||||
|
puts " 2. API configuration file exists at /etc/bunkerweb/api.env"
|
||||||
|
puts " 3. Check logs: journalctl -u bunkerweb-api -f"
|
||||||
|
puts ""
|
||||||
|
log_event E_INVALID, $ARGUMENTS
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
else
|
||||||
|
puts "[ERROR] #{e.message}"
|
||||||
|
log_event E_INVALID, $ARGUMENTS
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
rescue => e
|
||||||
|
puts "[ERROR] Unexpected error: #{e.message}"
|
||||||
|
puts "Backtrace:"
|
||||||
|
puts e.backtrace.inspect
|
||||||
|
log_event E_INVALID, $ARGUMENTS
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -229,7 +229,7 @@ if [ -n "$mail_data" ]; then
|
|||||||
echo " ssl_key = <$HOMEDIR/$user/conf/mail/$domain/ssl/$domain.key" >> /etc/dovecot/conf.d/domains/$domain.conf
|
echo " ssl_key = <$HOMEDIR/$user/conf/mail/$domain/ssl/$domain.key" >> /etc/dovecot/conf.d/domains/$domain.conf
|
||||||
echo "}" >> /etc/dovecot/conf.d/domains/$domain.conf
|
echo "}" >> /etc/dovecot/conf.d/domains/$domain.conf
|
||||||
|
|
||||||
# Add domain SSL configuration to exim4
|
# Add domain SSL configuration to exim
|
||||||
# Cleanup symlinks
|
# Cleanup symlinks
|
||||||
find /usr/local/hestia/ssl/mail -xtype l -delete
|
find /usr/local/hestia/ssl/mail -xtype l -delete
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ if [[ "$MAIL_SYSTEM" =~ exim ]]; then
|
|||||||
sed -i "/^$account@$domain_idn:/ d" $HOMEDIR/$user/conf/mail/$domain/limits
|
sed -i "/^$account@$domain_idn:/ d" $HOMEDIR/$user/conf/mail/$domain/limits
|
||||||
echo "$account@$domain_idn:$user_rate_limit" >> $HOMEDIR/$user/conf/mail/$domain/limits
|
echo "$account@$domain_idn:$user_rate_limit" >> $HOMEDIR/$user/conf/mail/$domain/limits
|
||||||
else
|
else
|
||||||
system=$(cat /etc/exim4/limit.conf)
|
system=$(cat /etc/exim/limit.conf)
|
||||||
sed -i "/^$account@$domain_idn:/ d" $HOMEDIR/$user/conf/mail/$domain/limits
|
sed -i "/^$account@$domain_idn:/ d" $HOMEDIR/$user/conf/mail/$domain/limits
|
||||||
echo "$account@$domain_idn:$system" >> $HOMEDIR/$user/conf/mail/$domain/limits
|
echo "$account@$domain_idn:$system" >> $HOMEDIR/$user/conf/mail/$domain/limits
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -68,13 +68,13 @@ if [ "$type" = "pma" ] || [ "$type" = "PMA" ] || [ "$type" = "phpmyadmin" ]; the
|
|||||||
$BIN/v-restart-service httpd
|
$BIN/v-restart-service httpd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -e "/etc/nginx/conf.d/phpmyadmin.inc" ]; then
|
if [ -e "/usr/local/hestia/nginx-system/etc/nginx/conf.d/phpmyadmin.inc" ]; then
|
||||||
rm -f /etc/nginx/conf.d/phpmyadmin.inc
|
rm -f /usr/local/hestia/nginx-system/etc/nginx/conf.d/phpmyadmin.inc
|
||||||
cp -f $HESTIA_INSTALL_DIR/nginx/phpmyadmin.inc /etc/nginx/conf.d/phpmyadmin.inc
|
cp -f $HESTIA_INSTALL_DIR/nginx/phpmyadmin.inc /usr/local/hestia/nginx-system/etc/nginx/conf.d/phpmyadmin.inc
|
||||||
sed -i "s|%pma_alias%|$alias|g" /etc/nginx/conf.d/phpmyadmin.inc
|
sed -i "s|%pma_alias%|$alias|g" /usr/local/hestia/nginx-system/etc/nginx/conf.d/phpmyadmin.inc
|
||||||
|
|
||||||
# Restart services
|
# Restart services
|
||||||
$BIN/v-restart-service nginx
|
$BIN/v-restart-service nginx-system
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -105,13 +105,13 @@ if [ "$type" = "pga" ] || [ "$type" = "PGA" ] || [ "$type" = "phppgadmin" ]; the
|
|||||||
$BIN/v-restart-service httpd
|
$BIN/v-restart-service httpd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -e "/etc/nginx/conf.d/phppgadmin.inc" ]; then
|
if [ -e "/usr/local/hestia/nginx-system/etc/nginx/conf.d/phppgadmin.inc" ]; then
|
||||||
rm -f /etc/nginx/conf.d/phppgadmin.inc
|
rm -f /usr/local/hestia/nginx-system/etc/nginx/conf.d/phppgadmin.inc
|
||||||
cp -f $HESTIA_INSTALL_DIR/nginx/phppgadmin.inc /etc/nginx/conf.d/phppgadmin.inc
|
cp -f $HESTIA_INSTALL_DIR/nginx/phppgadmin.inc /usr/local/hestia/nginx-system/etc/nginx/conf.d/phppgadmin.inc
|
||||||
sed -i "s|%pga_alias%|$alias|g" /etc/nginx/conf.d/phppgadmin.inc
|
sed -i "s|%pga_alias%|$alias|g" /usr/local/hestia/nginx-system/etc/nginx/conf.d/phppgadmin.inc
|
||||||
|
|
||||||
# Restart services
|
# Restart services
|
||||||
$BIN/v-restart-service nginx
|
$BIN/v-restart-service nginx-system
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -65,18 +65,18 @@ fi
|
|||||||
|
|
||||||
# Defining dst config path
|
# Defining dst config path
|
||||||
case $service in
|
case $service in
|
||||||
nginx) dst='/etc/nginx/nginx.conf' ;;
|
nginx) dst='/usr/local/hestia/nginx-system/etc/nginx/nginx.conf' ;;
|
||||||
httpd) dst='/etc/httpd/conf/httpd.conf' ;;
|
httpd) dst='/etc/httpd/conf/httpd.conf' ;;
|
||||||
apache2) dst='/etc/apache2/apache2.conf' ;;
|
apache2) dst='/etc/apache2/apache2.conf' ;;
|
||||||
exim) dst='/etc/exim/exim.conf' ;;
|
exim) dst='/etc/exim/exim.conf' ;;
|
||||||
exim4) dst='/etc/exim4/exim4.conf.template' ;;
|
exim4) dst='/etc/exim/exim.conf.template' ;;
|
||||||
vsftpd) dst=$(find /etc/vsftpd* -name 'vsftpd.conf') ;;
|
vsftpd) dst=$(find /etc/vsftpd* -name 'vsftpd.conf') ;;
|
||||||
proftpd) dst=$(find /etc/proftpd* -name 'proftpd.conf') ;;
|
proftpd) dst=$(find /etc/proftpd* -name 'proftpd.conf') ;;
|
||||||
php)
|
php)
|
||||||
if [ "$LOCAL_PHP" == "yes" ]; then
|
if [ "$LOCAL_PHP" == "yes" ]; then
|
||||||
dst=$(find /opt/brepo/php${php_v} -name php.ini)
|
dst=$(find /opt/brepo/php${php_v} -name php.ini)
|
||||||
else
|
else
|
||||||
dst=$(find /etc/opt/remi/php${php_v} -name php.ini)
|
dst=$(find /etc/opt/remi/php${php_v} -name php.ini)
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
mysql) dst=$(find /etc/my* -name my.cnf) ;;
|
mysql) dst=$(find /etc/my* -name my.cnf) ;;
|
||||||
@@ -136,7 +136,7 @@ if [ "$update" = 'yes' ] && [ "$restart" != 'no' ]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
$BIN/v-restart-service "$service" > /dev/null 2>&1
|
$BIN/v-restart-service "$service" > /dev/null 2>&1
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
for config in $dst; do
|
for config in $dst; do
|
||||||
|
|||||||
@@ -88,6 +88,25 @@ check_result $? "Web restart failed" > /dev/null
|
|||||||
$BIN/v-restart-proxy "$restart"
|
$BIN/v-restart-proxy "$restart"
|
||||||
check_result $? "Proxy restart failed" > /dev/null
|
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 addssl "$domain" "$BUNKW_DIR/$domain.pem" "$BUNKW_DIR/$domain.key"
|
||||||
|
fi
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
$BIN/v-log-action "$user" "Info" "Web" "SSL certificate changed (Domain: $domain)."
|
$BIN/v-log-action "$user" "Info" "Web" "SSL certificate changed (Domain: $domain)."
|
||||||
log_event "$OK" "$ARGUMENTS"
|
log_event "$OK" "$ARGUMENTS"
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ SERVICE_NAME="$1"
|
|||||||
|
|
||||||
case "$SERVICE_NAME" in
|
case "$SERVICE_NAME" in
|
||||||
nginx )
|
nginx )
|
||||||
/usr/sbin/nginx -t >> "$DEBUG_LOG_FILE" 2>&1
|
/usr/local/hestia/nginx-system/sbin/nginx -t >> "$DEBUG_LOG_FILE" 2>&1
|
||||||
V_RESULT=$?
|
V_RESULT=$?
|
||||||
exit $V_RESULT
|
exit $V_RESULT
|
||||||
;;
|
;;
|
||||||
@@ -47,5 +47,3 @@ esac
|
|||||||
|
|
||||||
# Something like error, we shouldn't be here
|
# Something like error, we shouldn't be here
|
||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ if [ -f "$HOMEDIR/$user/conf/web/$domain/$WEB_SYSTEM.fastcgi_cache.conf" ]; then
|
|||||||
rm -rf $HOMEDIR/$user/conf/web/$domain/$WEB_SYSTEM.fastcgi_cache.conf
|
rm -rf $HOMEDIR/$user/conf/web/$domain/$WEB_SYSTEM.fastcgi_cache.conf
|
||||||
fi
|
fi
|
||||||
|
|
||||||
conf='/etc/nginx/conf.d/fastcgi_cache_pool.conf'
|
conf='/usr/local/hestia/nginx-system/etc/nginx/conf.d/fastcgi_cache_pool.conf'
|
||||||
if [ -f "$conf" ]; then
|
if [ -f "$conf" ]; then
|
||||||
sed -i "/ keys_zone=$domain/d" $conf
|
sed -i "/ keys_zone=$domain/d" $conf
|
||||||
if [ ! -s "$conf" ]; then
|
if [ ! -s "$conf" ]; then
|
||||||
@@ -58,7 +58,7 @@ if [ -f "$conf" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Delete FastCGI cache folder
|
# Delete FastCGI cache folder
|
||||||
if [ -d "/var/cache/nginx/micro/$domain" ]; then
|
if [ -d "/usr/local/hestia/nginx-system/var/cache/nginx/micro/$domain" ]; then
|
||||||
rm -rf /var/cache/nginx/micro/$domain
|
rm -rf /var/cache/nginx/micro/$domain
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ is_object_unsuspended 'mail' 'DOMAIN' "$domain"
|
|||||||
# Action #
|
# Action #
|
||||||
#----------------------------------------------------------#
|
#----------------------------------------------------------#
|
||||||
|
|
||||||
rm -f /etc/exim4/domains/${domain}/smtp_relay.conf
|
rm -f /etc/exim/domains/${domain}/smtp_relay.conf
|
||||||
|
|
||||||
#----------------------------------------------------------#
|
#----------------------------------------------------------#
|
||||||
# Hestia #
|
# Hestia #
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ change_sys_value "SMTP_RELAY_HOST" ""
|
|||||||
change_sys_value "SMTP_RELAY_PORT" ""
|
change_sys_value "SMTP_RELAY_PORT" ""
|
||||||
change_sys_value "SMTP_RELAY_USER" ""
|
change_sys_value "SMTP_RELAY_USER" ""
|
||||||
|
|
||||||
rm -f /etc/exim4/smtp_relay.conf
|
rm -f /etc/exim/smtp_relay.conf
|
||||||
|
|
||||||
#----------------------------------------------------------#
|
#----------------------------------------------------------#
|
||||||
# Hestia #
|
# Hestia #
|
||||||
|
|||||||
@@ -170,6 +170,12 @@ check_result $? "Proxy restart failed" > /dev/null
|
|||||||
$BIN/v-restart-web-backend "$restart" "$version"
|
$BIN/v-restart-web-backend "$restart" "$version"
|
||||||
check_result $? "PHP restart failed" > /dev/null
|
check_result $? "PHP 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" ]; then
|
||||||
|
$BIN/v-ext-modules-run bunkerweb_module delete "$domain"
|
||||||
|
fi
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
$BIN/v-log-action "$user" "Info" "Web" "Deleted web domain (Name: $domain)."
|
$BIN/v-log-action "$user" "Info" "Web" "Deleted web domain (Name: $domain)."
|
||||||
log_event "$OK" "$ARGUMENTS"
|
log_event "$OK" "$ARGUMENTS"
|
||||||
|
|||||||
@@ -92,6 +92,13 @@ check_result $? "Web restart failed" > /dev/null
|
|||||||
$BIN/v-restart-proxy "$restart"
|
$BIN/v-restart-proxy "$restart"
|
||||||
check_result $? "Proxy restart failed" > /dev/null
|
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" ]; then
|
||||||
|
get_domain_values 'web'
|
||||||
|
$BIN/v-ext-modules-run bunkerweb_module alias "$domain" "$ALIAS"
|
||||||
|
fi
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
$BIN/v-log-action "$user" "Info" "Web" "Deleted web domain alias (Alias: $dom_alias, Domain: $domain)."
|
$BIN/v-log-action "$user" "Info" "Web" "Deleted web domain alias (Alias: $dom_alias, Domain: $domain)."
|
||||||
log_event "$OK" "$ARGUMENTS"
|
log_event "$OK" "$ARGUMENTS"
|
||||||
|
|||||||
@@ -90,6 +90,16 @@ check_result $? "Web restart failed" > /dev/null
|
|||||||
$BIN/v-restart-proxy "$restart"
|
$BIN/v-restart-proxy "$restart"
|
||||||
check_result $? "Proxy restart failed" > /dev/null
|
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
|
# Logging
|
||||||
$BIN/v-log-action "$user" "Warning" "Web" "SSL disabled (Domain: $domain)."
|
$BIN/v-log-action "$user" "Warning" "Web" "SSL disabled (Domain: $domain)."
|
||||||
log_event "$OK" "$ARGUMENTS"
|
log_event "$OK" "$ARGUMENTS"
|
||||||
|
|||||||
@@ -39,14 +39,10 @@ echo -en "\n---------------------------------------------"
|
|||||||
echo -en "---------------------------------------------\n\n"
|
echo -en "---------------------------------------------\n\n"
|
||||||
|
|
||||||
# Displaying exim stats
|
# Displaying exim stats
|
||||||
if [ -e "/var/log/exim4/mainlog" ]; then
|
eximstats /var/log/exim/main.log 2> /dev/null
|
||||||
eximstats /var/log/exim4/mainlog 2> /dev/null
|
|
||||||
else
|
|
||||||
eximstats /var/log/exim/main.log 2> /dev/null
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "[Exim4] No valid log lines read"
|
echo "[Exim] No valid log lines read"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ csv_list() {
|
|||||||
#----------------------------------------------------------#
|
#----------------------------------------------------------#
|
||||||
|
|
||||||
# Defining config path
|
# Defining config path
|
||||||
config_path='/etc/nginx/nginx.conf'
|
config_path='/usr/local/hestia/nginx-system/etc/nginx/nginx.conf'
|
||||||
|
|
||||||
# Defining keys
|
# Defining keys
|
||||||
keys="worker_processes |worker_connections |send_timeout"
|
keys="worker_processes |worker_connections |send_timeout"
|
||||||
|
|||||||
@@ -48,13 +48,13 @@ conf=$(grep "DOMAIN='$domain'" "$USER_DATA/web.conf")
|
|||||||
parse_object_kv_list "$conf"
|
parse_object_kv_list "$conf"
|
||||||
|
|
||||||
# Purge nginx FastCGI cache
|
# Purge nginx FastCGI cache
|
||||||
if [ -d "/var/cache/nginx/micro/$domain" ]; then
|
if [ -d "/usr/local/hestia/nginx-system/var/cache/nginx/micro/$domain" ]; then
|
||||||
rm -rf /var/cache/nginx/micro/$domain/*
|
rm -rf /usr/local/hestia/nginx-system/var/cache/nginx/micro/$domain/*
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Purge nginx proxy cache
|
# Purge nginx proxy cache
|
||||||
if [ -d "/var/cache/nginx/$domain" ]; then
|
if [ -d "/usr/local/hestia/nginx-system/var/cache/nginx/$domain" ]; then
|
||||||
rm -rf /var/cache/nginx/$domain/*
|
rm -rf /usr/local/hestia/nginx-system/var/cache/nginx/$domain/*
|
||||||
fi
|
fi
|
||||||
|
|
||||||
#----------------------------------------------------------#
|
#----------------------------------------------------------#
|
||||||
|
|||||||
@@ -32,11 +32,8 @@ source $HESTIA/func/syshealth.sh
|
|||||||
source_conf "$HESTIA/conf/hestia.conf"
|
source_conf "$HESTIA/conf/hestia.conf"
|
||||||
|
|
||||||
# Define mail user
|
# Define mail user
|
||||||
if [ "$MAIL_SYSTEM" = 'exim4' ]; then
|
export MAIL_USER="exim"
|
||||||
export MAIL_USER="Debian-exim"
|
|
||||||
else
|
|
||||||
export MAIL_USER="exim"
|
|
||||||
fi
|
|
||||||
|
|
||||||
#----------------------------------------------------------#
|
#----------------------------------------------------------#
|
||||||
# Verifications #
|
# Verifications #
|
||||||
|
|||||||
@@ -30,11 +30,8 @@ source $HESTIA/func/syshealth.sh
|
|||||||
source_conf "$HESTIA/conf/hestia.conf"
|
source_conf "$HESTIA/conf/hestia.conf"
|
||||||
|
|
||||||
# Define mail user
|
# Define mail user
|
||||||
if [ "$MAIL_SYSTEM" = 'exim4' ]; then
|
export MAIL_USER="exim"
|
||||||
export MAIL_USER="Debian-exim"
|
|
||||||
else
|
|
||||||
export MAIL_USER="exim"
|
|
||||||
fi
|
|
||||||
|
|
||||||
#----------------------------------------------------------#
|
#----------------------------------------------------------#
|
||||||
# Verifications #
|
# Verifications #
|
||||||
|
|||||||
@@ -25,10 +25,14 @@ send_email_report() {
|
|||||||
email=$(echo "$email" | cut -f 2 -d "'")
|
email=$(echo "$email" | cut -f 2 -d "'")
|
||||||
tmpfile=$(mktemp)
|
tmpfile=$(mktemp)
|
||||||
subj="$(hostname): $PROXY_SYSTEM restart failed"
|
subj="$(hostname): $PROXY_SYSTEM restart failed"
|
||||||
nginx -t >> $tmpfile 2>&1
|
/usr/local/hestia/nginx-system/sbin/nginx -t >> $tmpfile 2>&1
|
||||||
|
|
||||||
if [ "$1" == "DO_RESTART" ]; then
|
if [ "$1" == "DO_RESTART" ]; then
|
||||||
service "$PROXY_SYSTEM" restart >> $tmpfile 2>&1
|
if [ "$PROXY_SYSTEM" = "nginx" ]; then
|
||||||
|
service nginx-system restart >> $tmpfile 2>&1
|
||||||
|
else
|
||||||
|
service "$PROXY_SYSTEM" restart >> $tmpfile 2>&1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
cat "$tmpfile" | $SENDMAIL -s "$subj" "$email"
|
cat "$tmpfile" | $SENDMAIL -s "$subj" "$email"
|
||||||
if [ "$DEBUG_MODE" = "true" ]; then
|
if [ "$DEBUG_MODE" = "true" ]; then
|
||||||
|
|||||||
@@ -73,11 +73,11 @@ for service in $service_list; do
|
|||||||
$BIN/v-stop-firewall
|
$BIN/v-stop-firewall
|
||||||
$BIN/v-update-firewall
|
$BIN/v-update-firewall
|
||||||
elif [ "$restart" = "ssl" ] && [ "$service" = "nginx" ]; then
|
elif [ "$restart" = "ssl" ] && [ "$service" = "nginx" ]; then
|
||||||
service $service upgrade >> $log 2>&1
|
service nginx-system upgrade >> $log 2>&1
|
||||||
elif [ -z "$restart" -o "$restart" = "no" ] && [ \
|
elif [ -z "$restart" -o "$restart" = "no" ] && [ \
|
||||||
"$service" = "nginx" -o \
|
"$service" = "nginx" -o \
|
||||||
"$service" = "httpd" -o \
|
"$service" = "httpd" -o \
|
||||||
"$service" = "exim4" -o \
|
"$service" = "exim" -o \
|
||||||
"$service" = "dovecot" -o \
|
"$service" = "dovecot" -o \
|
||||||
"$service" = "bind9" -o \
|
"$service" = "bind9" -o \
|
||||||
"$service" = "named" -o \
|
"$service" = "named" -o \
|
||||||
@@ -86,10 +86,19 @@ for service in $service_list; do
|
|||||||
"$service" = "proftpd" -o \
|
"$service" = "proftpd" -o \
|
||||||
"$service" = "ssh" -o \
|
"$service" = "ssh" -o \
|
||||||
"$service" = "fail2ban" ]; then
|
"$service" = "fail2ban" ]; then
|
||||||
systemctl reload-or-restart "$service" >> $log 2>&1
|
if [ "$service" = "nginx" ]; then
|
||||||
|
systemctl reload-or-restart nginx-system >> $log 2>&1
|
||||||
|
else
|
||||||
|
systemctl reload-or-restart "$service" >> $log 2>&1
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
systemctl reset-failed "$service" >> $log 2>&1
|
if [ "$service" = "nginx" ]; then
|
||||||
systemctl restart "$service" >> $log 2>&1
|
systemctl reset-failed nginx-system >> $log 2>&1
|
||||||
|
systemctl restart nginx-system >> $log 2>&1
|
||||||
|
else
|
||||||
|
systemctl reset-failed "$service" >> $log 2>&1
|
||||||
|
systemctl restart "$service" >> $log 2>&1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check the result of the service restart and report whether it failed.
|
# Check the result of the service restart and report whether it failed.
|
||||||
|
|||||||
@@ -643,7 +643,7 @@ if [ "$mail" != 'no' ] && [ -n "$MAIL_SYSTEM" ]; then
|
|||||||
mkdir /usr/local/hestia/ssl/mail
|
mkdir /usr/local/hestia/ssl/mail
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Add domain SSL configuration to exim4
|
# Add domain SSL configuration to exim
|
||||||
# Cleanup broken symlinks
|
# Cleanup broken symlinks
|
||||||
find /usr/local/hestia/ssl/mail -xtype l -delete
|
find /usr/local/hestia/ssl/mail -xtype l -delete
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,11 @@ for service in $service_list; do
|
|||||||
if [ "$service" = "iptables" ]; then
|
if [ "$service" = "iptables" ]; then
|
||||||
$BIN/v-stop-firewall
|
$BIN/v-stop-firewall
|
||||||
else
|
else
|
||||||
systemctl stop "$service"
|
if [ "$service" = "nginx" ]; then
|
||||||
|
systemctl stop nginx-system
|
||||||
|
else
|
||||||
|
systemctl stop "$service"
|
||||||
|
fi
|
||||||
result=$?
|
result=$?
|
||||||
if [ "$result" -ne 0 ]; then
|
if [ "$result" -ne 0 ]; then
|
||||||
$BIN/v-log-action "system" "Error" "System" "Service failed to stop (Name: $service)."
|
$BIN/v-log-action "system" "Error" "System" "Service failed to stop (Name: $service)."
|
||||||
|
|||||||
282
bin/v-system-nginx-migrate
Executable file
282
bin/v-system-nginx-migrate
Executable file
@@ -0,0 +1,282 @@
|
|||||||
|
#!/opt/brepo/ruby33/bin/ruby
|
||||||
|
# info: utility to prepare existing server with hestiacp to use new alternative nginx
|
||||||
|
# options: COMMAND
|
||||||
|
#
|
||||||
|
# example: v-system-nginx-migrate migrate-nginx
|
||||||
|
#
|
||||||
|
# Commands:
|
||||||
|
# migratenginx - move old nginx configs to the new port and path
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
#------------------------------------------#
|
||||||
|
# Variables & Functions #
|
||||||
|
#------------------------------------------#
|
||||||
|
|
||||||
|
# Argument definition
|
||||||
|
v_command = ARGV[0]
|
||||||
|
|
||||||
|
require "/usr/local/hestia/func_ruby/global_options"
|
||||||
|
|
||||||
|
load_ruby_options_defaults
|
||||||
|
$HESTIA = load_hestia_default_path_from_env
|
||||||
|
|
||||||
|
require "main"
|
||||||
|
require "modules"
|
||||||
|
|
||||||
|
require 'json' unless defined?(JSON)
|
||||||
|
require 'fileutils'
|
||||||
|
require 'time'
|
||||||
|
require 'pathname'
|
||||||
|
|
||||||
|
|
||||||
|
def copy_nginx_files(src_root, dest_root)
|
||||||
|
FileUtils.mkdir_p(dest_root)
|
||||||
|
Dir.foreach(src_root) do |entry|
|
||||||
|
next if entry == '.' || entry == '..'
|
||||||
|
next if entry == 'modules' || entry == 'modules-enabled'
|
||||||
|
src_path = File.join(src_root, entry)
|
||||||
|
dest_path = File.join(dest_root, entry)
|
||||||
|
if File.directory?(src_path)
|
||||||
|
FileUtils.mkdir_p(dest_path)
|
||||||
|
copy_nginx_files(src_path, dest_path)
|
||||||
|
else
|
||||||
|
FileUtils.cp(src_path, dest_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Function to log and execute migration stages
|
||||||
|
def log_migrate_stage(stage)
|
||||||
|
log_file = '/usr/local/hestia/log/bunkerweb_migrate_stages.log'
|
||||||
|
# Check if the stage has already been recorded
|
||||||
|
if File.exist?(log_file) && File.readlines(log_file).any? { |line| line.strip == stage }
|
||||||
|
hestia_print_error_message_to_cli "Stage #{stage} already completed, skipping."
|
||||||
|
return
|
||||||
|
end
|
||||||
|
# Execute the stage block
|
||||||
|
begin
|
||||||
|
yield
|
||||||
|
# Record the successful stage
|
||||||
|
File.open(log_file, 'a') { |f| f.puts stage }
|
||||||
|
hestia_print_info_message_to_cli "Stage #{stage} completed."
|
||||||
|
rescue => e
|
||||||
|
hestia_print_error_message_to_cli "Stage #{stage} failed: #{e.message}"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Function to parse and migrate nginx.conf from /usr/local/hestia/nginx-system/etc/nginx/nginx.conf
|
||||||
|
def migrate_nginx_config_from_file(source_path)
|
||||||
|
return false unless File.exist?(source_path)
|
||||||
|
|
||||||
|
hestia_print_info_message_to_cli "Processing nginx config from: #{source_path}"
|
||||||
|
|
||||||
|
content = File.read(source_path)
|
||||||
|
original_content = content.dup
|
||||||
|
|
||||||
|
modified = false
|
||||||
|
|
||||||
|
# Replace all paths starting with /var/ to /usr/local/hestia/nginx-system/var/
|
||||||
|
# This pattern matches any absolute path that starts with /var/ anywhere in the line
|
||||||
|
content = content.gsub(/\/var\//, '/usr/local/hestia/nginx-system/var/')
|
||||||
|
|
||||||
|
# Replace pid path from /run/nginx.pid to /run/nginx-system.pid
|
||||||
|
content = content.gsub(/pid\s+\S+/) { |match| match.gsub('/run/nginx.pid', '/run/nginx-system.pid') }
|
||||||
|
|
||||||
|
if content != original_content
|
||||||
|
File.write(source_path, content)
|
||||||
|
hestia_print_info_message_to_cli "Updated config: #{source_path}"
|
||||||
|
modified = true
|
||||||
|
end
|
||||||
|
|
||||||
|
modified
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_listen(line)
|
||||||
|
# Попытка найти IP:port
|
||||||
|
m = line.match(/^\s*listen\s+([^\s:]+):(\d+)/i)
|
||||||
|
return [m[1], m[2]] if m
|
||||||
|
# Если только порт после listen
|
||||||
|
m = line.match(/^\s*listen\s+(\d+);?\s*$/i)
|
||||||
|
return [nil, m[1]] if m
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
# Helper function to parse and replace ports in listen directives using temp placeholders
|
||||||
|
def parse_and_replace_listen_directive(line, proxy_port, proxy_ssl_port)
|
||||||
|
return line unless line.match?(/\blisten\b/i)
|
||||||
|
|
||||||
|
new_line = line.dup
|
||||||
|
|
||||||
|
# Define target ports (always migrate to these values regardless of input)
|
||||||
|
target_http_port = '8078'
|
||||||
|
target_ssl_port = '8079'
|
||||||
|
|
||||||
|
result = parse_listen(line)
|
||||||
|
return line unless result
|
||||||
|
ip, port = result
|
||||||
|
|
||||||
|
if port == proxy_port
|
||||||
|
if ip
|
||||||
|
new_line.gsub!("#{ip}:#{port}", "#{ip}:#{target_http_port}")
|
||||||
|
else
|
||||||
|
new_line.gsub!(port, target_http_port)
|
||||||
|
end
|
||||||
|
elsif port == proxy_ssl_port
|
||||||
|
if ip
|
||||||
|
new_line.gsub!("#{ip}:#{port}", "#{ip}:#{target_ssl_port}")
|
||||||
|
else
|
||||||
|
new_line.gsub!(port, target_ssl_port)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
new_line
|
||||||
|
end
|
||||||
|
|
||||||
|
hestia_check_privileged_user
|
||||||
|
|
||||||
|
load_global_bash_variables "/etc/hestiacp/hestia.conf"
|
||||||
|
if $HESTIA.nil?
|
||||||
|
hestia_print_error_message_to_cli "Can't find HESTIA base path"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
load_global_bash_variables "#{$HESTIA}/conf/hestia.conf"
|
||||||
|
|
||||||
|
#------------------------------------------#
|
||||||
|
# Verifications #
|
||||||
|
#------------------------------------------#
|
||||||
|
|
||||||
|
check_args 1, ARGV, "COMMAND"
|
||||||
|
|
||||||
|
# Perform verification if read-only mode is enabled
|
||||||
|
check_hestia_demo_mode
|
||||||
|
|
||||||
|
#------------------------------------------#
|
||||||
|
# Action #
|
||||||
|
#------------------------------------------#
|
||||||
|
|
||||||
|
case v_command.to_sym
|
||||||
|
when :migratenginx
|
||||||
|
log_migrate_stage('nstage0') do
|
||||||
|
# Create backup of /etc/nginx with timestamp
|
||||||
|
|
||||||
|
timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
|
||||||
|
backup_root = "/etc/nginx_backup_#{timestamp}"
|
||||||
|
FileUtils.mkdir_p(backup_root)
|
||||||
|
|
||||||
|
src_root = '/etc/nginx'
|
||||||
|
dest_root = backup_root
|
||||||
|
|
||||||
|
# Custom copy function to handle symlinks in conf.d/domains
|
||||||
|
def copy_with_symlinks(src, dest)
|
||||||
|
Dir.foreach(src) do |entry|
|
||||||
|
next if entry == '.' || entry == '..'
|
||||||
|
src_path = File.join(src, entry)
|
||||||
|
dest_path = File.join(dest, entry)
|
||||||
|
|
||||||
|
if File.symlink?(src_path)
|
||||||
|
# Check if symlink is inside conf.d/domains
|
||||||
|
if src_path.include?(File.join('conf.d', 'domains'))
|
||||||
|
# Resolve the target of the symlink
|
||||||
|
target_path = File.readlink(src_path)
|
||||||
|
# Resolve relative symlink paths
|
||||||
|
unless Pathname.new(target_path).absolute?
|
||||||
|
target_path = File.expand_path(target_path, File.dirname(src_path))
|
||||||
|
end
|
||||||
|
if File.exist?(target_path) && File.file?(target_path)
|
||||||
|
content = File.read(target_path)
|
||||||
|
new_file_name = "#{entry}_content.conf"
|
||||||
|
new_file_path = File.join(dest, new_file_name)
|
||||||
|
File.write(new_file_path, content)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
# Preserve the symlink as is
|
||||||
|
FileUtils.mkdir_p(File.dirname(dest_path))
|
||||||
|
target = File.readlink(src_path)
|
||||||
|
FileUtils.ln_s(target, dest_path)
|
||||||
|
end
|
||||||
|
elsif File.directory?(src_path)
|
||||||
|
FileUtils.mkdir_p(dest_path)
|
||||||
|
copy_with_symlinks(src_path, dest_path)
|
||||||
|
else
|
||||||
|
FileUtils.cp(src_path, dest_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
copy_with_symlinks(src_root, dest_root)
|
||||||
|
end
|
||||||
|
log_migrate_stage('nstage2') do
|
||||||
|
unless system('yum install -y nginx-system')
|
||||||
|
hestia_print_error_message_to_cli "Failed to install nginx-system via yum"
|
||||||
|
log_event E_ARGS, $ARGUMENTS
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
log_migrate_stage('nstage3') do
|
||||||
|
FileUtils.rm_f Dir.glob('/usr/local/hestia/nginx-system/etc/nginx/conf.d/*.conf')
|
||||||
|
src_root = '/etc/nginx'
|
||||||
|
dest_root = '/usr/local/hestia/nginx-system/etc/nginx'
|
||||||
|
copy_nginx_files(src_root, dest_root)
|
||||||
|
end
|
||||||
|
log_migrate_stage('nstage4') do
|
||||||
|
# Find all files under the nginx-system directory
|
||||||
|
nginx_conf_dir = '/usr/local/hestia/nginx-system/etc/nginx'
|
||||||
|
Dir.glob(File.join(nginx_conf_dir, '**', '*')).each do |path|
|
||||||
|
hestia_print_info_message_to_cli "stage 4 processing file #{path}"
|
||||||
|
next if File.directory?(path)
|
||||||
|
content = File.read(path)
|
||||||
|
new_content = content.gsub(/(?<!\/usr\/local\/hestia\/nginx-system)\/etc\/nginx/, '/usr/local/hestia/nginx-system/etc/nginx')
|
||||||
|
if new_content != content
|
||||||
|
hestia_print_info_message_to_cli "Changed path to config in file #{path}"
|
||||||
|
File.open(path, 'w') { |f| f.write(new_content) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
log_migrate_stage('nstage4.1') do
|
||||||
|
# Parse nginx.conf file to replace paths
|
||||||
|
if migrate_nginx_config_from_file("/usr/local/hestia/nginx-system/etc/nginx/nginx.conf")
|
||||||
|
hestia_print_info_message_to_cli "Completed migration of nginx.conf paths"
|
||||||
|
else
|
||||||
|
hestia_print_error_message_to_cli "Warning: Could not migrate nginx.conf from #{File.expand_path('/usr/local/hestia/nginx-system/etc/nginx/nginx.conf')}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
log_migrate_stage('nstage7') do
|
||||||
|
if system('/usr/local/hestia/bin/v-ext-modules enable update_module')
|
||||||
|
output = IO.popen("/usr/local/hestia/bin/v-ext-modules state update_module json").read
|
||||||
|
begin
|
||||||
|
parsed = JSON.parse(output)
|
||||||
|
if parsed.is_a?(Array) && parsed.first && parsed.first['STATE'] == 'enabled'
|
||||||
|
system('/usr/local/hestia/bin/v-ext-modules-run update_module synctemplates')
|
||||||
|
else
|
||||||
|
hestia_print_error_message_to_cli "update_module not enabled after enable command"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
rescue JSON::ParserError => e
|
||||||
|
hestia_print_error_message_to_cli "Failed to parse JSON from state command: #{e.message}"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
else
|
||||||
|
hestia_print_error_message_to_cli "Failed to enable update_module"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
log_migrate_stage('stage9') do
|
||||||
|
# Delete all contents inside /etc/nginx
|
||||||
|
FileUtils.rm_rf Dir.glob('/etc/nginx/*')
|
||||||
|
# Stop nginx service
|
||||||
|
system('systemctl stop nginx')
|
||||||
|
system('systemctl disable nginx')
|
||||||
|
# Start nginx-system service
|
||||||
|
system('systemctl enable nginx-system')
|
||||||
|
system('systemctl start nginx-system')
|
||||||
|
end
|
||||||
|
else
|
||||||
|
hestia_print_error_message_to_cli "unknown command (use migratenginx)"
|
||||||
|
log_event E_ARGS, $ARGUMENTS
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -86,7 +86,11 @@ if [ -n "$old_ip" ]; then
|
|||||||
|
|
||||||
# Updating PROXY
|
# Updating PROXY
|
||||||
if [ -n "$PROXY_SYSTEM" ]; then
|
if [ -n "$PROXY_SYSTEM" ]; then
|
||||||
cd /etc/$PROXY_SYSTEM/$pconfd
|
if [ "$PROXY_SYSTEM" = "nginx" ]; then
|
||||||
|
cd /usr/local/hestia/nginx-system/etc/nginx/$pconfd
|
||||||
|
else
|
||||||
|
cd /etc/$PROXY_SYSTEM/$pconfd
|
||||||
|
fi
|
||||||
if [ -e "$old_ip.conf" ]; then
|
if [ -e "$old_ip.conf" ]; then
|
||||||
mv $old_ip.conf $new_ip.conf
|
mv $old_ip.conf $new_ip.conf
|
||||||
sed -i "s/$old_ip/$new_ip/g" $new_ip.conf
|
sed -i "s/$old_ip/$new_ip/g" $new_ip.conf
|
||||||
@@ -95,7 +99,11 @@ if [ -n "$old_ip" ]; then
|
|||||||
|
|
||||||
# Updating WEB
|
# Updating WEB
|
||||||
if [ -n "$WEB_SYSTEM" ]; then
|
if [ -n "$WEB_SYSTEM" ]; then
|
||||||
cd /etc/$WEB_SYSTEM/$confd
|
if [ "$WEB_SYSTEM" = "nginx" ]; then
|
||||||
|
cd /usr/local/hestia/nginx-system/etc/nginx/$confd
|
||||||
|
else
|
||||||
|
cd /etc/$WEB_SYSTEM/$confd
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -e "$old_ip.conf" ]; then
|
if [ -e "$old_ip.conf" ]; then
|
||||||
mv $old_ip.conf $new_ip.conf
|
mv $old_ip.conf $new_ip.conf
|
||||||
@@ -161,8 +169,8 @@ for ip in $ips; do
|
|||||||
prefixlen="$(ip -d -j addr show | jq --arg IP "$ip" -r '.[].addr_info[] | if .local == $IP then .prefixlen else empty end')"
|
prefixlen="$(ip -d -j addr show | jq --arg IP "$ip" -r '.[].addr_info[] | if .local == $IP then .prefixlen else empty end')"
|
||||||
netmask="$(convert_cidr "$prefixlen")"
|
netmask="$(convert_cidr "$prefixlen")"
|
||||||
$BIN/v-add-sys-ip "$ip" "$netmask" "$interface"
|
$BIN/v-add-sys-ip "$ip" "$netmask" "$interface"
|
||||||
elif [ -e "/etc/nginx/conf.d/$ip.conf" ]; then
|
elif [ -e "/usr/local/hestia/nginx-system/etc/nginx/conf.d/$ip.conf" ]; then
|
||||||
process_http2_directive "/etc/nginx/conf.d/$ip.conf"
|
process_http2_directive "/usr/local/hestia/nginx-system/etc/nginx/conf.d/$ip.conf"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,25 @@ check_result $? "Web restart failed" > /dev/null
|
|||||||
$BIN/v-restart-proxy "$restart"
|
$BIN/v-restart-proxy "$restart"
|
||||||
check_result $? "Proxy restart failed" > /dev/null
|
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
|
# Logging
|
||||||
$BIN/v-log-action "$user" "Info" "Web" "SSL certificate updated (Domain: $domain)."
|
$BIN/v-log-action "$user" "Info" "Web" "SSL certificate updated (Domain: $domain)."
|
||||||
log_event "$OK" "$EVENT"
|
log_event "$OK" "$EVENT"
|
||||||
|
|||||||
@@ -47,6 +47,35 @@ export default defineConfig({
|
|||||||
copyright:
|
copyright:
|
||||||
"Copyright © 2019-present Hestia Control Panel и некоторые RPM based компоненты принадлежат bayrepo",
|
"Copyright © 2019-present Hestia Control Panel и некоторые RPM based компоненты принадлежат bayrepo",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
search: {
|
||||||
|
provider: "local",
|
||||||
|
options: {
|
||||||
|
placeholder: "Поиск по документации",
|
||||||
|
minMatchCharLength: 1,
|
||||||
|
threshold: 0.2,
|
||||||
|
distance: 5000,
|
||||||
|
keys: ["title", "content", "headers"],
|
||||||
|
tokenize: (text) => {
|
||||||
|
const tokens = [];
|
||||||
|
const chineseChars = text.match(/[\u4e00-\u9fa5]/g) || [];
|
||||||
|
const englishWords = text.match(/[a-zA-Z0-9]+/g) || [];
|
||||||
|
return [...chineseChars, ...englishWords];
|
||||||
|
},
|
||||||
|
translations: {
|
||||||
|
button: { buttonText: "Поиск по документации" },
|
||||||
|
modal: {
|
||||||
|
noResultsText: "Ничего не найдено",
|
||||||
|
resetButtonTitle: "Сбросить поиск",
|
||||||
|
footer: {
|
||||||
|
selectText: "выбрать",
|
||||||
|
navigateText: "переключить",
|
||||||
|
closeText: "закрыть",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -60,6 +89,7 @@ function nav() {
|
|||||||
link: "/docs/introduction/getting-started.md",
|
link: "/docs/introduction/getting-started.md",
|
||||||
activeMatch: "/docs/",
|
activeMatch: "/docs/",
|
||||||
},
|
},
|
||||||
|
{ text: "Разработчики", link: "/team.md" },
|
||||||
{
|
{
|
||||||
text: `v${version}`,
|
text: `v${version}`,
|
||||||
items: [
|
items: [
|
||||||
|
|||||||
125
docs/.vitepress/theme/components/CopyToClipboardInput.vue
Normal file
125
docs/.vitepress/theme/components/CopyToClipboardInput.vue
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
<template>
|
||||||
|
<div class="CopyToClipboardInput" v-bind="$attrs">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="CopyToClipboardInput-input"
|
||||||
|
:value="value"
|
||||||
|
@focus="selectText"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="CopyToClipboardInput-button"
|
||||||
|
@click="copyToClipboard"
|
||||||
|
title="Copy to clipboard"
|
||||||
|
>
|
||||||
|
Copy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectText = (event) => {
|
||||||
|
if (window.innerWidth > 768) {
|
||||||
|
const inputElement = event.target;
|
||||||
|
inputElement.select();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyToClipboard = async (event) => {
|
||||||
|
const button = event.currentTarget;
|
||||||
|
const inputValue = button.previousElementSibling.value;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const copyMethod = navigator.clipboard
|
||||||
|
? () => navigator.clipboard.writeText(inputValue)
|
||||||
|
: () => {
|
||||||
|
const textarea = document.createElement("textarea");
|
||||||
|
textarea.value = inputValue;
|
||||||
|
document.body.appendChild(textarea);
|
||||||
|
textarea.select();
|
||||||
|
document.execCommand("copy");
|
||||||
|
document.body.removeChild(textarea);
|
||||||
|
};
|
||||||
|
|
||||||
|
await copyMethod();
|
||||||
|
if (navigator.vibrate) navigator.vibrate(50);
|
||||||
|
button.style.transform = "scale(0.95)";
|
||||||
|
button.textContent = "Copied!";
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
button.style.transform = "";
|
||||||
|
button.textContent = "Copy";
|
||||||
|
}, 1000);
|
||||||
|
} catch (err) {
|
||||||
|
button.textContent = "Copy failed";
|
||||||
|
console.error("Failed to copy:", err);
|
||||||
|
setTimeout(() => {
|
||||||
|
button.textContent = "Copy";
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.CopyToClipboardInput {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CopyToClipboardInput-input {
|
||||||
|
font-size: 0.9em;
|
||||||
|
font-family: monospace;
|
||||||
|
flex-grow: 1;
|
||||||
|
border: 1px solid var(--vp-c-border);
|
||||||
|
border-top-left-radius: 4px;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
background-color: var(--vp-c-bg);
|
||||||
|
padding: 8px 13px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: var(--vp-button-brand-bg);
|
||||||
|
}
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
font-size: 16px;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.CopyToClipboardInput-button {
|
||||||
|
font-size: 14px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
color: var(--vp-button-brand-text);
|
||||||
|
min-width: 73px;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 5px 10px;
|
||||||
|
background-color: var(--vp-button-brand-bg);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--vp-button-brand-hover-bg);
|
||||||
|
}
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
min-width: 68px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
touch-action: manipulation;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.96);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,204 +1,250 @@
|
|||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
languages: {
|
|
||||||
required: true,
|
|
||||||
selected: "en",
|
|
||||||
},
|
|
||||||
items: {
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
pageloader: false,
|
|
||||||
hestia_wget:
|
|
||||||
"wget https://raw.githubusercontent.com/bayrepo/hestiacp-rpm/refs/heads/rhel-version/install/hst-install.sh",
|
|
||||||
hestia_wget_devel:
|
|
||||||
"wget https://dev.brepo.ru/bayrepo/hestiacp/raw/branch/master/install/hst-install.sh",
|
|
||||||
hestia_install: "sudo bash hst-install.sh",
|
|
||||||
installStr: "",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getOptionString(item) {
|
|
||||||
if (item.textField) {
|
|
||||||
return item.selected ? `${item.param} '${item.text}'` : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.selectField) {
|
|
||||||
return item.selected ? `${item.param} '${item.text}'` : "";
|
|
||||||
}
|
|
||||||
return `${item.param}${item.selected ? " yes" : " no"}`;
|
|
||||||
},
|
|
||||||
generateString() {
|
|
||||||
const installStr = this.items.map(this.getOptionString).filter(Boolean);
|
|
||||||
|
|
||||||
this.installStr = `${this.hestia_install} ${installStr.join(" ")}`;
|
|
||||||
this.$refs.dialog.showModal();
|
|
||||||
},
|
|
||||||
closeDialog(e) {
|
|
||||||
if (e.target === this.$refs.dialogClose || e.target === this.$refs.dialog) {
|
|
||||||
this.$refs.dialog.close();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
checkNeedEnabled(e) {
|
|
||||||
if (e.target.value != "") {
|
|
||||||
let id = e.target.getAttribute("target");
|
|
||||||
if (!document.getElementById(id).checked) {
|
|
||||||
document.getElementById(id).click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
toggleOption(e) {
|
|
||||||
if (e.target.checked) {
|
|
||||||
let conflicts = e.target.getAttribute("conflicts");
|
|
||||||
if (conflicts) {
|
|
||||||
if (document.getElementById(conflicts).checked) {
|
|
||||||
document.getElementById(conflicts).click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let depends = e.target.getAttribute("depends");
|
|
||||||
if (depends) {
|
|
||||||
if (!document.getElementById(depends).checked) {
|
|
||||||
document.getElementById(depends).click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
copyToClipboard(text, button) {
|
|
||||||
navigator.clipboard.writeText(text).then(
|
|
||||||
() => {
|
|
||||||
button.textContent = "Copied!";
|
|
||||||
setTimeout(() => {
|
|
||||||
button.textContent = "Copy";
|
|
||||||
}, 1000);
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
console.error("Could not copy to clipboard:", err);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="container">
|
<div class="InstallOptions">
|
||||||
<div class="grid">
|
<div class="container">
|
||||||
<div class="form-group" v-for="item in items">
|
<div class="output-card">
|
||||||
<div class="form-check u-mb10">
|
<h1 class="u-text-center">Инструкция по установке для RHEL</h1>
|
||||||
<input
|
|
||||||
@change="toggleOption"
|
|
||||||
type="checkbox"
|
|
||||||
class="form-check-input"
|
|
||||||
v-model="item.selected"
|
|
||||||
:value="item.value"
|
|
||||||
:id="item.id"
|
|
||||||
:conflicts="item.conflicts"
|
|
||||||
:depends="item.depends"
|
|
||||||
/>
|
|
||||||
<label :for="item.id">{{ item.id }}</label>
|
|
||||||
</div>
|
|
||||||
<template v-if="item.textField || item.selectField">
|
|
||||||
<label class="form-label" :for="'input-' + item.id">{{ item.desc }}</label>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<p>{{ item.desc }}</p>
|
|
||||||
</template>
|
|
||||||
<div v-if="item.textField">
|
|
||||||
<input
|
|
||||||
@change="checkNeedEnabled"
|
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
v-model="item.text"
|
|
||||||
:target="item.id"
|
|
||||||
:id="'input-' + item.id"
|
|
||||||
:type="'+item.type+'"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div v-if="item.selectField">
|
|
||||||
<select class="form-select" v-model="item.text" :id="'input-' + item.id">
|
|
||||||
<option v-for="language in languages" :value="language.value" :key="language.value">
|
|
||||||
{{ language.text }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="u-text-center u-mb10">
|
|
||||||
<button @click="generateString" class="form-submit" type="button">Submit</button>
|
|
||||||
</div>
|
|
||||||
<dialog ref="dialog" class="modal" @click="closeDialog">
|
|
||||||
<button class="modal-close" @click="closeDialog" type="button" ref="dialogClose">
|
|
||||||
Close
|
|
||||||
</button>
|
|
||||||
<div ref="dialogContent" class="modal-content">
|
|
||||||
<h1 class="modal-heading">Installation instructions</h1>
|
|
||||||
<p class="u-mb10">
|
<p class="u-mb10">
|
||||||
Log in to your server as root, either directly or via SSH:
|
Войдите на свой сервер как root, напрямую или через SSH: ssh root@ваш.сервер, и загрузите
|
||||||
<code>ssh root@your.server</code> and download the installation script:
|
установочный скрипт:
|
||||||
</p>
|
</p>
|
||||||
<div class="u-pos-relative">
|
<CopyToClipboardInput
|
||||||
<input
|
class="u-mb10"
|
||||||
type="text"
|
value="wget https://raw.githubusercontent.com/bayrepo/hestiacp-rpm/refs/heads/rhel-version/install/hst-install.sh"
|
||||||
class="form-control u-monospace u-mb10"
|
style="font-size: 1.2em"
|
||||||
v-model="hestia_wget"
|
/>
|
||||||
readonly
|
<p class="u-mb10">Для загрузки разработочной версии используйте:</p>
|
||||||
/>
|
<CopyToClipboardInput
|
||||||
<button
|
class="u-mb10"
|
||||||
class="button-positioned"
|
value="wget https://dev.brepo.ru/bayrepo/hestiacp/raw/branch/master/install/hst-install.sh"
|
||||||
@click="copyToClipboard(hestia_wget, $event.target)"
|
style="font-size: 1.2em"
|
||||||
type="button"
|
/>
|
||||||
title="Copy to Clipboard"
|
<p class="u-mb10">
|
||||||
>
|
Убедитесь, что вы вошли на сервер как <code>root</code>. Настройте параметры в полях ниже,
|
||||||
Copy
|
затем скопируйте и выполните готовую команду в терминале сервера:
|
||||||
</button>
|
</p>
|
||||||
</div>
|
<CopyToClipboardInput class="u-mb10" :value="installCommand" style="font-size: 1.2em" />
|
||||||
<p class="u-mb10">For downloading devel version use:</p>
|
|
||||||
<div class="u-pos-relative">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="form-control u-monospace u-mb10"
|
|
||||||
v-model="hestia_wget_devel"
|
|
||||||
readonly
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
class="button-positioned"
|
|
||||||
@click="copyToClipboard(hestia_wget_devel, $event.target)"
|
|
||||||
type="button"
|
|
||||||
title="Copy to Clipboard"
|
|
||||||
>
|
|
||||||
Copy
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<p class="u-mb10">Then run the following command:</p>
|
|
||||||
<div class="u-pos-relative">
|
|
||||||
<textarea class="form-control u-min-height100" v-model="installStr" readonly />
|
|
||||||
<button
|
|
||||||
class="button-positioned"
|
|
||||||
@click="copyToClipboard(installStr, $event.target)"
|
|
||||||
type="button"
|
|
||||||
title="Copy to Clipboard"
|
|
||||||
>
|
|
||||||
Copy
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</dialog>
|
<h2 class="u-text-center">Настройка компонентов: выберите необходимые для установки</h2>
|
||||||
|
<h3 class="u-text-center">
|
||||||
|
<i class="fa-solid fa-triangle-exclamation"></i> Примечание: Рекомендуется выполнить полную
|
||||||
|
настройку здесь, иначе вам придётся вводить данные в терминале! (Пункты 1.2.3.4.5
|
||||||
|
обязательны)
|
||||||
|
</h3>
|
||||||
|
<ul class="option-list">
|
||||||
|
<li
|
||||||
|
v-for="option in options"
|
||||||
|
:key="option.flag"
|
||||||
|
:class="{
|
||||||
|
'option-item': true,
|
||||||
|
'is-active': selectedOptions[option.flag].enabled,
|
||||||
|
'is-clickable': !option.type || !selectedOptions[option.flag].enabled,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="option-header">
|
||||||
|
<div class="form-check">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="form-check-input"
|
||||||
|
:id="option.flag"
|
||||||
|
v-model="selectedOptions[option.flag].enabled"
|
||||||
|
:conflicts="option.conflicts"
|
||||||
|
:depends="option.depends"
|
||||||
|
@change="toggleOption"
|
||||||
|
/>
|
||||||
|
<label :for="option.flag" @click.stop>{{ option.label }}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="option-description" v-html="option.description"></div>
|
||||||
|
<div v-if="option.type === 'text'" class="option-content">
|
||||||
|
<input
|
||||||
|
class="form-control"
|
||||||
|
type="text"
|
||||||
|
:id="`${option.flag}-input`"
|
||||||
|
v-model="selectedOptions[option.flag].value"
|
||||||
|
:placeholder="option.default"
|
||||||
|
@input="checkNeedEnabled(option.flag, $event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-if="option.type === 'select'" class="option-content">
|
||||||
|
<select
|
||||||
|
class="form-select"
|
||||||
|
:id="`${option.flag}-input`"
|
||||||
|
v-model="selectedOptions[option.flag].value"
|
||||||
|
@change="checkNeedEnabled(option.flag, $event)"
|
||||||
|
>
|
||||||
|
<option v-for="opt in option.options" :key="opt.value" :value="opt.value">
|
||||||
|
{{ opt.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watchEffect } from "vue";
|
||||||
|
import CopyToClipboardInput from "./CopyToClipboardInput.vue";
|
||||||
|
|
||||||
|
const { options } = defineProps({
|
||||||
|
options: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedOptions = ref({});
|
||||||
|
options.forEach((option) => {
|
||||||
|
const isPortOrLang = ["port", "lang"].includes(option.flag);
|
||||||
|
selectedOptions.value[option.flag] = {
|
||||||
|
enabled: isPortOrLang || option.default === "yes",
|
||||||
|
value: option.default !== "yes" && option.default !== "no" ? option.default : null,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const checkNeedEnabled = (flag, event) => {
|
||||||
|
if (event.target.value !== "" && event.target.value !== null) {
|
||||||
|
if (!selectedOptions.value[flag].enabled) {
|
||||||
|
selectedOptions.value[flag].enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleOption = (event) => {
|
||||||
|
const checkbox = event.target;
|
||||||
|
const flag = checkbox.id;
|
||||||
|
const conflicts = checkbox.getAttribute("conflicts");
|
||||||
|
const depends = checkbox.getAttribute("depends");
|
||||||
|
|
||||||
|
if (checkbox.checked) {
|
||||||
|
if (conflicts) {
|
||||||
|
const conflictFlag = conflicts;
|
||||||
|
if (selectedOptions.value[conflictFlag]?.enabled) {
|
||||||
|
selectedOptions.value[conflictFlag].enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (depends) {
|
||||||
|
const dependFlag = depends;
|
||||||
|
if (!selectedOptions.value[dependFlag]?.enabled) {
|
||||||
|
selectedOptions.value[dependFlag].enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const installCommand = ref("bash hst-install.sh");
|
||||||
|
watchEffect(() => {
|
||||||
|
let cmd = "sudo bash hst-install.sh";
|
||||||
|
const quoteshellarg = (str) => {
|
||||||
|
if (!str) return "''";
|
||||||
|
return `'${str.replace(/'/g, "'\\''")}'`;
|
||||||
|
};
|
||||||
|
for (const [key, { enabled, value }] of Object.entries(selectedOptions.value)) {
|
||||||
|
const opt = options.find((o) => o.flag === key);
|
||||||
|
if (!opt.type || opt.type === "checkbox") {
|
||||||
|
if (enabled !== (opt.default === "yes")) {
|
||||||
|
cmd += ` --${key} ${enabled ? "yes" : "no"}`;
|
||||||
|
}
|
||||||
|
} else if (enabled && value !== opt.default) {
|
||||||
|
const value_quoted = quoteshellarg(value);
|
||||||
|
cmd += ` --${key} ${value_quoted}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
installCommand.value = cmd;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.InstallOptions {
|
||||||
|
padding: 0 24px;
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
padding: 0 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
padding: 0 72px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:root {
|
||||||
|
--text-color-light: #282828;
|
||||||
|
--text-color-dark: #f0f0f0;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
color: var(--text-color-light);
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
color: var(--text-color-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: #b7236a;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
h1 {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: var(--text-color-light);
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: var(--vp-button-brand-active-bg);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a:visited {
|
||||||
|
color: var(--vp-button-brand-active-bg);
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: var(--vp-button-brand-hover-bg);
|
||||||
|
}
|
||||||
|
a:active {
|
||||||
|
color: var(--vp-button-brand-hover-bg);
|
||||||
|
}
|
||||||
.container {
|
.container {
|
||||||
margin: 0px auto;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 0 auto;
|
||||||
max-width: 1152px;
|
max-width: 1152px;
|
||||||
}
|
}
|
||||||
.grid {
|
.output-card {
|
||||||
|
background-color: var(--vp-c-bg-alt);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 30px;
|
||||||
|
margin-top: 40px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
padding: 30px 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.option-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: 20px;
|
grid-gap: 23px;
|
||||||
margin-top: 30px;
|
margin-bottom: 50px;
|
||||||
margin-bottom: 30px;
|
|
||||||
|
|
||||||
@media (min-width: 640px) {
|
@media (min-width: 640px) {
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
@@ -208,17 +254,87 @@ export default {
|
|||||||
grid-template-columns: 1fr 1fr 1fr;
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.form-group {
|
.option-item {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
padding: 15px 20px;
|
border: 2px solid transparent;
|
||||||
|
padding: 10px 20px;
|
||||||
background-color: var(--vp-c-bg-alt);
|
background-color: var(--vp-c-bg-alt);
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--vp-button-brand-hover-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
border-color: var(--vp-button-brand-active-bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.option-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.option-icon {
|
||||||
|
padding: 5px;
|
||||||
|
margin-right: -5px;
|
||||||
|
|
||||||
|
& i {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover i {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.option-description {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
.option-description pre {
|
||||||
|
background-color: var(--vp-c-bg, #f5f5f5);
|
||||||
|
border: 1px solid var(--vp-c-border, #e0e0e0);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin: 12px 0;
|
||||||
|
font-family: "SF Mono", Monaco, "Cascadia Code", "Roboto Mono", monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.option-description code {
|
||||||
|
font-family: "SF Mono", Monaco, "Cascadia Code", "Roboto Mono", monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
background-color: var(--vp-c-bg, #f0f0f0);
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.option-description pre code {
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.option-description pre {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
border-color: #444;
|
||||||
|
}
|
||||||
|
.option-description code {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.option-content {
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
.form-label {
|
.form-label {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: 2px;
|
font-size: 16px;
|
||||||
|
line-height: 1.5;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
}
|
||||||
.form-control {
|
.form-control {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
@@ -242,7 +358,7 @@ export default {
|
|||||||
border: 1px solid var(--vp-c-border);
|
border: 1px solid var(--vp-c-border);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: var(--vp-c-bg);
|
background-color: var(--vp-c-bg);
|
||||||
padding: 5px 10px;
|
padding: 6px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@@ -254,100 +370,36 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.form-check {
|
.form-check {
|
||||||
|
flex-grow: 1;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-left: 20px;
|
padding-left: 25px;
|
||||||
margin-left: 3px;
|
|
||||||
min-height: 24px;
|
|
||||||
|
|
||||||
& label {
|
& label {
|
||||||
|
font-size: 16px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
display: block;
|
||||||
|
line-height: 1.6;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.form-check-input {
|
.form-check-input {
|
||||||
|
cursor: pointer;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
margin-left: -20px;
|
margin-left: -25px;
|
||||||
}
|
|
||||||
.form-submit {
|
|
||||||
border: 1px solid transparent;
|
|
||||||
display: inline-block;
|
|
||||||
font-weight: 600;
|
|
||||||
transition: color 0.25s, border-color 0.25s, background-color 0.25s;
|
|
||||||
border-radius: 20px;
|
|
||||||
font-size: 16px;
|
|
||||||
padding: 10px 20px;
|
|
||||||
background-color: var(--vp-button-brand-bg);
|
|
||||||
border-color: var(--vp-button-brand-border);
|
|
||||||
color: var(--vp-button-brand-text);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--vp-button-brand-hover-bg);
|
|
||||||
border-color: var(--vp-button-brand-hover-border);
|
|
||||||
color: var(--vp-button-brand-hover-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background-color: var(--vp-button-brand-active-bg);
|
|
||||||
border-color: var(--vp-button-brand-active-border);
|
|
||||||
color: var(--vp-button-brand-active-text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.button-positioned {
|
|
||||||
position: absolute;
|
|
||||||
right: 1px;
|
|
||||||
top: 1px;
|
|
||||||
border-top-right-radius: 3px;
|
|
||||||
border-bottom-right-radius: 3px;
|
|
||||||
color: var(--vp-c-brand);
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 6px 10px;
|
|
||||||
background-color: var(--vp-c-bg);
|
|
||||||
}
|
|
||||||
.modal {
|
|
||||||
position: fixed;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid var(--vp-c-border);
|
|
||||||
box-shadow: 0 8px 40px 0 rgb(0 0 0 / 35%);
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
&::backdrop {
|
|
||||||
background-color: rgb(0 0 0 / 50%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.modal-close {
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
right: 15px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--vp-c-brand);
|
|
||||||
}
|
|
||||||
.modal-content {
|
|
||||||
padding: 30px;
|
|
||||||
}
|
|
||||||
.modal-heading {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 1.3em;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
code {
|
|
||||||
background-color: var(--vp-c-bg-alt);
|
|
||||||
border-radius: 3px;
|
|
||||||
padding: 2px 5px;
|
|
||||||
}
|
}
|
||||||
.u-mb10 {
|
.u-mb10 {
|
||||||
margin-bottom: 10px !important;
|
margin-bottom: 10px !important;
|
||||||
}
|
}
|
||||||
.u-min-height100 {
|
|
||||||
min-height: 100px;
|
|
||||||
}
|
|
||||||
.u-text-center {
|
.u-text-center {
|
||||||
text-align: center !important;
|
text-align: center !important;
|
||||||
}
|
}
|
||||||
.u-monospace {
|
.is-clickable {
|
||||||
font-family: monospace !important;
|
cursor: pointer;
|
||||||
}
|
|
||||||
.u-pos-relative {
|
|
||||||
position: relative !important;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
<template>
|
|
||||||
<form class="InstallForm" id="form">
|
|
||||||
<div class="InstallOptionsSection">
|
|
||||||
<slot name="list" />
|
|
||||||
</div>
|
|
||||||
<cite
|
|
||||||
>Based on: <a href="https://github.com/gabizz/hestiacp-scriptline-generator">@gabizz</a> and
|
|
||||||
<a href="https://github.com/turbopixel/HestiaCP-Command-Creator">@turbopixel</a></cite
|
|
||||||
>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.InstallForm {
|
|
||||||
margin: 0.55em 0;
|
|
||||||
padding: 0 1em;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
cite {
|
|
||||||
font-size: small;
|
|
||||||
margin: 0.55em 0;
|
|
||||||
display: block;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
& a {
|
|
||||||
color: var(--vp-c-txt-1) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -6,23 +6,6 @@
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.InstallPage {
|
.InstallPage {
|
||||||
line-height: 1.5;
|
line-height: 1;
|
||||||
}
|
|
||||||
.InstallPage :deep(.container) {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
margin: 0 auto;
|
|
||||||
max-width: 1152px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.InstallPage :deep(a) {
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--vp-c-brand);
|
|
||||||
text-decoration-style: dotted;
|
|
||||||
transition: color 0.25s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.InstallPage :deep(a:hover) {
|
|
||||||
color: var(--vp-c-brand-dark);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
<template>
|
|
||||||
<header class="InstallPageTitle">
|
|
||||||
<div class="container">
|
|
||||||
<h1>
|
|
||||||
<slot name="title"></slot>
|
|
||||||
</h1>
|
|
||||||
<p v-if="$slots.lead" class="lead">
|
|
||||||
<slot name="lead" />
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.InstallPageTitle {
|
|
||||||
padding: 0 24px;
|
|
||||||
background-color: var(--vp-c-bg-alt);
|
|
||||||
}
|
|
||||||
|
|
||||||
.InstallPageTitle h1 {
|
|
||||||
margin: 0.75em 0;
|
|
||||||
font-size: 2rem;
|
|
||||||
font-weight: 700;
|
|
||||||
line-height: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 640px) {
|
|
||||||
.InstallPageTitle {
|
|
||||||
padding: 0 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.InstallPageTitle h1 {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 960px) {
|
|
||||||
.InstallPageTitle {
|
|
||||||
padding: 0 64px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
82
docs/.vitepress/theme/components/PageHeader.vue
Normal file
82
docs/.vitepress/theme/components/PageHeader.vue
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<template>
|
||||||
|
<header class="PageHeader">
|
||||||
|
<div class="container">
|
||||||
|
<h1>
|
||||||
|
<slot name="title"></slot>
|
||||||
|
</h1>
|
||||||
|
<h2 class="subtitle">
|
||||||
|
<slot name="subtitle"></slot>
|
||||||
|
</h2>
|
||||||
|
<div v-if="$slots.aside" class="aside-container">
|
||||||
|
<slot name="aside"></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:root {
|
||||||
|
--text-color-light: #282828;
|
||||||
|
--text-color-dark: var(--el-color-primary-light-9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
margin: 0 auto;
|
||||||
|
text-align: center;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
max-width: 1260px;
|
||||||
|
color: var(--text-color-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.aside-container {
|
||||||
|
display: flex;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
margin: -0.5em 0 1.2em 0;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: inherit;
|
||||||
|
text-align: center;
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle :deep(a) {
|
||||||
|
color: var(--vp-c-brand);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle :deep(a):hover {
|
||||||
|
color: #d8036a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.PageHeader {
|
||||||
|
padding: 0 24px;
|
||||||
|
background-color: var(--vp-c-bg-alt);
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0.75em 0;
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: inherit;
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
padding: 0 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
padding: 0 64px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -6,11 +6,12 @@ import './styles/base.css';
|
|||||||
import './styles/vars.css';
|
import './styles/vars.css';
|
||||||
import FeaturePage from './components/FeaturePage.vue';
|
import FeaturePage from './components/FeaturePage.vue';
|
||||||
import InstallPage from './components/InstallPage.vue';
|
import InstallPage from './components/InstallPage.vue';
|
||||||
|
import PageHeader from './components/PageHeader.vue';
|
||||||
export default {
|
export default {
|
||||||
...Theme,
|
...Theme,
|
||||||
enhanceApp({ app }) {
|
enhanceApp({ app }) {
|
||||||
app.component('FeaturePage', FeaturePage);
|
app.component('FeaturePage', FeaturePage);
|
||||||
app.component('InstallPage', InstallPage);
|
app.component('InstallPage', InstallPage);
|
||||||
|
app.component('PageHeader', PageHeader);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,10 +2,14 @@
|
|||||||
--vp-c-brand-lighter: #ff558f;
|
--vp-c-brand-lighter: #ff558f;
|
||||||
--vp-c-brand-light: #da4778;
|
--vp-c-brand-light: #da4778;
|
||||||
--vp-c-brand: #b7236a;
|
--vp-c-brand: #b7236a;
|
||||||
--vp-c-brand-dark: #9a1d5a;
|
--vp-c-brand-dark: #e2237f;
|
||||||
--vp-c-brand-darker: #781746;
|
--vp-c-brand-darker: #c00b62;
|
||||||
--vp-home-hero-name-color: transparent;
|
--vp-home-hero-name-color: transparent;
|
||||||
--vp-home-hero-name-background: linear-gradient(120deg, #d83182, #633288, #41d1ff);
|
--vp-home-hero-name-background: linear-gradient(120deg, #d83182, #633288, #41d1ff);
|
||||||
|
--vp-button-brand-bg: #b7236a;
|
||||||
|
--vp-button-brand-hover-bg: #d8036a;
|
||||||
|
--vp-button-brand-active-bg: #e7197d;
|
||||||
|
--vp-c-brand-1: #b7236a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
@@ -15,4 +19,8 @@
|
|||||||
--vp-c-brand: #ff558f;
|
--vp-c-brand: #ff558f;
|
||||||
--vp-c-brand-dark: #da4778;
|
--vp-c-brand-dark: #da4778;
|
||||||
--vp-c-brand-darker: #b7236a;
|
--vp-c-brand-darker: #b7236a;
|
||||||
|
--vp-button-brand-bg: #da4778;
|
||||||
|
--vp-button-brand-hover-bg: #b7236a;
|
||||||
|
--vp-c-brand-1: #da4778;
|
||||||
|
--vp-code-block-bg: #282c34;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
export const languages = [
|
|
||||||
{ text: 'Arabic', value: 'ar' },
|
|
||||||
{ text: 'Armenian', value: 'hy' },
|
|
||||||
{ text: 'Azerbaijani', value: 'az' },
|
|
||||||
{ text: 'Bengali', value: 'bn' },
|
|
||||||
{ text: 'Bosnian', value: 'bs' },
|
|
||||||
{ text: 'Bulgarian', value: 'bg' },
|
|
||||||
{ text: 'Catalan', value: 'ca' },
|
|
||||||
{ text: 'Croatian', value: 'hr' },
|
|
||||||
{ text: 'Czech', value: 'cs' },
|
|
||||||
{ text: 'Danish', value: 'da' },
|
|
||||||
{ text: 'Dutch', value: 'nl' },
|
|
||||||
{ text: 'English', value: 'en' },
|
|
||||||
{ text: 'Finnish', value: 'fi' },
|
|
||||||
{ text: 'French', value: 'fr' },
|
|
||||||
{ text: 'Georgian', value: 'ka' },
|
|
||||||
{ text: 'German', value: 'de' },
|
|
||||||
{ text: 'Greek', value: 'el' },
|
|
||||||
{ text: 'Hungarian', value: 'hu' },
|
|
||||||
{ text: 'Indonesian', value: 'id' },
|
|
||||||
{ text: 'Italian', value: 'it' },
|
|
||||||
{ text: 'Japanese', value: 'ja' },
|
|
||||||
{ text: 'Korean', value: 'ko' },
|
|
||||||
{ text: 'Kurdish Sorani', value: 'ku' },
|
|
||||||
{ text: 'Norwegain', value: 'no' },
|
|
||||||
{ text: 'Persian', value: 'fa' },
|
|
||||||
{ text: 'Polish', value: 'pl' },
|
|
||||||
{ text: 'Portuguese', value: 'pt' },
|
|
||||||
{ text: 'Portuguese (Brasil)', value: 'pt-br' },
|
|
||||||
{ text: 'Romanian', value: 'ro' },
|
|
||||||
{ text: 'Russian', value: 'ru' },
|
|
||||||
{ text: 'Serbian', value: 'sr' },
|
|
||||||
{ text: 'Simplified Chinese (China)', value: 'zh-cn' },
|
|
||||||
{ text: 'Slovak', value: 'sk' },
|
|
||||||
{ text: 'Spanish', value: 'es' },
|
|
||||||
{ text: 'Swedish', value: 'sv' },
|
|
||||||
{ text: 'Thai', value: 'th' },
|
|
||||||
{ text: 'Traditional Chinese (Taiwan)', value: 'zh-tw' },
|
|
||||||
{ text: 'Turkish', value: 'tr' },
|
|
||||||
{ text: 'Ukrainian', value: 'uk' },
|
|
||||||
{ text: 'Urdu', value: 'ur' },
|
|
||||||
{ text: 'Vietnamese', value: 'vi' },
|
|
||||||
];
|
|
||||||
@@ -1,207 +1,253 @@
|
|||||||
export const options = [
|
export const options = [
|
||||||
{
|
{
|
||||||
name: ' --port',
|
flag: 'port',
|
||||||
id: 'port',
|
label: 'Порт управления',
|
||||||
param: '--port',
|
description: 'Установите порт HTTPS панели Hestia. По умолчанию: 8083. Обязательный параметр!',
|
||||||
desc: 'Изменить порт Hestia',
|
type: 'text',
|
||||||
selected: true,
|
required: 'true',
|
||||||
text: '8083',
|
default: '8083',
|
||||||
textField: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: ' --lang',
|
flag: 'lang',
|
||||||
id: 'language',
|
label: 'Язык интерфейса панели',
|
||||||
param: '--lang',
|
description:
|
||||||
desc: 'ISO 639-1 codes',
|
'Выберите язык веб-интерфейса. Если не выбирать, система будет использовать английский язык по умолчанию.',
|
||||||
selected: true,
|
type: 'select',
|
||||||
default: 'en',
|
default: 'en',
|
||||||
selectField: true,
|
options: [
|
||||||
text: 'en',
|
{ label: 'Shqip (Albanian)', value: 'sq' },
|
||||||
|
{ label: 'العربية (Arabic)', value: 'ar' },
|
||||||
|
{ label: 'Հայերեն (Armenian)', value: 'hy' },
|
||||||
|
{ label: 'Azərbaycan (Azerbaijani)', value: 'az' },
|
||||||
|
{ label: 'বাংলা (Bengali)', value: 'bn' },
|
||||||
|
{ label: 'Bosanski (Bosnian)', value: 'bs' },
|
||||||
|
{ label: 'Български (Bulgarian)', value: 'bg' },
|
||||||
|
{ label: 'Català (Catalan)', value: 'ca' },
|
||||||
|
{ label: 'Hrvatski (Croatian)', value: 'hr' },
|
||||||
|
{ label: 'Čeština (Czech)', value: 'cs' },
|
||||||
|
{ label: 'Dansk (Danish)', value: 'da' },
|
||||||
|
{ label: 'Nederlands (Dutch)', value: 'nl' },
|
||||||
|
{ label: 'English', value: 'en' },
|
||||||
|
{ label: 'Suomi (Finnish)', value: 'fi' },
|
||||||
|
{ label: 'Français (French)', value: 'fr' },
|
||||||
|
{ label: 'ქართული (Georgian)', value: 'ka' },
|
||||||
|
{ label: 'Deutsch (German)', value: 'de' },
|
||||||
|
{ label: 'Ελληνικά (Greek)', value: 'el' },
|
||||||
|
{ label: 'Magyar (Hungarian)', value: 'hu' },
|
||||||
|
{ label: 'Bahasa Indonesia (Indonesian)', value: 'id' },
|
||||||
|
{ label: 'Italiano (Italian)', value: 'it' },
|
||||||
|
{ label: '日本語 (Japanese)', value: 'ja' },
|
||||||
|
{ label: '한국어 (Korean)', value: 'ko' },
|
||||||
|
{ label: 'Kurdî (Sorani Kurdish)', value: 'ku' },
|
||||||
|
{ label: 'Norsk (Norwegian)', value: 'no' },
|
||||||
|
{ label: 'فارسی (Persian)', value: 'fa' },
|
||||||
|
{ label: 'Polski (Polish)', value: 'pl' },
|
||||||
|
{ label: 'Português (Portuguese)', value: 'pt' },
|
||||||
|
{ label: 'Português do Brasil (Brazilian Portuguese)', value: 'pt-br' },
|
||||||
|
{ label: 'Română (Romanian)', value: 'ro' },
|
||||||
|
{ label: 'Русский (Russian)', value: 'ru' },
|
||||||
|
{ label: 'Српски (Serbian)', value: 'sr' },
|
||||||
|
{ label: '简体中文 (Chinese Simplified)', value: 'zh-cn' },
|
||||||
|
{ label: 'Slovenčina (Slovak)', value: 'sk' },
|
||||||
|
{ label: 'Español (Spanish)', value: 'es' },
|
||||||
|
{ label: 'Svenska (Swedish)', value: 'sv' },
|
||||||
|
{ label: 'ไทย (Thai)', value: 'th' },
|
||||||
|
{ label: '繁體中文 (Chinese Traditional)', value: 'zh-tw' },
|
||||||
|
{ label: 'Türkçe (Turkish)', value: 'tr' },
|
||||||
|
{ label: 'Українська (Ukrainian)', value: 'uk' },
|
||||||
|
{ label: 'اردو (Urdu)', value: 'ur' },
|
||||||
|
{ label: 'Tiếng Việt (Vietnamese)', value: 'vi' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: ' --hostname',
|
flag: 'hostname',
|
||||||
id: 'hostname',
|
label: 'Имя хоста сервера',
|
||||||
param: '--hostname',
|
description:
|
||||||
desc: 'Установить имя хоста',
|
'Укажите веб-имя хоста сервера. Следуйте формату: demo.example.com. Обязательный параметр!',
|
||||||
selected: false,
|
type: 'text',
|
||||||
text: '',
|
default: '',
|
||||||
textField: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: ' --email',
|
flag: 'email',
|
||||||
id: 'email',
|
label: 'Электронная почта',
|
||||||
param: '--email',
|
description:
|
||||||
desc: 'Установить почту адмнистратора',
|
'Введите email администратора для уведомлений об异常 конфигурации сервера. Обязательный параметр!',
|
||||||
selected: false,
|
type: 'text',
|
||||||
text: '',
|
default: '',
|
||||||
textField: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: ' --password',
|
flag: 'password',
|
||||||
id: 'password',
|
label: 'Пароль',
|
||||||
param: '--password',
|
description:
|
||||||
desc: 'Установить пароль администратора',
|
'Установите пароль для учетной записи администратора. Если не указать, будет сгенерирован случайный пароль.',
|
||||||
selected: false,
|
type: 'text',
|
||||||
text: '',
|
default: '',
|
||||||
textField: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: ' --apache',
|
flag: 'nopublicip',
|
||||||
id: 'apache',
|
label: 'Принудительно использовать локальный IP',
|
||||||
param: '--apache',
|
description:
|
||||||
desc: 'Веб сервер с поддержкой .htaccess',
|
'Использовать внутренний/локальный IP-адрес сервера в качестве адреса привязки, не пытаться получить публичный IP. Подходит для внутренних сетей, NAT-сетей, локальной разработки и тестирования, где не требуется публичный доступ.',
|
||||||
selected: true,
|
default: 'no',
|
||||||
},
|
|
||||||
{ name: ' --phpfpm', id: 'phpfpm', param: '--phpfpm', desc: 'Установить PHP-FPM.', selected: true },
|
|
||||||
{
|
|
||||||
name: ' --multiphp',
|
|
||||||
id: 'multiphp',
|
|
||||||
param: '--multiphp',
|
|
||||||
desc: 'Разрешить использовать несколько PHP версий.',
|
|
||||||
selected: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: ' --vsftpd',
|
flag: 'apache',
|
||||||
id: 'vsftpd',
|
label: 'Apache2',
|
||||||
param: '--vsftpd',
|
description:
|
||||||
desc: 'Легковесный, минималистичный и безопасный FTP сервер.',
|
'Apache2 — мощный, высококонфигурируемый и широко поддерживаемый веб-сервер с открытым исходным кодом. Используется для хостинга веб-сайтов, поддержки веб-приложений, балансировки нагрузки и кэширования.',
|
||||||
selected: true,
|
default: 'yes',
|
||||||
conflicts: 'proftpd',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: ' --proftpd',
|
flag: 'phpfpm',
|
||||||
id: 'proftpd',
|
label: 'PHP-FPM',
|
||||||
param: '--proftpd',
|
description: 'PHP-FPM — это менеджер процессов FastCGI для выполнения PHP-скриптов.',
|
||||||
desc: 'Продвинутый, модульный FTP сервер с поддержкой LDAP.',
|
default: 'yes',
|
||||||
selected: false,
|
|
||||||
conflicts: 'vsftpd',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: ' --named',
|
flag: 'multiphp',
|
||||||
id: 'named',
|
label: 'MultiPHP',
|
||||||
param: '--named',
|
description:
|
||||||
desc: 'Собственный DNS сервер.',
|
'При включении по умолчанию устанавливается PHP 8.2, а также появляется возможность установки нескольких версий PHP 7.4-8.5 в панели. При отключении устанавливается только стандартная версия PHP.',
|
||||||
selected: true,
|
default: 'yes',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: ' --mysql',
|
flag: 'vsftpd',
|
||||||
id: 'mariadb',
|
label: 'VSFTPD',
|
||||||
param: '--mysql',
|
description:
|
||||||
desc: 'Ответление MySQL с дополнительными усовершенствованиями.',
|
'VSFTPD — это безопасный, быстрый и стабильный FTP-сервер. Особенно подходит для Linux-систем. Обеспечивает безопасную и быструю передачу файлов между сервером и пользователями.',
|
||||||
selected: true,
|
default: 'yes',
|
||||||
conflicts: 'mysql8',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: ' --mysql-classic',
|
flag: 'proftpd',
|
||||||
id: 'mysql8',
|
label: 'ProFTPD',
|
||||||
param: '--mysql-classic',
|
description:
|
||||||
desc: 'База данных с открытым исходным кодом',
|
'ProFTPD — это продвинутый модульный FTP-сервер. Поддерживает аутентификацию и авторизацию пользователей через LDAP.',
|
||||||
selected: false,
|
default: 'no',
|
||||||
conflicts: 'mariadb',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: ' --postgresql',
|
flag: 'named',
|
||||||
id: 'postgresql',
|
label: 'BIND',
|
||||||
param: '--postgresql',
|
description:
|
||||||
desc: 'База данных с открытым исходным кодом',
|
'BIND (Berkeley Internet Name Domain) — популярное DNS-программное обеспечение. Обязательный параметр для серверных кластеров, требующих настройки собственного DNS.',
|
||||||
selected: false,
|
default: 'yes',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: ' --exim',
|
flag: 'mysql',
|
||||||
id: 'exim',
|
label: 'MariaDB',
|
||||||
param: '--exim',
|
description:
|
||||||
desc: 'Позволить отправлять почту по SMTP.',
|
'MariaDB — это ветка MySQL, предоставляющая функции, совместимые с MySQL, а также некоторые дополнительные возможности и улучшения.',
|
||||||
selected: true,
|
default: 'yes',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: ' --dovecot',
|
flag: 'mysql-classic',
|
||||||
id: 'dovecot',
|
label: 'MySQL 8',
|
||||||
param: '--dovecot',
|
description:
|
||||||
desc: 'Принимать почту и отправлять по IMAP/POP3.',
|
'MySQL 8 предлагает простоту настройки, управления и масштабирования. Обеспечивает повышенную безопасность, высокую доступность в пределах одного региона или с резервированием и гарантирует 99.99% уровень обслуживания (SLA).',
|
||||||
selected: true,
|
default: 'no',
|
||||||
depends: 'exim',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: ' --sieve',
|
flag: 'postgresql',
|
||||||
id: 'sieve',
|
label: 'PostgreSQL',
|
||||||
param: '--sieve',
|
description:
|
||||||
desc: 'Управлять собственными фильтрами почты.',
|
'PostgreSQL — мощная объектно-реляционная система управления базами данных с открытым исходным кодом. Широко используется в финансовых услугах, обрабатывающей промышленности, розничной торговле, логистике и многих других областях.',
|
||||||
selected: false,
|
default: 'no',
|
||||||
depends: 'dovecot',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: ' --clamav',
|
flag: 'exim',
|
||||||
id: 'clamav',
|
label: 'Exim',
|
||||||
param: '--clamav',
|
description:
|
||||||
desc: 'Сканировать почту на вирусы.',
|
'Exim — это агент передачи сообщений (MTA), используемый для маршрутизации, доставки и приема сообщений электронной почты. Позволяет отправлять электронные письма через SMTP из сети или от локальных программ.',
|
||||||
selected: true,
|
default: 'yes',
|
||||||
depends: 'exim',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: ' --usemirrorclamav',
|
|
||||||
id: 'usemirrorclamav',
|
|
||||||
param: '--usemirrorclamav',
|
|
||||||
desc: 'Использовать русское зеркало баз данных ClamaV',
|
|
||||||
selected: false,
|
|
||||||
depends: 'clamav',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: ' --spamassassin',
|
flag: 'dovecot',
|
||||||
id: 'spamassassin',
|
label: 'Dovecot',
|
||||||
param: '--spamassassin',
|
description:
|
||||||
desc: 'Фильтровать почту на спам.',
|
'Dovecot — это серверное программное обеспечение с открытым исходным кодом для IMAP и POP3. Обеспечивает почтовые услуги для Linux-систем. Известен своей безопасностью, простотой использования, скоростью и низким потреблением ресурсов.',
|
||||||
selected: true,
|
default: 'yes',
|
||||||
depends: 'exim',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: ' --firewall',
|
flag: 'sieve',
|
||||||
id: 'firewall',
|
label: 'Правила Sieve',
|
||||||
param: '--firewall',
|
description:
|
||||||
desc: 'Управлять фаерволом.',
|
'Включает поддержку правил фильтрации почты (Sieve), позволяя пользователям настраивать автоматическую сортировку, обработку спама, автоматические ответы и другие правила. Требует Dovecot и Exim.',
|
||||||
selected: true,
|
default: 'no',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: ' --nopublicip',
|
flag: 'clamav',
|
||||||
id: 'nopublicip',
|
label: 'ClamAV',
|
||||||
param: '--nopublicip',
|
description:
|
||||||
desc: 'Использовать локальный IP сервера',
|
'ClamAV — это кроссплатформенное программное обеспечение с открытым исходным кодом для обеспечения безопасности электронной почты. Используется для обнаружения вирусов в электронных письмах и других файлах.',
|
||||||
selected: false,
|
default: 'yes',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: ' --uselocalphp',
|
flag: 'usemirrorclamav',
|
||||||
id: 'uselocalphp',
|
label: 'Российское зеркало ClamAV',
|
||||||
param: '--uselocalphp',
|
description:
|
||||||
desc: 'Использовать PHP из текущего репозитория HestiaCP',
|
'Использовать российское зеркало для обновления вирусных баз ClamAV. Подходит для серверов, расположенных в России или соседних регионах, где загрузка с официального источника медленная или нестабильная. Базы будут загружаться с repo.brepo.ru, возможна задержка в несколько часов.',
|
||||||
selected: false,
|
default: 'no',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: ' --usemirrorclamav',
|
flag: 'spamassassin',
|
||||||
id: 'usemirrorclamav',
|
label: 'SpamAssassin',
|
||||||
param: '--usemirrorclamav',
|
description:
|
||||||
desc: 'Использовать clamav с базами скачанными с зеркала в России',
|
'SpamAssassin — это инструмент с открытым исходным кодом для идентификации и фильтрации спама. Он анализирует заголовки, тело и информацию об отправителе письма, применяя ряд правил для оценки того, является ли письмо спамом.',
|
||||||
selected: false,
|
default: 'yes',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: ' --fail2ban',
|
flag: 'firewall',
|
||||||
id: 'fail2ban',
|
label: 'Системный брандмауэр',
|
||||||
param: '--fail2ban',
|
description:
|
||||||
desc: 'Предотвращение Bruteforce атак на SSH, Email, FTP, БД.',
|
'Использовать firewalld / nftables в качестве системного брандмауэра.<br>\n<br>⚠️ Внимание: Если после установки вы не можете получить доступ к панели через публичный IP (например, доступ только через localhost), это обычно означает, что порт 8083 не открыт в брандмауэре.\n\nРешение:\n\n<pre><code>firewall-cmd --permanent --add-port=8083/tcp\nfirewall-cmd --reload</code></pre>\n\nПри использовании nftables:\n\n<pre><code>nft add rule inet filter input tcp dport 8083 accept</code></pre>\n\nРекомендуется оставить брандмауэр включенным для повышения безопасности, возможна совместная работа с Fail2Ban для защиты от брутфорса.',
|
||||||
selected: true,
|
default: 'yes',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: ' --quota',
|
flag: 'bunkerweb',
|
||||||
id: 'quota',
|
label: 'Межсетевой экран WAF Bunkerweb',
|
||||||
param: '--quota',
|
description:
|
||||||
desc: 'Использовать квоты на пользовательские аккаунты.',
|
'Bunkerweb — это межсетевой экран веб-приложений (WAF) с открытым исходным кодом, обеспечивающий защиту на основе основных правил OWASP, черные/белые списки IP, ограничение скорости, HTTP-заголовки безопасности и другие функции безопасности.',
|
||||||
selected: false,
|
default: 'no',
|
||||||
},
|
},
|
||||||
{ name: ' --api', id: 'api', param: '--api', desc: 'Activate API.', selected: true },
|
|
||||||
{
|
{
|
||||||
name: ' --interactive',
|
flag: 'uselocalphp',
|
||||||
id: 'interactive',
|
label: 'Использовать официальную сборку PHP от Hestia',
|
||||||
param: '--interactive',
|
description:
|
||||||
desc: 'Интерактивная установка.',
|
'Использовать версию PHP из официального репозитория HestiaCP вместо стандартного источника операционной системы. При включении пакеты PHP будут загружаться из репозитория brepo, что обеспечивает единообразие версий и совместимость.',
|
||||||
selected: true,
|
default: 'no',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: 'fail2ban',
|
||||||
|
label: 'Fail2Ban',
|
||||||
|
description:
|
||||||
|
'Fail2Ban может автоматически обнаруживать и блокировать пользователей, использующих недействительные данные аутентификации, например, при попытках взлома SSH, FTP и т.д.',
|
||||||
|
default: 'yes',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: 'quota',
|
||||||
|
label: 'Дисковые квоты',
|
||||||
|
description:
|
||||||
|
'Установить квоты на дисковое пространство для системных пользователей, ограничивая использование дискового пространства.',
|
||||||
|
default: 'no',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: 'api',
|
||||||
|
label: 'Hestia API',
|
||||||
|
description:
|
||||||
|
'Включить внутренний API Hestia, позволяющий другим системам или сервисам взаимодействовать с панелью.',
|
||||||
|
default: 'yes',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: 'interactive',
|
||||||
|
label: 'Интерактивная установка',
|
||||||
|
description:
|
||||||
|
'Включить интерактивный режим во время установки, чтобы пользователь мог отвечать на запросы и выбирать опции.',
|
||||||
|
default: 'yes',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: 'force',
|
||||||
|
label: 'Принудительная установка',
|
||||||
|
description:
|
||||||
|
'Принудительно перезаписать любые существующие конфигурации и файлы во время установки. В дистрибутивах семейства RHEL рекомендуется включить этот параметр. Перезаписывает конфигурацию администратора по умолчанию.',
|
||||||
|
default: 'no',
|
||||||
},
|
},
|
||||||
{ name: ' --force', id: 'force', param: '--force', desc: 'Принудительная установка.', selected: false },
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -3,46 +3,33 @@ export const projectManagers = [
|
|||||||
{
|
{
|
||||||
avatar: 'https://www.github.com/ScIT-Raphael.png',
|
avatar: 'https://www.github.com/ScIT-Raphael.png',
|
||||||
name: 'Raphael Schneeberger 🇨🇭',
|
name: 'Raphael Schneeberger 🇨🇭',
|
||||||
title: 'Project Founder',
|
title: 'Основатель проекта',
|
||||||
links: [{ icon: 'github', link: 'https://github.com/ScIT-Raphael' }],
|
links: [{ icon: 'github', link: 'https://github.com/ScIT-Raphael' }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
avatar: 'https://www.github.com/jaapmarcus.png',
|
avatar: 'https://www.github.com/jaapmarcus.png',
|
||||||
name: 'Jaap Marcus 🇳🇱',
|
name: 'Jaap Marcus 🇳🇱',
|
||||||
links: [
|
links: [{ icon: 'github', link: 'https://github.com/jaapmarcus' }],
|
||||||
{ icon: 'github', link: 'https://github.com/jaapmarcus' },
|
|
||||||
{ icon: 'twitter', link: 'https://twitter.com/jaapmarcus' },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
avatar: 'https://www.github.com/Lupul.png',
|
avatar: 'https://www.github.com/Lupul.png',
|
||||||
name: 'Robert Zollner 🇷🇴',
|
name: 'Robert Zollner 🇷🇴',
|
||||||
links: [{ icon: 'github', link: 'https://github.com/Lupul' }],
|
links: [{ icon: 'github', link: 'https://github.com/Lupul' }],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
avatar: 'https://www.github.com/krismkenn.png',
|
|
||||||
name: 'Kristan Kenney 🇨🇦',
|
|
||||||
links: [{ icon: 'github', link: 'https://github.com/krismkenn' }],
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const globeIcon = {
|
||||||
|
svg: '🌐',
|
||||||
|
};
|
||||||
|
|
||||||
/** @type {import("vitepress").DefaultTheme.TeamMember[]} */
|
/** @type {import("vitepress").DefaultTheme.TeamMember[]} */
|
||||||
export const teamMembers = [
|
export const teamMembers = [
|
||||||
{
|
{
|
||||||
avatar: 'https://www.github.com/jakobbouchard.png',
|
avatar: 'https://www.github.com/imjuniper.png',
|
||||||
name: 'Jakob Bouchard 🇨🇦',
|
name: 'Juniper Bouchard 🇨🇦',
|
||||||
title: 'Developer',
|
|
||||||
org: 'Prosomo',
|
|
||||||
orgLink: 'https://prosomo.com',
|
|
||||||
links: [
|
links: [
|
||||||
{ icon: 'github', link: 'https://github.com/jakobbouchard' },
|
{ icon: 'github', link: 'https://github.com/imjuniper' },
|
||||||
{ icon: 'linkedin', link: 'https://linkedin.com/in/jakobbouchard' },
|
{ icon: globeIcon, link: 'https://imjuniper.fyi' },
|
||||||
{
|
|
||||||
icon: {
|
|
||||||
svg: '<svg role="img" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><title>Website</title><path stroke-linecap="round" stroke-linejoin="round" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" /></svg>',
|
|
||||||
},
|
|
||||||
link: 'https://jakobbouchard.dev',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -56,11 +43,6 @@ export const teamMembers = [
|
|||||||
name: 'divinity76 🇳🇴',
|
name: 'divinity76 🇳🇴',
|
||||||
links: [{ icon: 'github', link: 'https://github.com/divinity76' }],
|
links: [{ icon: 'github', link: 'https://github.com/divinity76' }],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
avatar: 'https://www.github.com/istiak101.png',
|
|
||||||
name: 'istiak101 🇧🇩',
|
|
||||||
links: [{ icon: 'github', link: 'https://github.com/istiak101' }],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
avatar: 'https://www.github.com/Pleskan.png',
|
avatar: 'https://www.github.com/Pleskan.png',
|
||||||
name: 'Anton Pleskanovskyy 🇺🇦',
|
name: 'Anton Pleskanovskyy 🇺🇦',
|
||||||
@@ -69,6 +51,33 @@ export const teamMembers = [
|
|||||||
{
|
{
|
||||||
avatar: 'https://www.github.com/AlecRust.png',
|
avatar: 'https://www.github.com/AlecRust.png',
|
||||||
name: 'Alec Rust 🇬🇧',
|
name: 'Alec Rust 🇬🇧',
|
||||||
links: [{ icon: 'github', link: 'https://github.com/AlecRust' }],
|
links: [
|
||||||
|
{ icon: 'github', link: 'https://github.com/AlecRust' },
|
||||||
|
{ icon: globeIcon, link: 'https://www.alecrust.com/' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
avatar: 'https://github.com/ioannidesalex.png',
|
||||||
|
name: 'Alexandros Ioannides 🇨🇾',
|
||||||
|
title: 'Технический директор (CTO)',
|
||||||
|
org: 'FocusNet',
|
||||||
|
orgLink: 'https://focus-net.net',
|
||||||
|
links: [
|
||||||
|
{ icon: 'github', link: 'https://github.com/ioannidesalex' },
|
||||||
|
{ icon: 'linkedin', link: 'https://www.linkedin.com/in/ioannidesa' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
/** @type {import("vitepress").DefaultTheme.TeamMember[]} */
|
||||||
|
export const teamRpm = [
|
||||||
|
{
|
||||||
|
avatar: 'https://www.github.com/bayrepo.png',
|
||||||
|
name: 'Alexey Berezhok 🇷🇺',
|
||||||
|
title: 'Сопровождающий RPM-версии',
|
||||||
|
desc: 'Отвечает за адаптацию и поддержку экосистем RHEL / Rocky / AlmaLinux.',
|
||||||
|
links: [
|
||||||
|
{ icon: 'github', link: 'https://github.com/bayrepo' },
|
||||||
|
{ icon: globeIcon, link: 'https://hestiadocs.brepo.ru' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ Hestia необходимо установить поверх новой уст
|
|||||||
Если на VPS/KVM уже есть учетная запись администратора, либо удалите этот идентификатор администратора по умолчанию, либо используйте `--force`, чтобы продолжить установку. Дополнительные сведения см. в разделе «Выборочная установка» ниже.
|
Если на VPS/KVM уже есть учетная запись администратора, либо удалите этот идентификатор администратора по умолчанию, либо используйте `--force`, чтобы продолжить установку. Дополнительные сведения см. в разделе «Выборочная установка» ниже.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
| | Минимум | Рекомендуется |
|
| Компонент | Минимум | Рекомендуется |
|
||||||
| -------------------- | --------------------------------------------- | ------------------------------------ |
|
| :--------------------------------------- | :------------------------------------------------------- | :----------------------------------------------------- |
|
||||||
| **ЦП** | 1 ядро, 64-разрядный | 4 ядра |
|
| **ЦП** | 1 ядро, 64-разрядный | 4 ядра |
|
||||||
| **Память** | 1 ГБ (без SpamAssassin и ClamAV) | 4 ГБ |
|
| **Память** | 1 ГБ (без SpamAssassin и ClamAV) | 4 ГБ |
|
||||||
| **Диск** | 10 ГБ HDD | 40 ГБ SSD |
|
| **Диск** | 10 ГБ HDD | 40 ГБ SSD |
|
||||||
| **Операционная система** | Debian 10, 11 или 12<br>Ubuntu 20.04, 22.04 LTS | Последняя версия Debian <br> Последняя версия Ubuntu LTS |
|
| **Операционная система (RPM)** | AlmaLinux 9<br>Rocky Linux 9 | Rocky Linux 9<br>AlmaLinux 9 |
|
||||||
|
|
||||||
::: warning
|
::: warning
|
||||||
Hestia работает только на процессорах AMD64 / x86_64 и ARM64 / aarch64. Также требуется 64-разрядная операционная система!
|
Hestia работает только на процессорах AMD64 / x86_64 и ARM64 / aarch64. Также требуется 64-разрядная операционная система!
|
||||||
@@ -28,7 +28,6 @@ Hestia работает только на процессорах AMD64 / x86_64
|
|||||||
|
|
||||||
### Поддерживаемые операционные системы
|
### Поддерживаемые операционные системы
|
||||||
|
|
||||||
- MSVSphere 9
|
|
||||||
- AlmaLinux 9
|
- AlmaLinux 9
|
||||||
- Rocky Linux 9
|
- Rocky Linux 9
|
||||||
|
|
||||||
@@ -79,37 +78,36 @@ bash hst-install.sh -h
|
|||||||
Чтобы выбрать, какое программное обеспечение будет установлено, вы можете указать флаги в скрипте установки. Полный список параметров можно просмотреть ниже.
|
Чтобы выбрать, какое программное обеспечение будет установлено, вы можете указать флаги в скрипте установки. Полный список параметров можно просмотреть ниже.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
-a, --apache Install Apache [yes|no] default: yes
|
-a, --apache Install Apache [yes | no] default: yes
|
||||||
-w, --phpfpm Install PHP-FPM [yes|no] default: yes
|
-w, --phpfpm Install PHP-FPM [yes | no] default: yes
|
||||||
-o, --multiphp Install Multi-PHP [yes|no] default: no
|
-o, --multiphp Install Multi-PHP [yes | no] default: no
|
||||||
-v, --vsftpd Install Vsftpd [yes|no] default: yes
|
-v, --vsftpd Install Vsftpd [yes | no] default: yes
|
||||||
-j, --proftpd Install ProFTPD [yes|no] default: no
|
-j, --proftpd Install ProFTPD [yes | no] default: no
|
||||||
-k, --named Install Bind [yes|no] default: yes
|
-k, --named Install Bind [yes | no] default: yes
|
||||||
-m, --mysql Install MariaDB [yes|no] default: yes
|
-m, --mysql Install MariaDB [yes | no] default: yes
|
||||||
-M, --mysql-classic Install MySQL 8 [yes|no] default: no
|
-M, --mysql-classic Install MySQL 8 [yes | no] default: no
|
||||||
-g, --postgresql Install PostgreSQL [yes|no] default: no
|
-g, --postgresql Install PostgreSQL [yes | no] default: no
|
||||||
-x, --exim Install Exim [yes|no] default: yes
|
-x, --exim Install Exim [yes | no] default: yes
|
||||||
-z, --dovecot Install Dovecot [yes|no] default: yes
|
-z, --dovecot Install Dovecot [yes | no] default: yes
|
||||||
-Z, --sieve Install Sieve [yes|no] default: no
|
-Z, --sieve Install Sieve [yes | no] default: no
|
||||||
-c, --clamav Install ClamAV [yes|no] default: no
|
-c, --clamav Install ClamAV [yes | no] default: no
|
||||||
-t, --spamassassin Install SpamAssassin [yes|no] default: yes
|
-t, --spamassassin Install SpamAssassin [yes | no] default: yes
|
||||||
-i, --firewall Install firewalld [yes|no] default: yes
|
-i, --firewall Install firewalld [yes | no] default: yes
|
||||||
-b, --fail2ban Install Fail2ban [yes|no] default: yes
|
-b, --fail2ban Install Fail2ban [yes | no] default: yes
|
||||||
-q, --quota Filesystem Quota [yes|no] default: no
|
-q, --quota Filesystem Quota [yes | no] default: no
|
||||||
-d, --api Activate API [yes|no] default: yes
|
-d, --api Activate API [yes | no] default: yes
|
||||||
-r, --port Change Backend Port default: 8083
|
-r, --port Change Backend Port default: 8083
|
||||||
-l, --lang Default language default: en
|
-l, --lang Default language default: en
|
||||||
-y, --interactive Interactive install [yes|no] default: yes
|
-y, --interactive Interactive install [yes | no] default: yes
|
||||||
-I, --nopublicip Use local ip [yes|no] default: no
|
-I, --nopublicip Use local ip [yes | no] default: no
|
||||||
-u, --uselocalphp Use PHP from local repo [yes|no] default: no
|
-u, --uselocalphp Use PHP from local repo [yes | no] default: no
|
||||||
-C, --usemirrorclamav Use mirrored clamav [yes|no] default: no
|
-C, --usemirrorclamav Use mirrored clamav [yes | no] default: no
|
||||||
-s, --hostname Set hostname
|
-s, --hostname Set hostname
|
||||||
-e, --email Set admin email
|
-e, --email Set admin email
|
||||||
-p, --password Set admin password
|
-p, --password Set admin password
|
||||||
-R, --with-rpms Path to Hestia rpms
|
-R, --with-rpms Path to Hestia rpms
|
||||||
-f, --force Force installation
|
-f, --force Force installation
|
||||||
-h, --help Print this help
|
-h, --help Print this help
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Пример
|
#### Пример
|
||||||
@@ -149,4 +147,4 @@ bash hst-install.sh \
|
|||||||
|
|
||||||
- `-I, --nopublicip` - если установлена данная опция, то установщик панели не получает внешний IP адрес сервера, где контрольная панель устанавливается (на случай если ваш сервер за NAT). Данную опцию можно включать, если у вас на сервере публичный IP или необходима внутрисетевая установка без внешнего доступа.
|
- `-I, --nopublicip` - если установлена данная опция, то установщик панели не получает внешний IP адрес сервера, где контрольная панель устанавливается (на случай если ваш сервер за NAT). Данную опцию можно включать, если у вас на сервере публичный IP или необходима внутрисетевая установка без внешнего доступа.
|
||||||
|
|
||||||
- `-u, --uselocalphp` - включить установку пакетов PHP из того-же репозитория, откуда ставится HestiaCP RPM Edition. Если не включать данную опцию, то PHP пакеты будут установлены из репозитория Remi. Если включить, то будет произведена так называемая установка локальных пакетов. Есть отличия в способе упаковке Local PHP и Remi PHP. Если сервера требуется расширенный набор PHP из Remi репозитория, то данную опцию лучше отключить, т.е задать `--uselocalphp no`. Local PHP более простая сборка с меньшим числом поддерживаемых модулей PHP, но подходит для большинства конфигураций CMS и имеет [страницу настройки подключаемых модулей](/docs/extensions/local-php.md)
|
- `-u, --uselocalphp` - включить установку пакетов PHP из того-же репозитория, откуда ставится HestiaCP RPM Edition. Если не включать данную опцию, то PHP пакеты будут установлены из репозитория Remi. Если включить, то будет произведена так называемая установка локальных пакетов. Есть отличия в способе упаковке Local PHP и Remi PHP. Если сервера требуется расширенный набор PHP из Remi репозитория, то данную опцию лучше отключить, т.е задать `--uselocalphp no`. Local PHP более простая сборка с меньшим числом поддерживаемых модулей PHP, но подходит для большинства конфигураций CMS и имеет [страницу настройки подключаемых модулей](/docs/extensions/local-php.md)
|
||||||
|
|||||||
@@ -24,13 +24,6 @@ yum update
|
|||||||
sed -i "s/obscure yescrypt/obscure sha512/g" /etc/pam.d/common-password
|
sed -i "s/obscure yescrypt/obscure sha512/g" /etc/pam.d/common-password
|
||||||
```
|
```
|
||||||
|
|
||||||
### Конфигурация Exim4
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rm -f /etc/exim4/exim4.conf.template
|
|
||||||
cp -f /usr/local/hestia/install/deb/exim/exim4.conf.4.94.template /etc/exim4/exim4.conf.template
|
|
||||||
```
|
|
||||||
|
|
||||||
### ProFTPD
|
### ProFTPD
|
||||||
|
|
||||||
Закомментируйте [строку 29](https://github.com/hestiacp/hestiacp/blob/1ff8a4e5207aae1e241954a83b7e8070bcdca788/install/deb/proftpd/proftpd.conf#L29) в `/etc/profpd/prodtpd.conf`.
|
Закомментируйте [строку 29](https://github.com/hestiacp/hestiacp/blob/1ff8a4e5207aae1e241954a83b7e8070bcdca788/install/deb/proftpd/proftpd.conf#L29) в `/etc/profpd/prodtpd.conf`.
|
||||||
|
|||||||
@@ -112,11 +112,11 @@ fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
|||||||
try_files $uri =404;
|
try_files $uri =404;
|
||||||
fastcgi_pass %backend_lsnr%;
|
fastcgi_pass %backend_lsnr%;
|
||||||
fastcgi_index index.php;
|
fastcgi_index index.php;
|
||||||
include /etc/nginx/fastcgi_params;
|
include /usr/local/hestia/nginx-system/usr/local/hestia/nginx-system/usr/local/hestia/nginx-system/usr/local/hestia/nginx-system/usr/local/hestia/nginx-system/usr/local/hestia/nginx-system/usr/local/hestia/nginx-system/usr/local/hestia/nginx-system/usr/local/hestia/nginx-system/usr/local/hestia/nginx-system/etc/nginx/fastcgi_params;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Добавьте следующие строки под `include /etc/nginx/fastcgi_params;`:
|
Добавьте следующие строки под `include /usr/local/hestia/nginx-system/usr/local/hestia/nginx-system/etc/nginx/fastcgi_params;`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
include %home%/%user%/conf/web/%domain%/nginx.fastcgi_cache.conf*;
|
include %home%/%user%/conf/web/%domain%/nginx.fastcgi_cache.conf*;
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
---
|
|
||||||
aside: false
|
|
||||||
lastUpdated: false
|
|
||||||
---
|
|
||||||
|
|
||||||
# Donate to the project
|
|
||||||
|
|
||||||
By donating to Hestia, you help maintain server costs, development time, and more!
|
|
||||||
|
|
||||||
Here are our official donation platforms:
|
|
||||||
|
|
||||||
- [PayPal](https://www.paypal.com/donate/?cmd=_s-xclick&hosted_button_id=ST87LQH2CHGLA)
|
|
||||||
- Crypto
|
|
||||||
- Bitcoin: bc1q48jt5wg5jaj8g9zy7c3j03cv57j2m2u5anlutu
|
|
||||||
- Ethereum: 0xfF3Dd2c889bd0Ff73d8085B84A314FC7c88e5D51
|
|
||||||
- Binance: bnb1l4ywvw5ejfmsgjdcx8jn5lxj7zsun8ktfu7rh8
|
|
||||||
- Smart Chain: 0xfF3Dd2c889bd0Ff73d8085B84A314FC7c88e5D51
|
|
||||||
@@ -15,22 +15,27 @@ hero:
|
|||||||
- theme: brand
|
- theme: brand
|
||||||
text: Начало работы
|
text: Начало работы
|
||||||
link: /docs/introduction/getting-started
|
link: /docs/introduction/getting-started
|
||||||
- theme: alt
|
- theme: brand
|
||||||
text: RPM версия
|
text: RPM версия
|
||||||
link: https://dev.brepo.ru/bayrepo/hestiacp
|
link: https://dev.brepo.ru/bayrepo/hestiacp
|
||||||
- theme: alt
|
- theme: brand
|
||||||
text: Оригинальная панель для Debian/Ubuntu
|
text: Оригинал для Debian/Ubuntu
|
||||||
link: https://github.com/hestiacp/hestiacp
|
link: https://github.com/hestiacp/hestiacp
|
||||||
|
|
||||||
features:
|
features:
|
||||||
- icon: 🚀
|
- icon: 🚀
|
||||||
title: Быстро и легко обучиться
|
title: Минималистичный опыт эксплуатации и обслуживания
|
||||||
details: Hestia создана с учетом простоты использования. Создать пользователя или веб-сайт так же просто, как нажать кнопку и заполнить пару полей, при этом расширенные функции легко доступны.
|
details: Hestia разработана с учётом простоты использования. Создание пользователя или сайта так же просто, как нажать на кнопку и заполнить несколько полей. Расширенные функции также настраиваются очень легко.
|
||||||
link: /docs/introduction/getting-started
|
link: /docs/introduction/getting-started
|
||||||
linkText: Документация
|
linkText: Документация
|
||||||
|
- icon: ❤️
|
||||||
|
title: Сделано с душой и гордостью
|
||||||
|
details: Hestia — это开源 серверное программное обеспечение, созданное увлечёнными разработчиками со всего мира. Это означает, что код проверяется многими людьми, и каждый может внести свой вклад в его развитие!
|
||||||
|
link: /team
|
||||||
|
linkText: Команда
|
||||||
- icon: 🛠️
|
- icon: 🛠️
|
||||||
title: Мощная и расширяемая
|
title: Расширяемая и мощная
|
||||||
details: Hestia расширяема и проста в освоении. Вы можете создавать свои собственные темы, веб-шаблоны, быстро устанавливать приложения и взаимодействовать с ней с помощью программного обеспечения сторонних производителей через ее API.
|
details: Hestia расширяема и проста в освоении. Вы можете создавать свои собственные темы, веб-шаблоны, быстро устанавливать приложения и взаимодействовать с ней с помощью программного обеспечения сторонних производителей через её API.
|
||||||
link: /docs/contributing/development
|
link: /docs/contributing/development
|
||||||
linkText: Помощь в разработке Hestia
|
linkText: Помочь разработке Hestia
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,23 +1,54 @@
|
|||||||
---
|
---
|
||||||
layout: page
|
layout: page
|
||||||
title: Install
|
title: Установка панели
|
||||||
---
|
---
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import InstallPageTitle from "./.vitepress/theme/components/InstallPageTitle.vue";
|
import PageHeader from './.vitepress/theme/components/PageHeader.vue';
|
||||||
import InstallOptions from "./.vitepress/theme/components/InstallOptions.vue";
|
import InstallOptions from './.vitepress/theme/components/InstallOptions.vue';
|
||||||
import InstallOptionsSection from "./.vitepress/theme/components/InstallOptionsSection.vue";
|
import InstallPage from './.vitepress/theme/components/InstallPage.vue';
|
||||||
import { options } from "./_data/options";
|
import { options } from './_data/options';
|
||||||
import { languages } from "./_data/languages";
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<InstallPage>
|
<InstallPage>
|
||||||
<InstallPageTitle>
|
<PageHeader>
|
||||||
<template #title>Генератор установочной команды</template>
|
<template #title>Генератор конфигурации для быстрой установки панели управления сервером Hestia</template>
|
||||||
</InstallPageTitle>
|
</PageHeader>
|
||||||
<InstallOptionsSection>
|
<PageHeader>
|
||||||
<template #list>
|
<template #aside><a class="header-button" href="./docs/introduction/getting-started#требования">Посмотреть требования к установке</a></template>
|
||||||
<InstallOptions :items="options" :languages="languages"></InstallOptions>
|
</PageHeader>
|
||||||
</template>
|
<InstallOptions :options="options"></InstallOptions>
|
||||||
</InstallOptionsSection>
|
|
||||||
</InstallPage>
|
</InstallPage>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.header-button {
|
||||||
|
display: inline-block;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: color 0.25s, border-color 0.25s, background-color 0.25s;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 0 20px;
|
||||||
|
line-height: 38px;
|
||||||
|
font-size: 14px;
|
||||||
|
border-color: var(--vp-button-alt-border);
|
||||||
|
color: var(--vp-button-alt-text);
|
||||||
|
background-color: var(--vp-button-alt-bg);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--vp-button-alt-hover-border);
|
||||||
|
color: var(--vp-button-alt-hover-text);
|
||||||
|
background-color: var(--vp-button-alt-hover-bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.header-button {
|
||||||
|
color: white;
|
||||||
|
background-color: var(--vp-c-brand);
|
||||||
|
}
|
||||||
|
.header-button:hover {
|
||||||
|
color: white;
|
||||||
|
background-color: #d8036a;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
6
docs/public/paypal.svg
Normal file
6
docs/public/paypal.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 12 KiB |
29
docs/team.md
29
docs/team.md
@@ -1,37 +1,38 @@
|
|||||||
---
|
---
|
||||||
layout: page
|
layout: page
|
||||||
|
title: Команда
|
||||||
title: The Team
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.VPTeamPageSection {
|
||||||
|
margin-top: 50px !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { VPTeamPage, VPTeamPageTitle, VPTeamPageSection, VPTeamMembers } from "vitepress/theme";
|
import { VPTeamPage, VPTeamPageTitle, VPTeamPageSection, VPTeamMembers } from "vitepress/theme";
|
||||||
import { projectManagers, teamMembers } from "./_data/team";
|
import { projectManagers, teamMembers, teamRpm } from "./_data/team";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<VPTeamPage>
|
|
||||||
<VPTeamPageTitle>
|
<VPTeamPageTitle>
|
||||||
<template #title>The Team</template>
|
<template #title>Команда</template>
|
||||||
<template #lead>
|
<template #lead>
|
||||||
The development of Hestia is guided by an international team, some of whom have chosen to be featured below.
|
Разработка Hestia осуществляется международной командой, некоторые из участников которой представлены ниже.
|
||||||
</template>
|
</template>
|
||||||
</VPTeamPageTitle>
|
</VPTeamPageTitle>
|
||||||
<VPTeamPageSection>
|
<VPTeamPageSection>
|
||||||
<template #title>Project Managers</template>
|
<template #title>Руководители проекта</template>
|
||||||
<template #members>
|
<template #members>
|
||||||
<VPTeamMembers :members="projectManagers" />
|
<VPTeamMembers :members="projectManagers" />
|
||||||
</template>
|
</template>
|
||||||
</VPTeamPageSection>
|
</VPTeamPageSection>
|
||||||
<VPTeamPageSection>
|
<VPTeamPageSection>
|
||||||
<template #title>Team Members</template>
|
<template #title>Участники команды</template>
|
||||||
<template #members>
|
<template #members>
|
||||||
<VPTeamMembers :members="teamMembers" />
|
<VPTeamMembers :members="teamMembers" />
|
||||||
</template>
|
</template>
|
||||||
</VPTeamPageSection>
|
</VPTeamPageSection>
|
||||||
<!-- <VPTeamPageSection>
|
<VPTeamPageSection>
|
||||||
<template #title>Contributors ❤️</template>
|
<template #title>Сопровождающий RPM-версии</template>
|
||||||
<template #members>
|
<template #members>
|
||||||
<VPTeamMembers size="small" :members="featuredContributors" />
|
<VPTeamMembers :members="teamRpm" />
|
||||||
</template>
|
</template>
|
||||||
</VPTeamPageSection> -->
|
</VPTeamPageSection>
|
||||||
</VPTeamPage>
|
|
||||||
|
|||||||
115
func/domain.sh
115
func/domain.sh
@@ -383,7 +383,11 @@ add_web_config() {
|
|||||||
|
|
||||||
if [[ "$TPLNM" =~ stpl$ ]]; then
|
if [[ "$TPLNM" =~ stpl$ ]]; then
|
||||||
rm -f /etc/$1/$confd/domains/$domain.ssl.conf
|
rm -f /etc/$1/$confd/domains/$domain.ssl.conf
|
||||||
ln -s $conf /etc/$1/$confd/domains/$domain.ssl.conf
|
if [ "$1" = "nginx" ]; then
|
||||||
|
ln -s $conf /usr/local/hestia/nginx-system/etc/$1/$confd/domains/$domain.ssl.conf
|
||||||
|
else
|
||||||
|
ln -s $conf /etc/$1/$confd/domains/$domain.ssl.conf
|
||||||
|
fi
|
||||||
|
|
||||||
# Rename/Move extra SSL config files
|
# Rename/Move extra SSL config files
|
||||||
find=$(find $HOMEDIR/$user/conf/web/*.$domain.org* 2> /dev/null)
|
find=$(find $HOMEDIR/$user/conf/web/*.$domain.org* 2> /dev/null)
|
||||||
@@ -399,8 +403,13 @@ add_web_config() {
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
else
|
else
|
||||||
rm -f /etc/$1/$confd/domains/$domain.conf
|
if [ "$1" = "nginx" ]; then
|
||||||
ln -s $conf /etc/$1/$confd/domains/$domain.conf
|
rm -f /usr/local/hestia/nginx-system/etc/$1/$confd/domains/$domain.conf
|
||||||
|
ln -s $conf /usr/local/hestia/nginx-system/etc/$1/$confd/domains/$domain.conf
|
||||||
|
else
|
||||||
|
rm -f /etc/$1/$confd/domains/$domain.conf
|
||||||
|
ln -s $conf /etc/$1/$confd/domains/$domain.conf
|
||||||
|
fi
|
||||||
# Rename/Move extra config files
|
# Rename/Move extra config files
|
||||||
find=$(find $HOMEDIR/$user/conf/web/*.$domain.org* 2> /dev/null)
|
find=$(find $HOMEDIR/$user/conf/web/*.$domain.org* 2> /dev/null)
|
||||||
for f in $find; do
|
for f in $find; do
|
||||||
@@ -518,17 +527,29 @@ del_web_config() {
|
|||||||
rm -f $legacyconf
|
rm -f $legacyconf
|
||||||
|
|
||||||
# Remove old global includes file
|
# Remove old global includes file
|
||||||
rm -f /etc/$1/$confd/hestia.conf
|
if [ "$1" = "nginx" ]; then
|
||||||
|
rm -f /usr/local/hestia/nginx-system/etc/$1/$confd/hestia.conf
|
||||||
|
else
|
||||||
|
rm -f /etc/$1/$confd/hestia.conf
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Remove domain configuration files and clean up symbolic links
|
# Remove domain configuration files and clean up symbolic links
|
||||||
rm -f "$conf"
|
rm -f "$conf"
|
||||||
|
|
||||||
if [ -n "$WEB_SYSTEM" ] && [ "$WEB_SYSTEM" = "$1" ]; then
|
if [ -n "$WEB_SYSTEM" ] && [ "$WEB_SYSTEM" = "$1" ]; then
|
||||||
rm -f "/etc/$WEB_SYSTEM/$confd/domains/$confname"
|
if [ "$WEB_SYSTEM" = "nginx" ]; then
|
||||||
|
rm -f "/usr/local/hestia/nginx-system/etc/$WEB_SYSTEM/$confd/domains/$confname"
|
||||||
|
else
|
||||||
|
rm -f "/etc/$WEB_SYSTEM/$confd/domains/$confname"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
if [ -n "$PROXY_SYSTEM" ] && [ "$PROXY_SYSTEM" = "$1" ]; then
|
if [ -n "$PROXY_SYSTEM" ] && [ "$PROXY_SYSTEM" = "$1" ]; then
|
||||||
rm -f "/etc/$PROXY_SYSTEM/$confd/domains/$confname"
|
if [ "$PROXY_SYSTEM" = "nginx" ]; then
|
||||||
|
rm -f "/usr/local/hestia/nginx-system/etc/$PROXY_SYSTEM/$confd/domains/$confname"
|
||||||
|
else
|
||||||
|
rm -f "/etc/$PROXY_SYSTEM/$confd/domains/$confname"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -868,7 +889,7 @@ add_mail_ssl_config() {
|
|||||||
echo " ssl_key = <$HOMEDIR/$user/conf/mail/$domain/ssl/$domain.key" >> /etc/dovecot/conf.d/domains/$domain.conf
|
echo " ssl_key = <$HOMEDIR/$user/conf/mail/$domain/ssl/$domain.key" >> /etc/dovecot/conf.d/domains/$domain.conf
|
||||||
echo "}" >> /etc/dovecot/conf.d/domains/$domain.conf
|
echo "}" >> /etc/dovecot/conf.d/domains/$domain.conf
|
||||||
|
|
||||||
# Add domain SSL configuration to exim4
|
# Add domain SSL configuration to exim
|
||||||
ln -s $HOMEDIR/$user/conf/mail/$domain/ssl/$domain.pem $HESTIA/ssl/mail/$domain.crt
|
ln -s $HOMEDIR/$user/conf/mail/$domain/ssl/$domain.pem $HESTIA/ssl/mail/$domain.crt
|
||||||
ln -s $HOMEDIR/$user/conf/mail/$domain/ssl/$domain.key $HESTIA/ssl/mail/$domain.key
|
ln -s $HOMEDIR/$user/conf/mail/$domain/ssl/$domain.key $HESTIA/ssl/mail/$domain.key
|
||||||
fi
|
fi
|
||||||
@@ -880,7 +901,7 @@ add_mail_ssl_config() {
|
|||||||
echo " ssl_key = <$HOMEDIR/$user/conf/mail/$domain/ssl/$domain.key" >> /etc/dovecot/conf.d/domains/$domain.conf
|
echo " ssl_key = <$HOMEDIR/$user/conf/mail/$domain/ssl/$domain.key" >> /etc/dovecot/conf.d/domains/$domain.conf
|
||||||
echo "}" >> /etc/dovecot/conf.d/domains/$domain.conf
|
echo "}" >> /etc/dovecot/conf.d/domains/$domain.conf
|
||||||
|
|
||||||
# Add domain SSL configuration to exim4
|
# Add domain SSL configuration to exim
|
||||||
ln -s $HOMEDIR/$user/conf/mail/$domain/ssl/$domain.pem $HESTIA/ssl/mail/mail.$domain.crt
|
ln -s $HOMEDIR/$user/conf/mail/$domain/ssl/$domain.pem $HESTIA/ssl/mail/mail.$domain.crt
|
||||||
ln -s $HOMEDIR/$user/conf/mail/$domain/ssl/$domain.key $HESTIA/ssl/mail/mail.$domain.key
|
ln -s $HOMEDIR/$user/conf/mail/$domain/ssl/$domain.key $HESTIA/ssl/mail/mail.$domain.key
|
||||||
|
|
||||||
@@ -917,8 +938,16 @@ del_mail_ssl_config() {
|
|||||||
|
|
||||||
# Remove SSL vhost configuration
|
# Remove SSL vhost configuration
|
||||||
rm -f $HOMEDIR/$user/conf/mail/$domain/*.*ssl.conf
|
rm -f $HOMEDIR/$user/conf/mail/$domain/*.*ssl.conf
|
||||||
rm -f /etc/$WEB_SYSTEM/$confd/domains/$WEBMAIL_ALIAS.$domain.ssl.conf
|
if [ "$WEB_SYSTEM" = "nginx" ]; then
|
||||||
rm -f /etc/$PROXY_SYSTEM/$pconfd/domains/$WEBMAIL_ALIAS.$domain.ssl.conf
|
rm -f /usr/local/hestia/nginx-system/etc/$WEB_SYSTEM/$confd/domains/$WEBMAIL_ALIAS.$domain.ssl.conf
|
||||||
|
else
|
||||||
|
rm -f /etc/$WEB_SYSTEM/$confd/domains/$WEBMAIL_ALIAS.$domain.ssl.conf
|
||||||
|
fi
|
||||||
|
if [ "$PROXY_SYSTEM" = "nginx" ]; then
|
||||||
|
rm -f /usr/local/hestia/nginx-system/etc/$PROXY_SYSTEM/$pconfd/domains/$WEBMAIL_ALIAS.$domain.ssl.conf
|
||||||
|
else
|
||||||
|
rm -f /etc/$PROXY_SYSTEM/$pconfd/domains/$WEBMAIL_ALIAS.$domain.ssl.conf
|
||||||
|
fi
|
||||||
|
|
||||||
# Remove SSL certificates
|
# Remove SSL certificates
|
||||||
rm -f $HOMEDIR/$user/conf/mail/$domain/ssl/*
|
rm -f $HOMEDIR/$user/conf/mail/$domain/ssl/*
|
||||||
@@ -1006,13 +1035,23 @@ add_webmail_config() {
|
|||||||
if [[ "$2" =~ stpl$ ]]; then
|
if [[ "$2" =~ stpl$ ]]; then
|
||||||
if [ -n "$WEB_SYSTEM" ]; then
|
if [ -n "$WEB_SYSTEM" ]; then
|
||||||
forcessl="$HOMEDIR/$user/conf/mail/$domain/$WEB_SYSTEM.forcessl.conf"
|
forcessl="$HOMEDIR/$user/conf/mail/$domain/$WEB_SYSTEM.forcessl.conf"
|
||||||
rm -f /etc/$1/$confd/domains/$WEBMAIL_ALIAS.$domain.ssl.conf
|
if [ "$1" = "nginx" ]; then
|
||||||
ln -s $conf /etc/$1/$confd/domains/$WEBMAIL_ALIAS.$domain.ssl.conf
|
rm -f /usr/local/hestia/nginx-system/etc/$1/$confd/domains/$WEBMAIL_ALIAS.$domain.ssl.conf
|
||||||
|
ln -s $conf /usr/local/hestia/nginx-system/etc/$1/$confd/domains/$WEBMAIL_ALIAS.$domain.ssl.conf
|
||||||
|
else
|
||||||
|
rm -f /etc/$1/$confd/domains/$WEBMAIL_ALIAS.$domain.ssl.conf
|
||||||
|
ln -s $conf /etc/$1/$confd/domains/$WEBMAIL_ALIAS.$domain.ssl.conf
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
if [ -n "$PROXY_SYSTEM" ]; then
|
if [ -n "$PROXY_SYSTEM" ]; then
|
||||||
forcessl="$HOMEDIR/$user/conf/mail/$domain/$PROXY_SYSTEM.forcessl.conf"
|
forcessl="$HOMEDIR/$user/conf/mail/$domain/$PROXY_SYSTEM.forcessl.conf"
|
||||||
rm -f /etc/$1/$confd/domains/$WEBMAIL_ALIAS.$domain.ssl.conf
|
if [ "$1" = "nginx" ]; then
|
||||||
ln -s $conf /etc/$1/$confd/domains/$WEBMAIL_ALIAS.$domain.ssl.conf
|
rm -f /usr/local/hestia/nginx-system/etc/$1/$confd/domains/$WEBMAIL_ALIAS.$domain.ssl.conf
|
||||||
|
ln -s $conf /usr/local/hestia/nginx-system/etc/$1/$confd/domains/$WEBMAIL_ALIAS.$domain.ssl.conf
|
||||||
|
else
|
||||||
|
rm -f /etc/$1/$confd/domains/$WEBMAIL_ALIAS.$domain.ssl.conf
|
||||||
|
ln -s $conf /etc/$1/$confd/domains/$WEBMAIL_ALIAS.$domain.ssl.conf
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Add rewrite rules to force HTTPS/SSL connections
|
# Add rewrite rules to force HTTPS/SSL connections
|
||||||
@@ -1027,12 +1066,22 @@ add_webmail_config() {
|
|||||||
find $HOMEDIR/$user/conf/mail/ -maxdepth 1 -type f \( -name "$domain.*" -o -name "ssl.$domain.*" -o -name "*nginx.$domain.*" \) -exec rm {} \;
|
find $HOMEDIR/$user/conf/mail/ -maxdepth 1 -type f \( -name "$domain.*" -o -name "ssl.$domain.*" -o -name "*nginx.$domain.*" \) -exec rm {} \;
|
||||||
else
|
else
|
||||||
if [ -n "$WEB_SYSTEM" ]; then
|
if [ -n "$WEB_SYSTEM" ]; then
|
||||||
rm -f /etc/$1/$confd/domains/$WEBMAIL_ALIAS.$domain.conf
|
if [ "$1" = "nginx" ]; then
|
||||||
ln -s $conf /etc/$1/$confd/domains/$WEBMAIL_ALIAS.$domain.conf
|
rm -f /usr/local/hestia/nginx-system/etc/$1/$confd/domains/$WEBMAIL_ALIAS.$domain.conf
|
||||||
|
ln -s $conf /usr/local/hestia/nginx-system/etc/$1/$confd/domains/$WEBMAIL_ALIAS.$domain.conf
|
||||||
|
else
|
||||||
|
rm -f /etc/$1/$confd/domains/$WEBMAIL_ALIAS.$domain.conf
|
||||||
|
ln -s $conf /etc/$1/$confd/domains/$WEBMAIL_ALIAS.$domain.conf
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
if [ -n "$PROXY_SYSTEM" ]; then
|
if [ -n "$PROXY_SYSTEM" ]; then
|
||||||
rm -f /etc/$1/$confd/domains/$WEBMAIL_ALIAS.$domain.conf
|
if [ "$1" = "nginx" ]; then
|
||||||
ln -s $conf /etc/$1/$confd/domains/$WEBMAIL_ALIAS.$domain.conf
|
rm -f /usr/local/hestia/nginx-system/etc/$1/$confd/domains/$WEBMAIL_ALIAS.$domain.conf
|
||||||
|
ln -s $conf /usr/local/hestia/nginx-system/etc/$1/$confd/domains/$WEBMAIL_ALIAS.$domain.conf
|
||||||
|
else
|
||||||
|
rm -f /etc/$1/$confd/domains/$WEBMAIL_ALIAS.$domain.conf
|
||||||
|
ln -s $conf /etc/$1/$confd/domains/$WEBMAIL_ALIAS.$domain.conf
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
# Clear old configurations
|
# Clear old configurations
|
||||||
find $HOMEDIR/$user/conf/mail/ -maxdepth 1 -type f \( -name "$domain.*" \) -exec rm {} \;
|
find $HOMEDIR/$user/conf/mail/ -maxdepth 1 -type f \( -name "$domain.*" \) -exec rm {} \;
|
||||||
@@ -1053,12 +1102,20 @@ del_webmail_config() {
|
|||||||
fi
|
fi
|
||||||
if [ -n "$WEB_SYSTEM" ]; then
|
if [ -n "$WEB_SYSTEM" ]; then
|
||||||
rm -f $HOMEDIR/$user/$confd/mail/$domain/$WEB_SYSTEM.conf
|
rm -f $HOMEDIR/$user/$confd/mail/$domain/$WEB_SYSTEM.conf
|
||||||
rm -f /etc/$WEB_SYSTEM/$confd/domains/$WEBMAIL_ALIAS.$domain.conf
|
if [ "$WEB_SYSTEM" = "nginx" ]; then
|
||||||
|
rm -f /usr/local/hestia/nginx-system/etc/$WEB_SYSTEM/$confd/domains/$WEBMAIL_ALIAS.$domain.conf
|
||||||
|
else
|
||||||
|
rm -f /etc/$WEB_SYSTEM/$confd/domains/$WEBMAIL_ALIAS.$domain.conf
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "$PROXY_SYSTEM" ]; then
|
if [ -n "$PROXY_SYSTEM" ]; then
|
||||||
rm -f $HOMEDIR/$user/conf/mail/$domain/$PROXY_SYSTEM.*conf
|
rm -f $HOMEDIR/$user/conf/mail/$domain/$PROXY_SYSTEM.*conf
|
||||||
rm -f /etc/$PROXY_SYSTEM/$pconfd/domains/$WEBMAIL_ALIAS.$domain.conf
|
if [ "$PROXY_SYSTEM" = "nginx" ]; then
|
||||||
|
rm -f /usr/local/hestia/nginx-system/etc/$PROXY_SYSTEM/$pconfd/domains/$WEBMAIL_ALIAS.$domain.conf
|
||||||
|
else
|
||||||
|
rm -f /etc/$PROXY_SYSTEM/$pconfd/domains/$WEBMAIL_ALIAS.$domain.conf
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1076,12 +1133,20 @@ del_webmail_ssl_config() {
|
|||||||
fi
|
fi
|
||||||
if [ -n "$WEB_SYSTEM" ]; then
|
if [ -n "$WEB_SYSTEM" ]; then
|
||||||
rm -f $HOMEDIR/$user/conf/mail/$domain/$WEB_SYSTEM.*ssl.conf
|
rm -f $HOMEDIR/$user/conf/mail/$domain/$WEB_SYSTEM.*ssl.conf
|
||||||
rm -f /etc/$WEB_SYSTEM/$confd/domains/$WEBMAIL_ALIAS.$domain.ssl.conf
|
if [ "$WEB_SYSTEM" = "nginx" ]; then
|
||||||
|
rm -f /usr/local/hestia/nginx-system/etc/$WEB_SYSTEM/$confd/domains/$WEBMAIL_ALIAS.$domain.ssl.conf
|
||||||
|
else
|
||||||
|
rm -f /etc/$WEB_SYSTEM/$confd/domains/$WEBMAIL_ALIAS.$domain.ssl.conf
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "$PROXY_SYSTEM" ]; then
|
if [ -n "$PROXY_SYSTEM" ]; then
|
||||||
rm -f $HOMEDIR/$user/conf/mail/$domain/$PROXY_SYSTEM.*ssl.conf
|
rm -f $HOMEDIR/$user/conf/mail/$domain/$PROXY_SYSTEM.*ssl.conf
|
||||||
rm -f /etc/$PROXY_SYSTEM/$pconfd/domains/$WEBMAIL_ALIAS.$domain.ssl.conf
|
if [ "$PROXY_SYSTEM" = "nginx" ]; then
|
||||||
|
rm -f /usr/local/hestia/nginx-system/etc/$PROXY_SYSTEM/$pconfd/domains/$WEBMAIL_ALIAS.$domain.ssl.conf
|
||||||
|
else
|
||||||
|
rm -f /etc/$PROXY_SYSTEM/$pconfd/domains/$WEBMAIL_ALIAS.$domain.ssl.conf
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1187,14 +1252,14 @@ is_base_domain_owner() {
|
|||||||
#----------------------------------------------------------#
|
#----------------------------------------------------------#
|
||||||
|
|
||||||
process_http2_directive() {
|
process_http2_directive() {
|
||||||
if [ -e /etc/nginx/conf.d/http2-directive.conf ]; then
|
if [ -e /usr/local/hestia/nginx-system/etc/nginx/conf.d/http2-directive.conf ]; then
|
||||||
while IFS= read -r old_param; do
|
while IFS= read -r old_param; do
|
||||||
new_param="$(echo "$old_param" | sed 's/\shttp2//')"
|
new_param="$(echo "$old_param" | sed 's/\shttp2//')"
|
||||||
sed -i "s/$old_param/$new_param/" "$1"
|
sed -i "s/$old_param/$new_param/" "$1"
|
||||||
done < <(grep -E "listen.*(\bssl\b(\s|.+){1,}\bhttp2\b|\bhttp2\b(\s|.+){1,}\bssl\b).*;" "$1")
|
done < <(grep -E "listen.*(\bssl\b(\s|.+){1,}\bhttp2\b|\bhttp2\b(\s|.+){1,}\bssl\b).*;" "$1")
|
||||||
else
|
else
|
||||||
if version_ge "$(nginx -v 2>&1 | cut -d'/' -f2)" "1.25.1"; then
|
if version_ge "$(/usr/local/hestia/nginx-system/sbin/nginx -v 2>&1 | cut -d'/' -f2)" "1.25.1"; then
|
||||||
echo "http2 on;" > /etc/nginx/conf.d/http2-directive.conf
|
echo "http2 on;" > /usr/local/hestia/nginx-system/etc/nginx/conf.d/http2-directive.conf
|
||||||
|
|
||||||
while IFS= read -r old_param; do
|
while IFS= read -r old_param; do
|
||||||
new_param="$(echo "$old_param" | sed 's/\shttp2//')"
|
new_param="$(echo "$old_param" | sed 's/\shttp2//')"
|
||||||
|
|||||||
@@ -736,7 +736,7 @@ rebuild_mail_domain_conf() {
|
|||||||
echo "$account@$domain_idn:$user_rate_limit" >> $HOMEDIR/$user/conf/mail/$domain/limits
|
echo "$account@$domain_idn:$user_rate_limit" >> $HOMEDIR/$user/conf/mail/$domain/limits
|
||||||
else
|
else
|
||||||
#revert to system value
|
#revert to system value
|
||||||
system=$(cat /etc/exim4/limit.conf)
|
system=$(cat /etc/exim/limit.conf)
|
||||||
sed -i "/^$account@$domain_idn:/ d" $HOMEDIR/$user/conf/mail/$domain/limits
|
sed -i "/^$account@$domain_idn:/ d" $HOMEDIR/$user/conf/mail/$domain/limits
|
||||||
echo "$account@$domain_idn:$system" >> $HOMEDIR/$user/conf/mail/$domain/limits
|
echo "$account@$domain_idn:$system" >> $HOMEDIR/$user/conf/mail/$domain/limits
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -536,7 +536,7 @@ upgrade_cloudflare_ip() {
|
|||||||
cf_ips="$(curl -fsLm5 --retry 2 https://api.cloudflare.com/client/v4/ips)"
|
cf_ips="$(curl -fsLm5 --retry 2 https://api.cloudflare.com/client/v4/ips)"
|
||||||
|
|
||||||
if [ -n "$cf_ips" ] && [ "$(echo "$cf_ips" | jq -r '.success//""')" = "true" ]; then
|
if [ -n "$cf_ips" ] && [ "$(echo "$cf_ips" | jq -r '.success//""')" = "true" ]; then
|
||||||
cf_inc="/etc/nginx/conf.d/cloudflare.inc"
|
cf_inc="/usr/local/hestia/nginx-system/etc/nginx/conf.d/cloudflare.inc"
|
||||||
|
|
||||||
echo "[ * ] Updating Cloudflare IP Ranges for NGINX..."
|
echo "[ * ] Updating Cloudflare IP Ranges for NGINX..."
|
||||||
echo "# Cloudflare IP Ranges" > $cf_inc
|
echo "# Cloudflare IP Ranges" > $cf_inc
|
||||||
|
|||||||
630
func_ruby/HestiaBunkerWebApi.rb
Normal file
630
func_ruby/HestiaBunkerWebApi.rb
Normal file
@@ -0,0 +1,630 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
require 'json'
|
||||||
|
require 'net/http'
|
||||||
|
require 'uri'
|
||||||
|
require 'openssl'
|
||||||
|
|
||||||
|
class BunkerWebApiError < StandardError; end
|
||||||
|
class HestiaBunkerWebApi
|
||||||
|
# Override puts to accumulate logs into @extra_info
|
||||||
|
def puts(*args)
|
||||||
|
@extra_info ||= ""
|
||||||
|
@extra_info << args.join("\n") << "\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Accessor for @extra_info
|
||||||
|
def extra_info
|
||||||
|
@extra_info || ""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Hook to wrap methods of HestiaBunkerWebApi to reset @extra_info at start
|
||||||
|
class Module
|
||||||
|
alias_method :orig_method_added, :method_added
|
||||||
|
def method_added(name)
|
||||||
|
orig_method_added(name)
|
||||||
|
# Skip wrapping for the overridden puts method
|
||||||
|
return if name == :puts
|
||||||
|
if self.name == 'HestiaBunkerWebApi'
|
||||||
|
@__wrapping ||= false
|
||||||
|
return if @__wrapping
|
||||||
|
@__wrapping = true
|
||||||
|
original = instance_method(name)
|
||||||
|
define_method(name) do |*args, &block|
|
||||||
|
@extra_info = ""
|
||||||
|
original.bind(self).call(*args, &block)
|
||||||
|
end
|
||||||
|
@__wrapping = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
class HestiaBunkerWebApi
|
||||||
|
# Retrieve API username and password from /etc/bunkerweb/api.env if available
|
||||||
|
def get_api_user_password
|
||||||
|
env_path = "/etc/bunkerweb/api.env"
|
||||||
|
return nil unless File.file?(env_path)
|
||||||
|
|
||||||
|
username = nil
|
||||||
|
password = nil
|
||||||
|
|
||||||
|
File.foreach(env_path) do |line|
|
||||||
|
line.strip!
|
||||||
|
next if line.empty? || line.start_with?('#')
|
||||||
|
key, value = line.split('=', 2)
|
||||||
|
next unless key && value
|
||||||
|
case key
|
||||||
|
when 'API_USERNAME'
|
||||||
|
username = value
|
||||||
|
when 'API_PASSWORD'
|
||||||
|
password = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if username && password
|
||||||
|
[username, password]
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(api_url, username = nil, password = nil)
|
||||||
|
@api_base = api_url
|
||||||
|
if username.nil?
|
||||||
|
result = get_api_user_password
|
||||||
|
if result.nil?
|
||||||
|
raise BunkerWebApiError.new("Authentication error: no username or password")
|
||||||
|
else
|
||||||
|
@username = result[0]
|
||||||
|
@password = result[1]
|
||||||
|
end
|
||||||
|
else
|
||||||
|
@username = username
|
||||||
|
@password = password
|
||||||
|
end
|
||||||
|
@token = nil
|
||||||
|
@extra_info = ""
|
||||||
|
|
||||||
|
# Authenticate and get token
|
||||||
|
authenticate!
|
||||||
|
|
||||||
|
puts "[INFO] Successfully authenticated with BunkerWeb API"
|
||||||
|
end
|
||||||
|
|
||||||
|
def authenticate!
|
||||||
|
uri = URI(@api_base)
|
||||||
|
http = Net::HTTP.new(uri.host, uri.port)
|
||||||
|
http.use_ssl = (uri.scheme == "https")
|
||||||
|
|
||||||
|
# Try both Basic Auth and JSON body with credentials
|
||||||
|
request = Net::HTTP::Post.new("/auth")
|
||||||
|
request.content_type = "application/json"
|
||||||
|
request.body = { username: @username, password: @password }.to_json
|
||||||
|
|
||||||
|
response = http.request(request)
|
||||||
|
|
||||||
|
if response.code != '200'
|
||||||
|
raise BunkerWebApiError.new("Authentication failed: #{response.code} - #{response.message}")
|
||||||
|
end
|
||||||
|
|
||||||
|
body = JSON.parse(response.body)
|
||||||
|
|
||||||
|
unless body['token']
|
||||||
|
raise BunkerWebApiError.new("Authentication succeeded but no token received")
|
||||||
|
end
|
||||||
|
|
||||||
|
@token = body['token']
|
||||||
|
rescue => e
|
||||||
|
raise BunkerWebApiError.new("Authentication error: #{e.message}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def api_call(method, path, headers = {}, body = nil)
|
||||||
|
uri = URI(@api_base + path)
|
||||||
|
http = Net::HTTP.new(uri.host, uri.port)
|
||||||
|
http.use_ssl = (uri.scheme == "https")
|
||||||
|
|
||||||
|
request = case method
|
||||||
|
when "GET"
|
||||||
|
Net::HTTP::Get.new(uri.path)
|
||||||
|
when "POST"
|
||||||
|
req = Net::HTTP::Post.new(uri.path)
|
||||||
|
req.body = body if body
|
||||||
|
req
|
||||||
|
when "PATCH"
|
||||||
|
req = Net::HTTP::Patch.new(uri.path)
|
||||||
|
req.body = body if body
|
||||||
|
req
|
||||||
|
when "DELETE"
|
||||||
|
Net::HTTP::Delete.new(uri.path)
|
||||||
|
else
|
||||||
|
raise BunkerWebApiError.new("Unsupported HTTP method: #{method}")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Add Authorization header with token for most operations (except /auth)
|
||||||
|
unless path == "/auth"
|
||||||
|
headers["Authorization"] = "Bearer #{@token}" if @token
|
||||||
|
end
|
||||||
|
|
||||||
|
# Add Content-Type if not already set and we have a body
|
||||||
|
if body && !headers.key?("Content-Type")
|
||||||
|
headers["Content-Type"] = "application/json"
|
||||||
|
end
|
||||||
|
|
||||||
|
headers.each { |k, v| request[k] = v }
|
||||||
|
|
||||||
|
response = http.request(request)
|
||||||
|
parsed_body = begin
|
||||||
|
JSON.parse(response.body)
|
||||||
|
rescue => e
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
{ status: response.code.to_i, body: parsed_body || {}, raw_body: response.body }
|
||||||
|
rescue => e
|
||||||
|
raise BunkerWebApiError.new("API call to #{path} failed: #{e.message}")
|
||||||
|
end
|
||||||
|
|
||||||
|
# === Service Operations ===
|
||||||
|
|
||||||
|
def create_service(service_name, options = {})
|
||||||
|
@extra_info = ""
|
||||||
|
# Create a new service with the given configuration.
|
||||||
|
#
|
||||||
|
# Args:
|
||||||
|
# service_name (String): The domain name for this service (server_name)
|
||||||
|
# options (Hash): Service configuration including:
|
||||||
|
# - USE_TEMPLATE (default: "high")
|
||||||
|
# - USE_SSL (default: "no") - if not "no", you need CERTIFICATE and KEY paths
|
||||||
|
# - REVERSE_PROXY_HOST (optional)
|
||||||
|
# - REVERSE_PROXY_URL (optional, default: "~ ^/(.*)$")
|
||||||
|
# - Additional options like:
|
||||||
|
# - USE_REVERSE_PROXY (default: "yes" if REVERSE_PROXY_HOST is set)
|
||||||
|
# - USE_REAL_IP, REAL_IP_FROM
|
||||||
|
# - USE_MODSECURITY, USE_ANTIBOT
|
||||||
|
# - LISTEN_HTTP_PORT, LISTEN_HTTPS_PORT
|
||||||
|
# - CERTIFICATE_FILE_PATH (if USE_SSL != "no")
|
||||||
|
# - KEY_FILE_PATH (if USE_SSL != "no")
|
||||||
|
# Returns: Hash with creation response
|
||||||
|
|
||||||
|
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_CUSTOM_SSL"] = ssl_enabled ? "yes" : "no"
|
||||||
|
|
||||||
|
if ssl_enabled
|
||||||
|
unless options[:certificate_path] && options[:key_path]
|
||||||
|
raise BunkerWebApiError.new("Certificate and Key paths are required when USE_SSL is enabled")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Set certificate paths in variables
|
||||||
|
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"
|
||||||
|
else
|
||||||
|
# No SSL - HTTP only
|
||||||
|
# API expects string "null", not nil/JSON null
|
||||||
|
variables["LISTEN_HTTPS_PORT"] = "null"
|
||||||
|
variables["LISTEN_HTTP_PORT"] = (options[:http_port] || "80").to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
# 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] || "~ ^(?!/challenge)(.*)$"
|
||||||
|
|
||||||
|
# Real IP settings for reverse proxy
|
||||||
|
unless options[:real_ip_from].nil?
|
||||||
|
variables["USE_REAL_IP"] = "yes"
|
||||||
|
variables["REAL_IP_FROM"] = options[:real_ip_from]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Additional security settings from High template
|
||||||
|
variables["USE_MODSECURITY"] = options[:use_modsecurity] || "yes"
|
||||||
|
variables["USE_ANTIBOT"] = options[:anti_bot] || "captcha"
|
||||||
|
end
|
||||||
|
|
||||||
|
variables["ANTIBOT_IGNORE_URI"] = options[:anti_bot_ignore_uri] || "^/\.well-known/acme-challenge/.+$"
|
||||||
|
variables["LETS_ENCRYPT_PASSTHROUGH"] = options[:lets_encrypt_passthrough] || "yes"
|
||||||
|
|
||||||
|
|
||||||
|
service_body = {
|
||||||
|
server_name: service_name,
|
||||||
|
is_draft: false,
|
||||||
|
variables: variables
|
||||||
|
}
|
||||||
|
|
||||||
|
response = api_call("POST", "/services", {}, JSON.generate(service_body))
|
||||||
|
|
||||||
|
# Accept both 201 (Created) and 200 (OK) for successful creation
|
||||||
|
if [201, 200].include?(response[:status])
|
||||||
|
puts "[INFO] Service '#{service_name}' created successfully"
|
||||||
|
elsif response[:status] == 409
|
||||||
|
raise BunkerWebApiError.new("Service '#{service_name}' already exists")
|
||||||
|
else
|
||||||
|
raise BunkerWebApiError.new("Failed to create service: status=#{response[:status]}, body=#{response[:raw_body]}")
|
||||||
|
end
|
||||||
|
|
||||||
|
return response[:body] || {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_service_ssl(service_name, certificate_path, key_path, https_port = nil)
|
||||||
|
# Update or change the SSL certificate path for an existing service.
|
||||||
|
#
|
||||||
|
# Args:
|
||||||
|
# service_name (String): Name of the service to update
|
||||||
|
# certificate_path (String): Path to the SSL certificate file
|
||||||
|
# key_path (String): Path to the SSL private key file
|
||||||
|
# https_port (Integer, optional): HTTPS port (default 443)
|
||||||
|
# Returns: Hash with update response
|
||||||
|
|
||||||
|
@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 variables from config - API stores them in 'config' not 'variables'
|
||||||
|
# Each variable is a hash with 'value' as the actual setting value
|
||||||
|
current_vars = {}
|
||||||
|
if get_service_response[:body]["config"]
|
||||||
|
get_service_response[:body]["config"].each do |key, value_hash|
|
||||||
|
if value_hash.is_a?(Hash) && value_hash.key?("value")
|
||||||
|
current_vars[key] = value_hash["value"]
|
||||||
|
else
|
||||||
|
current_vars[key] = value_hash
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Update SSL settings
|
||||||
|
updated_vars = {
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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 set_alias(service_name, list_aliases)
|
||||||
|
@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
|
||||||
|
|
||||||
|
# Save the entire current configuration (except server_name which will be replaced)
|
||||||
|
current_config = get_service_response[:body]
|
||||||
|
|
||||||
|
# Extract variables from config - API stores them in 'config' not 'variables'
|
||||||
|
# Each variable is a hash with 'value' as the actual setting value
|
||||||
|
current_vars = {}
|
||||||
|
if current_config["config"]
|
||||||
|
current_config["config"].each do |key, value_hash|
|
||||||
|
if key != "SERVER_NAME"
|
||||||
|
if value_hash.is_a?(Hash) && value_hash.key?("value")
|
||||||
|
current_vars[key] = value_hash["value"]
|
||||||
|
else
|
||||||
|
current_vars[key] = value_hash
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Clean the list_aliases string according to the rules
|
||||||
|
cleaned_aliases = list_aliases.to_s
|
||||||
|
|
||||||
|
# Replace commas (with or without space) with a single space
|
||||||
|
cleaned_aliases.gsub!(/,\s?/, ' ')
|
||||||
|
|
||||||
|
# Replace multiple spaces with a single space
|
||||||
|
cleaned_aliases.gsub!(/\s{2,}/, ' ')
|
||||||
|
|
||||||
|
# Strip leading/trailing whitespace
|
||||||
|
cleaned_aliases.strip!
|
||||||
|
|
||||||
|
# Ensure the main service name appears first in the alias list
|
||||||
|
aliases_array = cleaned_aliases.split(' ')
|
||||||
|
if aliases_array.include?(service_name)
|
||||||
|
aliases_array.delete(service_name)
|
||||||
|
end
|
||||||
|
# Rebuild cleaned string
|
||||||
|
cleaned_aliases = aliases_array.join(' ')
|
||||||
|
current_vars["SERVER_NAME"]=cleaned_aliases
|
||||||
|
|
||||||
|
# Step 1: Delete the old service
|
||||||
|
delete_response = api_call("DELETE", "/services/#{service_name}")
|
||||||
|
if delete_response[:status] != 200 && delete_response[:status] != 204
|
||||||
|
raise BunkerWebApiError.new("Failed to delete service '#{service_name}': status=#{delete_response[:status]}")
|
||||||
|
end
|
||||||
|
puts "[INFO] Service '#{service_name}' deleted"
|
||||||
|
|
||||||
|
# Step 2: Create a new service with the cleaned alias list as server_name
|
||||||
|
# and preserve all existing configuration variables
|
||||||
|
service_body = {
|
||||||
|
server_name: service_name, # Use the full alias list including service_name first
|
||||||
|
is_draft: current_config["is_draft"] || false,
|
||||||
|
variables: current_vars # Preserve all existing variables from the original service
|
||||||
|
}
|
||||||
|
|
||||||
|
post_response = api_call("POST", "/services", {}, JSON.generate(service_body))
|
||||||
|
|
||||||
|
if post_response[:status] == 200 || post_response[:status] == 201
|
||||||
|
puts "[INFO] Service recreated successfully with aliases: #{cleaned_aliases}"
|
||||||
|
elsif post_response[:status] == 409
|
||||||
|
raise BunkerWebApiError.new("Service '#{service_name}' already exists")
|
||||||
|
else
|
||||||
|
raise BunkerWebApiError.new("Failed to create service: status=#{post_response[:status]}, body=#{post_response[:raw_body]}")
|
||||||
|
end
|
||||||
|
|
||||||
|
return post_response || {}
|
||||||
|
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 variables from config - API stores them in 'config' not 'variables'
|
||||||
|
# Each variable is a hash with 'value' as the actual setting value
|
||||||
|
current_vars = {}
|
||||||
|
if get_service_response[:body]["config"]
|
||||||
|
get_service_response[:body]["config"].each do |key, value_hash|
|
||||||
|
if value_hash.is_a?(Hash) && value_hash.key?("value")
|
||||||
|
current_vars[key] = value_hash["value"]
|
||||||
|
else
|
||||||
|
current_vars[key] = value_hash
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# Args:
|
||||||
|
# service_name (String): Name of the service to delete
|
||||||
|
# Returns: Hash with deletion response
|
||||||
|
|
||||||
|
@extra_info = ""
|
||||||
|
# Verify service exists first
|
||||||
|
get_response = api_call("GET", "/services/#{service_name}", {})
|
||||||
|
|
||||||
|
if get_response[:status] != 200
|
||||||
|
raise BunkerWebApiError.new("Service '#{service_name}' not found")
|
||||||
|
end
|
||||||
|
|
||||||
|
response = api_call("DELETE", "/services/#{service_name}")
|
||||||
|
|
||||||
|
if response[:status] == 200 || response[:status] == 204
|
||||||
|
puts "[INFO] Service '#{service_name}' deleted successfully"
|
||||||
|
return response[:body] || {}
|
||||||
|
else
|
||||||
|
raise BunkerWebApiError.new("Failed to delete service: status=#{response[:status]}, body=#{response[:raw_body]}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# === Additional Utility Methods ===
|
||||||
|
|
||||||
|
def list_services(drafts = false)
|
||||||
|
# List all services.
|
||||||
|
#
|
||||||
|
# Args:
|
||||||
|
# drafts (Boolean): Include draft services (default: false, set to true to include drafts)
|
||||||
|
# Returns: Array of service objects
|
||||||
|
|
||||||
|
@extra_info = ""
|
||||||
|
response = api_call("GET", "/services")
|
||||||
|
|
||||||
|
if response[:status] != 200
|
||||||
|
raise BunkerWebApiError.new("Failed to list services: status=#{response[:status]}")
|
||||||
|
end
|
||||||
|
|
||||||
|
return response[:body] || []
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_service(service_name)
|
||||||
|
# Get details of a specific service.
|
||||||
|
#
|
||||||
|
# Args:
|
||||||
|
# service_name (String): Name of the service to retrieve
|
||||||
|
# Returns: Hash with service configuration
|
||||||
|
|
||||||
|
@extra_info = ""
|
||||||
|
response = api_call("GET", "/services/#{service_name}")
|
||||||
|
|
||||||
|
if response[:status] != 200
|
||||||
|
raise BunkerWebApiError.new("Service '#{service_name}' not found")
|
||||||
|
end
|
||||||
|
|
||||||
|
return response[:body] || {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def reload_instance(instance_hostname = nil)
|
||||||
|
# Reload configuration on an instance.
|
||||||
|
#
|
||||||
|
# Args:
|
||||||
|
# instance_hostname (String, optional): Instance hostname to reload (if nil, reloads all instances)
|
||||||
|
# Returns: Hash with reload response
|
||||||
|
|
||||||
|
@extra_info = ""
|
||||||
|
path = if instance_hostname.nil?
|
||||||
|
"/instances/reload"
|
||||||
|
else
|
||||||
|
"/instances/#{instance_hostname}/reload"
|
||||||
|
end
|
||||||
|
|
||||||
|
response = api_call("POST", "#{path}?test=no")
|
||||||
|
|
||||||
|
if response[:status] == 200 || response[:status] == 201
|
||||||
|
puts "[INFO] Configuration reloaded successfully"
|
||||||
|
return response[:body] || {}
|
||||||
|
else
|
||||||
|
raise BunkerWebApiError.new("Failed to reload configuration: status=#{response[:status]}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def list_instances()
|
||||||
|
# List all registered instances.
|
||||||
|
# Returns: Array of instance objects
|
||||||
|
|
||||||
|
@extra_info = ""
|
||||||
|
response = api_call("GET", "/instances")
|
||||||
|
|
||||||
|
if response[:status] != 200
|
||||||
|
raise BunkerWebApiError.new("Failed to list instances: status=#{response[:status]}")
|
||||||
|
end
|
||||||
|
|
||||||
|
return response[:body] || []
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_instance(hostname, name = nil, port = 8888, https_port = nil)
|
||||||
|
# Create/register a new BunkerWeb instance (worker node).
|
||||||
|
#
|
||||||
|
# Args:
|
||||||
|
# hostname (String): IP address or hostname of the worker node
|
||||||
|
# name (String, optional): Human-readable name for the instance
|
||||||
|
# port (Integer): API port on the worker node (default 8888)
|
||||||
|
# https_port (Integer, optional): HTTPS port
|
||||||
|
# Returns: Hash with creation response
|
||||||
|
|
||||||
|
@extra_info = ""
|
||||||
|
instance_body = {
|
||||||
|
hostname: hostname,
|
||||||
|
name: name || "BunkerWeb Instance",
|
||||||
|
port: port,
|
||||||
|
listen_https: !https_port.nil?,
|
||||||
|
https_port: https_port,
|
||||||
|
server_name: hostname,
|
||||||
|
method: "api" # Using API deployment method
|
||||||
|
}
|
||||||
|
|
||||||
|
response = api_call("POST", "/instances", {}, JSON.generate(instance_body))
|
||||||
|
|
||||||
|
if response[:status] == 201
|
||||||
|
puts "[INFO] Instance '#{hostname}' registered successfully"
|
||||||
|
elsif response[:status] == 409
|
||||||
|
# Instance already exists - that's OK, we just want to use it
|
||||||
|
puts "[INFO] Instance '#{hostname}' already exists, will be used for this service"
|
||||||
|
else
|
||||||
|
raise BunkerWebApiError.new("Failed to create instance: status=#{response[:status]}, body=#{response[:raw_body]}")
|
||||||
|
end
|
||||||
|
|
||||||
|
return response[:body] || {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_instance(hostname)
|
||||||
|
"""
|
||||||
|
Delete a registered instance.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hostname (String): Hostname of the instance to delete
|
||||||
|
"""
|
||||||
|
|
||||||
|
@extra_info = ""
|
||||||
|
response = api_call("DELETE", "/instances/#{hostname}")
|
||||||
|
|
||||||
|
if response[:status] == 200 || response[:status] == 204
|
||||||
|
puts "[INFO] Instance '#{hostname}' deleted successfully"
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
raise BunkerWebApiError.new("Failed to delete instance: status=#{response[:status]}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# === Example Usage (can be run as script) ===
|
||||||
|
|
||||||
|
if __FILE__ == $0
|
||||||
|
# Example usage demonstration
|
||||||
|
begin
|
||||||
|
api = HestiaBunkerWebApi.new(
|
||||||
|
"http://127.0.0.1:8888",
|
||||||
|
"admin",
|
||||||
|
"your_password"
|
||||||
|
)
|
||||||
|
|
||||||
|
puts ""
|
||||||
|
puts "[INFO] Creating service 'example.my.domain'"
|
||||||
|
result = api.create_service("example.my.domain", {
|
||||||
|
reverse_proxy_host: "http://192.168.3.51:8078",
|
||||||
|
ssl: "no",
|
||||||
|
use_template: "high"
|
||||||
|
})
|
||||||
|
|
||||||
|
puts ""
|
||||||
|
puts "[INFO] Listing services:"
|
||||||
|
services = api.list_services()
|
||||||
|
services.each { |s| puts "- #{s['server_name']}" }
|
||||||
|
|
||||||
|
rescue BunkerWebApiError => e
|
||||||
|
puts "[ERROR] #{e.message}"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
end
|
||||||
467
func_ruby/docs/BunkerWebApiDoc.md
Normal file
467
func_ruby/docs/BunkerWebApiDoc.md
Normal file
@@ -0,0 +1,467 @@
|
|||||||
|
# HestiaBunkerWebApi - Ruby класс для работы с BunkerWeb API
|
||||||
|
|
||||||
|
## Описание
|
||||||
|
|
||||||
|
Класс `HestiaBunkerWebApi` предоставляет простой интерфейс для управления сервисами BunkerWeb через REST API. Класс реализует:
|
||||||
|
|
||||||
|
- **Аутентификацию** с получением токена
|
||||||
|
- **Управление сервисами** (создание, обновление, удаление)
|
||||||
|
- **Управление SSL сертификатами**
|
||||||
|
- **Управление instances** (worker nodes)
|
||||||
|
- **Полное исключение ошибок** при любых проблемах
|
||||||
|
|
||||||
|
## Установка и импорт
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ruby 3.3+ рекомендуется
|
||||||
|
ruby --version
|
||||||
|
# ruby 3.3.x or later
|
||||||
|
|
||||||
|
# Класс использует стандартные библиотеки Ruby:
|
||||||
|
# - json (для JSON парсинга)
|
||||||
|
# - net/http (для HTTP запросов)
|
||||||
|
# - uri (для URL парсинга)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Использование класса
|
||||||
|
|
||||||
|
### Базовое использование
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require_relative "HestiaBunkerWebApi.rb"
|
||||||
|
|
||||||
|
# Создаём экземпляр API с аутентификацией
|
||||||
|
api = HestiaBunkerWebApi.new(
|
||||||
|
"http://127.0.0.1:8888", # URL API (можно https://)
|
||||||
|
"admin", # username
|
||||||
|
"password" # password
|
||||||
|
)
|
||||||
|
# или
|
||||||
|
api = HestiaBunkerWebApi.new(
|
||||||
|
"http://127.0.0.1:8888", # URL API (можно https://)
|
||||||
|
)
|
||||||
|
# в этом случае пароль и логин читаются автоматически из файла /etc/bunkerweb/api.env
|
||||||
|
|
||||||
|
|
||||||
|
# При создании экземпляра автоматически происходит аутентификация
|
||||||
|
# Если ошибка - выбрасывается BunkerWebApiError с описанием проблемы
|
||||||
|
```
|
||||||
|
|
||||||
|
### Обработка ошибок
|
||||||
|
|
||||||
|
Все ошибки наследуются от `StandardError` через класс `BunkerWebApiError`:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
begin
|
||||||
|
api.create_service("example.domain", options)
|
||||||
|
rescue BunkerWebApiError => e
|
||||||
|
puts "[ERROR] Ошибка API: #{e.message}"
|
||||||
|
|
||||||
|
# Примеры возможных ошибок:
|
||||||
|
# - "Authentication failed: 401 - Unauthorized"
|
||||||
|
# - "Service 'x' already exists"
|
||||||
|
# - "Certificate and Key paths are required when USE_SSL is enabled"
|
||||||
|
# - "Failed to create service: status=500, body={...}"
|
||||||
|
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Методы класса
|
||||||
|
|
||||||
|
### Конструктор
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
HestiaBunkerWebApi.new(api_url, username, password)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Параметры:**
|
||||||
|
- `api_url` - URL BunkerWeb API в формате `http://ip:port` или `https://ip:port`
|
||||||
|
- `username` - имя администратора для аутентификации
|
||||||
|
- `password` - пароль для аутентификации
|
||||||
|
|
||||||
|
**Действие:** При создании автоматически пытается аутентифицироваться через POST /auth и сохраняет токен.
|
||||||
|
|
||||||
|
### Создание сервиса
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
api.create_service(service_name, options = {})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Параметры:**
|
||||||
|
- `service_name` - имя домена/сервиса (например, "example.my.domain")
|
||||||
|
- `options` - хэш с конфигурацией:
|
||||||
|
|
||||||
|
| Параметр | Тип | Описание | Пример |
|
||||||
|
|----------|-----|----------|--------|
|
||||||
|
| `ssl` | String | "yes" для SSL, "no" для HTTP | `"no"` |
|
||||||
|
| `certificate_path` | String | Путь к SSL сертификату (если ssl="yes") | `"/etc/ssl/certs/example.crt"` |
|
||||||
|
| `key_path` | String | Путь к приватному ключу (если ssl="yes") | `"/etc/ssl/private/example.key"` |
|
||||||
|
| `use_template` | String | Безопасность шаблона | `"high"` (default) |
|
||||||
|
| `reverse_proxy_host` | String | Target reverse proxy | `"http://192.168.3.51:8078"` |
|
||||||
|
| `reverse_proxy_url` | String | URL трансформация | `"~ ^/(.*)$"` |
|
||||||
|
| `real_ip_from` | String | CIDR trusted network для RealIP | `"192.168.3.0/24"` |
|
||||||
|
| `use_modsecurity` | String | WAF включение | `"yes"` (default) |
|
||||||
|
| `anti_bot` | String | Bot protection | `"captcha"` (default) |
|
||||||
|
| `http_port` | Integer/nil | HTTP порт | `"80"` или `null` |
|
||||||
|
| `https_port` | Integer | HTTPS порт | `443` или `null` |
|
||||||
|
|
||||||
|
**Пример - создание reverse proxy сервиса без SSL:**
|
||||||
|
```ruby
|
||||||
|
result = api.create_service("example.my.domain", {
|
||||||
|
ssl: "no",
|
||||||
|
reverse_proxy_host: "http://192.168.3.51:8078"
|
||||||
|
})
|
||||||
|
|
||||||
|
# Генерирует variables:
|
||||||
|
# - USE_TEMPLATE: "high"
|
||||||
|
# - USE_SSL: "no"
|
||||||
|
# - LISTEN_HTTPS_PORT: "null"
|
||||||
|
# - LISTEN_HTTP_PORT: "80"
|
||||||
|
# - USE_REVERSE_PROXY: "yes"
|
||||||
|
# - REVERSE_PROXY_HOST: "http://192.168.3.51:8078"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Пример - создание сервиса с SSL + reverse proxy:**
|
||||||
|
```ruby
|
||||||
|
result = api.create_service("example.my.domain", {
|
||||||
|
ssl: "yes", # Включаем SSL
|
||||||
|
|
||||||
|
certificate_path: "/etc/ssl/certs/example.crt", # Путь к сертификату
|
||||||
|
key_path: "/etc/ssl/private/example.key", # Путь к ключу
|
||||||
|
|
||||||
|
reverse_proxy_host: "http://192.168.3.51:8078", # Reverse proxy target
|
||||||
|
|
||||||
|
use_template: "high",
|
||||||
|
anti_bot: "captcha"
|
||||||
|
})
|
||||||
|
|
||||||
|
# Генерирует variables:
|
||||||
|
# - USE_TEMPLATE: "high"
|
||||||
|
# - USE_SSL: "yes"
|
||||||
|
# - SSL_CERTIFICATE_FILE_PATH: "/etc/ssl/certs/example.crt"
|
||||||
|
# - SSL_KEY_FILE_PATH: "/etc/ssl/private/example.key"
|
||||||
|
# - LISTEN_HTTPS_PORT: "443"
|
||||||
|
# - LISTEN_HTTP_PORT: "80"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Обновление SSL сертификата для существующего сервиса
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
api.update_service_ssl(service_name, certificate_path, key_path, https_port = nil)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Параметры:**
|
||||||
|
- `service_name` - имя уже созданного сервиса
|
||||||
|
- `certificate_path` - новый путь к SSL сертификату
|
||||||
|
- `key_path` - новый путь к приватному ключу
|
||||||
|
- `https_port` (optional) - HTTPS порт (default: 443)
|
||||||
|
|
||||||
|
**Пример:**
|
||||||
|
```ruby
|
||||||
|
api.update_service_ssl(
|
||||||
|
"example.my.domain",
|
||||||
|
"/etc/ssl/certs/example.crt",
|
||||||
|
"/etc/ssl/private/example.key"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Обновляет существующий сервис, сохраняя reverse proxy настройки
|
||||||
|
```
|
||||||
|
|
||||||
|
### Удаление сервиса
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
api.delete_service(service_name)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Параметры:**
|
||||||
|
- `service_name` - имя сервиса для удаления
|
||||||
|
|
||||||
|
**Пример:**
|
||||||
|
```ruby
|
||||||
|
api.delete_service("example.my.domain")
|
||||||
|
# Удаляет сервис и конфигурацию
|
||||||
|
```
|
||||||
|
|
||||||
|
### Получение списка всех сервисов
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
api.list_services(drafts = false)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Параметры:**
|
||||||
|
- `drafts` (optional) - включать draft сервисы (default: false)
|
||||||
|
|
||||||
|
**Возвращает:** Array of service objects
|
||||||
|
|
||||||
|
**Пример:**
|
||||||
|
```ruby
|
||||||
|
services = api.list_services()
|
||||||
|
services.each { |s| puts "- #{s['server_name']}" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Получение деталей конкретного сервиса
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
api.get_service(service_name)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Параметры:**
|
||||||
|
- `service_name` - имя сервиса для получения деталей
|
||||||
|
|
||||||
|
**Возвращает:** Hash with service configuration (variables, settings, etc.)
|
||||||
|
|
||||||
|
**Пример:**
|
||||||
|
```ruby
|
||||||
|
config = api.get_service("example.my.domain")
|
||||||
|
puts config.inspect
|
||||||
|
```
|
||||||
|
|
||||||
|
### Перезагрузка конфигурации на instance
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
api.reload_instance(instance_hostname = nil)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Параметры:**
|
||||||
|
- `instance_hostname` (optional) - hostname instance для перезагрузки (если nil, reloads all instances)
|
||||||
|
|
||||||
|
**Пример:**
|
||||||
|
```ruby
|
||||||
|
# Reload все instances
|
||||||
|
api.reload_instance()
|
||||||
|
|
||||||
|
# Reload конкретный instance
|
||||||
|
api.reload_instance("192.168.3.50")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Получение списка всех instances
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
api.list_instances()
|
||||||
|
```
|
||||||
|
|
||||||
|
**Возвращает:** Array of instance objects (hostname, name, port, etc.)
|
||||||
|
|
||||||
|
### Создание/регистрация BunkerWeb instance (worker node)
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
api.create_instance(hostname, name = nil, port = 8888, https_port = nil)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Параметры:**
|
||||||
|
- `hostname` - IP address или hostname worker node
|
||||||
|
- `name` (optional) - Human-readable имя instance
|
||||||
|
- `port` - API port на worker node (default: 8888)
|
||||||
|
- `https_port` (optional) - HTTPS port если есть
|
||||||
|
|
||||||
|
**Пример:**
|
||||||
|
```ruby
|
||||||
|
api.create_instance(
|
||||||
|
"192.168.3.50", # IP worker node
|
||||||
|
"BunkerWeb Worker Node", # Optional name
|
||||||
|
8888 # API port
|
||||||
|
)
|
||||||
|
|
||||||
|
# Возвращает: { status: 201/409, body: {...} }
|
||||||
|
# Если статус 201 - instance создан
|
||||||
|
# Если статус 409 - instance уже существует (это OK)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Удаление BunkerWeb instance
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
api.delete_instance(hostname)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Параметры:**
|
||||||
|
- `hostname` - hostname instance для удаления
|
||||||
|
|
||||||
|
## Примеры полного использования
|
||||||
|
|
||||||
|
### Пример 1: Создание и управление сервисом
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require_relative "HestiaBunkerWebApi.rb"
|
||||||
|
|
||||||
|
begin
|
||||||
|
# 1. Подключаемся к API
|
||||||
|
api = HestiaBunkerWebApi.new(
|
||||||
|
"http://127.0.0.1:8888",
|
||||||
|
"admin",
|
||||||
|
"your_password"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 2. Создаём reverse proxy сервис без SSL
|
||||||
|
result = api.create_service("u4.my.brp", {
|
||||||
|
ssl: "no",
|
||||||
|
reverse_proxy_host: "http://192.168.3.51:8078"
|
||||||
|
})
|
||||||
|
|
||||||
|
puts "[INFO] Service created: #{result.inspect}"
|
||||||
|
|
||||||
|
# 3. Добавляем SSL сертификат позже (если нужно)
|
||||||
|
api.update_service_ssl(
|
||||||
|
"u4.my.brp",
|
||||||
|
"/etc/ssl/certs/u4.crt",
|
||||||
|
"/etc/ssl/private/u4.key"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 4. Проверяем список сервисов
|
||||||
|
services = api.list_services()
|
||||||
|
puts "[INFO] All services:"
|
||||||
|
services.each { |s| puts "- #{s['server_name']}" }
|
||||||
|
|
||||||
|
# 5. Удаление сервиса (при необходимости)
|
||||||
|
api.delete_service("u4.my.brp")
|
||||||
|
|
||||||
|
rescue BunkerWebApiError => e
|
||||||
|
puts "[ERROR] Ошибка API: #{e.message}"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Пример 2: Управление несколькими сервисами
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require_relative "HestiaBunkerWebApi.rb"
|
||||||
|
|
||||||
|
api = HestiaBunkerWebApi.new("http://127.0.0.1:8888", "admin", "password")
|
||||||
|
|
||||||
|
# Создаём несколько сервисов с разными конфигурациями
|
||||||
|
services_to_create = [
|
||||||
|
{ name: "service1.domain", ssl: "no", reverse_proxy_host: "http://192.168.3.50:80" },
|
||||||
|
{ name: "service2.domain", ssl: "yes", certificate_path: "/certs/service2.crt", key_path: "/keys/service2.key", reverse_proxy_host: "http://192.168.3.51:8078" }
|
||||||
|
]
|
||||||
|
|
||||||
|
services_to_create.each do |opts|
|
||||||
|
begin
|
||||||
|
api.create_service(opts[:name], opts)
|
||||||
|
rescue BunkerWebApiError => e
|
||||||
|
puts "[ERROR] #{e.message}" if e.message.include?("already exists")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Reload конфигурации на instance
|
||||||
|
api.reload_instance("192.168.3.50")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Пример 3: Обработка ошибок и логирование
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require_relative "HestiaBunkerWebApi.rb"
|
||||||
|
|
||||||
|
def safe_create_service(api_url, username, password, service_name, options)
|
||||||
|
begin
|
||||||
|
api = HestiaBunkerWebApi.new(api_url, username, password)
|
||||||
|
|
||||||
|
result = api.create_service(service_name, options)
|
||||||
|
return { success: true, data: result }
|
||||||
|
|
||||||
|
rescue BunkerWebApiError => e
|
||||||
|
if e.message.include?("Authentication")
|
||||||
|
puts "[FATAL] Authentication failed: #{e.message}"
|
||||||
|
elsif e.message.include?("already exists")
|
||||||
|
begin
|
||||||
|
existing = api.get_service(service_name)
|
||||||
|
return { success: false, already_exists: true, service: existing }
|
||||||
|
rescue => get_error
|
||||||
|
return { success: false, error: "Can't retrieve service: #{get_error.message}" }
|
||||||
|
else
|
||||||
|
return { success: false, error: e.message }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
{ success: false, error: "Unknown error" }
|
||||||
|
end
|
||||||
|
|
||||||
|
# Использование
|
||||||
|
result = safe_create_service("http://127.0.0.1:8888", "admin", "password", "example.domain", { ssl: "yes" })
|
||||||
|
|
||||||
|
if result[:success]
|
||||||
|
puts "[SUCCESS] Service created"
|
||||||
|
elsif result[:already_exists]
|
||||||
|
puts "[INFO] Service exists:"
|
||||||
|
puts JSON.generate(result[:service])
|
||||||
|
else
|
||||||
|
puts "[FAILED] #{result[:error]}"
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ошибки и их обработка
|
||||||
|
|
||||||
|
### Типичные ошибки:
|
||||||
|
|
||||||
|
| Код ответа | Описание | Пример сообщения |
|
||||||
|
|------------|----------|------------------|
|
||||||
|
| **401** | Authentication failed | "Authentication failed: 401 - Unauthorized" |
|
||||||
|
| **200 (no token)** | Auth passed but no token | "Authentication succeeded but no token received" |
|
||||||
|
| **Connection error** | API недоступен | "Authentication error: Connection refused" |
|
||||||
|
| **409 Conflict** | Service already exists | "Service 'x' already exists" |
|
||||||
|
| **422 Unprocessable Entity** | Invalid data (например, LISTEN_HTTPS_PORT = nil) | "Failed to create service: status=422..." |
|
||||||
|
| **429 Too Many Requests** | Rate limit exceeded | "Rate limit exceeded: 10 per 1 minute" |
|
||||||
|
|
||||||
|
### Решение проблем с rate limiting
|
||||||
|
|
||||||
|
Если получаете ошибку `429` (rate limit), нужно отключить или увеличить лимит в `/etc/bunkerweb/api.env`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Откройте конфиг и найдите секцию Rate limiting
|
||||||
|
nano /etc/bunkerweb/api.env
|
||||||
|
|
||||||
|
# Добавьте/измените:
|
||||||
|
API_RATE_LIMIT_ENABLED=no # Отключение rate limiting
|
||||||
|
# или
|
||||||
|
API_RATE_LIMIT=1000/minute # Увеличение лимита до 1000/m
|
||||||
|
```
|
||||||
|
|
||||||
|
Затем перезагрузите API service:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl reload bunkerweb-api.service
|
||||||
|
```
|
||||||
|
|
||||||
|
## Особенности реализации
|
||||||
|
|
||||||
|
### 1. SSL сертификатные пути
|
||||||
|
|
||||||
|
Когда `ssl: "no"` - API ожидает `"LISTEN_HTTPS_PORT" => "null"` (строка), а не JSON null (`nil`):
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# ❌ Ошибка:
|
||||||
|
variables["LISTEN_HTTPS_PORT"] = nil # → 422 error
|
||||||
|
|
||||||
|
# ✅ Правильно:
|
||||||
|
variables["LISTEN_HTTPS_PORT"] = "null" # → 200 OK
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Статусы ответа для создания сервиса
|
||||||
|
|
||||||
|
BunkerWeb API возвращает **200 OK** вместо стандартного **201 Created**:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# Класс принимает оба статуса как успех:
|
||||||
|
if [201, 200].include?(response[:status])
|
||||||
|
puts "[INFO] Service created successfully"
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Поддержка HTTP методов
|
||||||
|
|
||||||
|
Класс поддерживает все основные HTTP методы для API операций:
|
||||||
|
- **GET** - получение данных (services, instances)
|
||||||
|
- **POST** - создание (services, instances, auth)
|
||||||
|
- **PATCH** - обновление (services)
|
||||||
|
- **DELETE** - удаление (services, instances)
|
||||||
|
|
||||||
|
## Совместимость
|
||||||
|
|
||||||
|
- **Ruby**: 3.0+
|
||||||
|
- **BunkerWeb API**: 1.6.x и выше
|
||||||
|
- **ZooKeeper/Redis**: не требуются для этого класса (работает через HTTP API напрямую)
|
||||||
|
|
||||||
|
## Дополнительные ресурсы
|
||||||
|
|
||||||
|
- [Документация BunkerWeb API](https://docs.bunkerweb.io/api.md)
|
||||||
|
- [API Swagger docs at /docs](http://127.0.0.1:8888/docs)
|
||||||
|
- [OpenAPI schema](http://127.0.0.1:8888/openapi.json)
|
||||||
240
func_ruby/ext-modules/bunkerweb_module.mod
Normal file
240
func_ruby/ext-modules/bunkerweb_module.mod
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
#!/opt/brepo/ruby33/bin/ruby
|
||||||
|
|
||||||
|
require 'shellwords'
|
||||||
|
|
||||||
|
class BunkerwebWorker < Kernel::ModuleCoreWorker
|
||||||
|
MODULE_ID = "bunkerweb_module"
|
||||||
|
|
||||||
|
def info
|
||||||
|
{
|
||||||
|
ID: 5,
|
||||||
|
NAME: MODULE_ID,
|
||||||
|
DESCR: "Bunkerweb enabling",
|
||||||
|
REQ: "",
|
||||||
|
CONF: "yes",
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def enable
|
||||||
|
log_file = get_log
|
||||||
|
f_inst_pp = get_module_paydata("bunkerweb_installer.yml")
|
||||||
|
if !check
|
||||||
|
inf = info
|
||||||
|
log("Req error, needed #{inf[:REQ]}")
|
||||||
|
"Req error, needed #{inf[:REQ]}"
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
log("install packages for bunkerweb support: /usr/bin/ansible-playbook -vv #{f_inst_pp}")
|
||||||
|
result_action = `LC_ALL=C.UTF-8 /usr/bin/ansible-playbook -vv "#{f_inst_pp}" 2>&1`
|
||||||
|
ex_status = $?.exitstatus
|
||||||
|
if ex_status.to_i == 0 || ex_status.to_i == 2
|
||||||
|
log(result_action)
|
||||||
|
super
|
||||||
|
end
|
||||||
|
rescue => e
|
||||||
|
log("module installation error #{e.message} #{e.backtrace.first}")
|
||||||
|
"module installation error. See log #{log_file}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
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 "add"
|
||||||
|
m_domain = args[1].strip unless args[1].nil?
|
||||||
|
m_ip = args[2].strip unless args[2].nil?
|
||||||
|
if m_domain.nil? || m_ip.nil?
|
||||||
|
log_return("Domain and IP should be specified. #{args}")
|
||||||
|
else
|
||||||
|
|
||||||
|
log("add domain to bunkerweb protection")
|
||||||
|
output = `/usr/local/hestia/bin/v-bunkerweb-module add #{m_domain} #{m_ip} shell`
|
||||||
|
exit_status = $?.exitstatus
|
||||||
|
if exit_status != 0
|
||||||
|
log_return("Command failed with status #{exit_status}")
|
||||||
|
else
|
||||||
|
ACTION_OK
|
||||||
|
end
|
||||||
|
end
|
||||||
|
when "delete"
|
||||||
|
m_domain = args[1].strip unless args[1].nil?
|
||||||
|
if m_domain.nil?
|
||||||
|
log_return("Domain should be specified. #{args}")
|
||||||
|
else
|
||||||
|
|
||||||
|
log("add domain to bunkerweb protection")
|
||||||
|
output = `/usr/local/hestia/bin/v-bunkerweb-module delete #{m_domain} shell`
|
||||||
|
exit_status = $?.exitstatus
|
||||||
|
if exit_status != 0
|
||||||
|
log_return("Command failed with status #{exit_status}")
|
||||||
|
else
|
||||||
|
ACTION_OK
|
||||||
|
end
|
||||||
|
end
|
||||||
|
when "addssl"
|
||||||
|
m_domain = args[1].strip unless args[1].nil?
|
||||||
|
m_ssl_cert = args[2].strip unless args[2].nil?
|
||||||
|
m_ssl_key = args[3].strip unless args[3].nil?
|
||||||
|
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")
|
||||||
|
output = `/usr/local/hestia/bin/v-bunkerweb-module addssl #{m_domain} #{m_ssl_cert} #{m_ssl_key} shell`
|
||||||
|
exit_status = $?.exitstatus
|
||||||
|
if exit_status != 0
|
||||||
|
log_return("Command failed with status #{exit_status}")
|
||||||
|
else
|
||||||
|
ACTION_OK
|
||||||
|
end
|
||||||
|
end
|
||||||
|
when "updssl"
|
||||||
|
m_domain = args[1].strip unless args[1].nil?
|
||||||
|
m_ssl_cert = args[2].strip unless args[2].nil?
|
||||||
|
m_ssl_key = args[3].strip unless args[3].nil?
|
||||||
|
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("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
|
||||||
|
log_return("Command failed with status #{exit_status}")
|
||||||
|
else
|
||||||
|
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")
|
||||||
|
output = `/usr/local/hestia/bin/v-bunkerweb-module list #{format}`
|
||||||
|
exit_status = $?.exitstatus
|
||||||
|
if exit_status != 0
|
||||||
|
log_return("Command failed with status #{exit_status}")
|
||||||
|
else
|
||||||
|
puts output
|
||||||
|
ACTION_OK
|
||||||
|
end
|
||||||
|
when "passwd"
|
||||||
|
format = (args[1].nil? ? "shell" : args[1].strip)
|
||||||
|
cred = {}
|
||||||
|
api_file = "/etc/bunkerweb/api.env"
|
||||||
|
if File.exist?(api_file)
|
||||||
|
File.readlines(api_file).each do |line|
|
||||||
|
line.strip!
|
||||||
|
next if line.empty? || line.start_with?('#')
|
||||||
|
key, value = line.split('=', 2)
|
||||||
|
if %w[API_USERNAME API_PASSWORD].include?(key)
|
||||||
|
cred[key] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
cred["API_USERNAME"] = nil
|
||||||
|
cred["API_PASSWORD"] = nil
|
||||||
|
end
|
||||||
|
cred["API_USERNAME"] ||= nil
|
||||||
|
cred["API_PASSWORD"] ||= nil
|
||||||
|
|
||||||
|
ui_file = "/etc/bunkerweb/ui.env"
|
||||||
|
if File.exist?(ui_file)
|
||||||
|
File.readlines(ui_file).each do |line|
|
||||||
|
line.strip!
|
||||||
|
next if line.empty? || line.start_with?('#')
|
||||||
|
key, value = line.split('=', 2)
|
||||||
|
if %w[ADMIN_USERNAME ADMIN_PASSWORD].include?(key)
|
||||||
|
cred[key] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
cred["ADMIN_USERNAME"] = nil
|
||||||
|
cred["ADMIN_PASSWORD"] = nil
|
||||||
|
end
|
||||||
|
cred["ADMIN_USERNAME"] ||= nil
|
||||||
|
cred["ADMIN_PASSWORD"] ||= nil
|
||||||
|
|
||||||
|
result = []
|
||||||
|
result << cred
|
||||||
|
hestia_print_array_of_hashes(result, format, "API_USERNAME,API_PASSWORD,ADMIN_USERNAME,ADMIN_PASSWORD")
|
||||||
|
ACTION_OK
|
||||||
|
when "configure"
|
||||||
|
param1 = args[1]
|
||||||
|
param2 = args[2]
|
||||||
|
if param1 && param2 && !param1.strip.empty? && !param2.strip.empty?
|
||||||
|
cmd = "/usr/local/hestia/bin/v-bunkerweb-module-install #{Shellwords.escape(param1)} #{Shellwords.escape(param2)}"
|
||||||
|
else
|
||||||
|
cmd = "/usr/local/hestia/bin/v-bunkerweb-module-install"
|
||||||
|
end
|
||||||
|
output = `#{cmd} 2>&1`
|
||||||
|
exit_status = $?.exitstatus
|
||||||
|
if exit_status != 0
|
||||||
|
log_return("#{output}\nCommand failed with status #{exit_status}")
|
||||||
|
else
|
||||||
|
puts output
|
||||||
|
ACTION_OK
|
||||||
|
end
|
||||||
|
when "alias"
|
||||||
|
m_domain = args[1].strip unless args[1].nil?
|
||||||
|
m_alias = args[2].strip unless args[2].nil?
|
||||||
|
if m_domain.nil?
|
||||||
|
log_return("Domain should be specified. #{args}")
|
||||||
|
else
|
||||||
|
|
||||||
|
log("add alias #{m_alias} to domain #{m_domain} to bunkerweb protection")
|
||||||
|
output = `/usr/local/hestia/bin/v-bunkerweb-module alias #{m_domain} "#{m_alias}" shell`
|
||||||
|
exit_status = $?.exitstatus
|
||||||
|
if exit_status != 0
|
||||||
|
log_return("Command failed with status #{exit_status}")
|
||||||
|
else
|
||||||
|
ACTION_OK
|
||||||
|
end
|
||||||
|
end
|
||||||
|
when "help"
|
||||||
|
puts "#{$0} bunkerweb_module COMMAND [OPTIONS] [json|csv|plain]"
|
||||||
|
puts "COMMANDS:"
|
||||||
|
puts " add domain - add domain to bunkerweb"
|
||||||
|
puts " delete domain - delete domain from bunkerweb"
|
||||||
|
puts " addssl domain [path_to_cert] [path_to_key] - add existsing certificate to bunkerweb domain"
|
||||||
|
puts " updssl domain [path_to_cert] [path_to_key] - update existsing certificate to bunkerweb domain"
|
||||||
|
puts " passwd - get ui and api passwd"
|
||||||
|
puts " configure [path_to_cert] [path_to_key] - start initial setup of bunkerweb should do only once"
|
||||||
|
puts " help - help"
|
||||||
|
ACTION_OK
|
||||||
|
else
|
||||||
|
log_return("Unknown command. #{args}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
implements IPluginInterface
|
||||||
|
end
|
||||||
|
|
||||||
|
module BunkerwebModule
|
||||||
|
def get_object
|
||||||
|
Proc.new { BunkerwebWorker.new }
|
||||||
|
end
|
||||||
|
|
||||||
|
module_function :get_object
|
||||||
|
end
|
||||||
|
|
||||||
|
class Kernel::PluginConfiguration
|
||||||
|
include BunkerwebModule
|
||||||
|
|
||||||
|
@@loaded_plugins[BunkerwebWorker::MODULE_ID] = BunkerwebModule.get_object
|
||||||
|
end
|
||||||
1
func_ruby/ext-modules/last_free_id
Normal file
1
func_ruby/ext-modules/last_free_id
Normal file
@@ -0,0 +1 @@
|
|||||||
|
7
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
- name: Install Bunkerweb on localhost
|
||||||
|
hosts: localhost
|
||||||
|
connection: local
|
||||||
|
become: true
|
||||||
|
gather_facts: false
|
||||||
|
environment:
|
||||||
|
LANG: en_US.UTF-8
|
||||||
|
LC_ALL: en_US.UTF-8
|
||||||
|
tasks:
|
||||||
|
- name: Install bunkerweb
|
||||||
|
ansible.builtin.dnf:
|
||||||
|
name: bunkerweb
|
||||||
|
state: present
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
# Конфигурируем Nginx для Passenger
|
# Конфигурируем Nginx для Passenger
|
||||||
- name: Create passenger.conf
|
- name: Create passenger.conf
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
dest: /etc/nginx/conf.d/passenger.conf
|
dest: /usr/local/hestia/nginx-system/etc/nginx/conf.d/passenger.conf
|
||||||
content: |
|
content: |
|
||||||
passenger_root /usr/share/ruby/vendor_ruby/phusion_passenger/locations.ini;
|
passenger_root /usr/share/ruby/vendor_ruby/phusion_passenger/locations.ini;
|
||||||
passenger_ruby /usr/bin/ruby;
|
passenger_ruby /usr/bin/ruby;
|
||||||
@@ -59,11 +59,11 @@
|
|||||||
passenger_env_var PASSENGER_DOWNLOAD_NATIVE_SUPPORT_BINARY 0;
|
passenger_env_var PASSENGER_DOWNLOAD_NATIVE_SUPPORT_BINARY 0;
|
||||||
- name: Create passenger_includer.conf
|
- name: Create passenger_includer.conf
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
dest: /etc/nginx/conf.d/main/passenger.conf
|
dest: /usr/local/hestia/nginx-system/etc/nginx/conf.d/main/passenger.conf
|
||||||
content: |
|
content: |
|
||||||
load_module modules/ngx_http_passenger_module.so;
|
load_module modules/ngx_http_passenger_module.so;
|
||||||
# Перезапускаем Nginx
|
# Перезапускаем Nginx
|
||||||
- name: Restart nginx service
|
- name: Restart nginx service
|
||||||
ansible.builtin.service:
|
ansible.builtin.service:
|
||||||
name: nginx
|
name: nginx-system
|
||||||
state: restarted
|
state: restarted
|
||||||
|
|||||||
@@ -25,14 +25,14 @@
|
|||||||
# Удаляем конфигурационные файлы Nginx
|
# Удаляем конфигурационные файлы Nginx
|
||||||
- name: Remove passenger.conf
|
- name: Remove passenger.conf
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: /etc/nginx/conf.d/passenger.conf
|
path: /usr/local/hestia/nginx-system/etc/nginx/conf.d/passenger.conf
|
||||||
state: absent
|
state: absent
|
||||||
- name: Remove passenger_includer.conf
|
- name: Remove passenger_includer.conf
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: /etc/nginx/conf.d/main/passenger.conf
|
path: /usr/local/hestia/nginx-system/etc/nginx/conf.d/main/passenger.conf
|
||||||
state: absent
|
state: absent
|
||||||
# Перезапускаем Nginx (необязательно, но полезно)
|
# Перезапускаем Nginx (необязательно, но полезно)
|
||||||
- name: Restart nginx service
|
- name: Restart nginx service
|
||||||
ansible.builtin.service:
|
ansible.builtin.service:
|
||||||
name: nginx
|
name: nginx-system
|
||||||
state: restarted
|
state: restarted
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/opt/brepo/ruby33/bin/ruby
|
#!/opt/brepo/ruby33/bin/ruby
|
||||||
|
|
||||||
class EmptyWorker < Kernel::ModuleCoreWorker
|
class PHPWorker < Kernel::ModuleCoreWorker
|
||||||
MODULE_ID = "php_brepo_modules"
|
MODULE_ID = "php_brepo_modules"
|
||||||
|
|
||||||
def info
|
def info
|
||||||
@@ -241,16 +241,16 @@ class EmptyWorker < Kernel::ModuleCoreWorker
|
|||||||
implements IPluginInterface
|
implements IPluginInterface
|
||||||
end
|
end
|
||||||
|
|
||||||
module EmptyModule
|
module PHPModule
|
||||||
def get_object
|
def get_object
|
||||||
Proc.new { EmptyWorker.new }
|
Proc.new { PHPWorker.new }
|
||||||
end
|
end
|
||||||
|
|
||||||
module_function :get_object
|
module_function :get_object
|
||||||
end
|
end
|
||||||
|
|
||||||
class Kernel::PluginConfiguration
|
class Kernel::PluginConfiguration
|
||||||
include EmptyModule
|
include PHPModule
|
||||||
|
|
||||||
@@loaded_plugins[EmptyWorker::MODULE_ID] = EmptyModule.get_object
|
@@loaded_plugins[PHPWorker::MODULE_ID] = PHPModule.get_object
|
||||||
end
|
end
|
||||||
|
|||||||
122
func_ruby/ext-modules/update_module.mod
Normal file
122
func_ruby/ext-modules/update_module.mod
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
#!/opt/brepo/ruby33/bin/ruby
|
||||||
|
|
||||||
|
require 'pathname'
|
||||||
|
require 'fileutils'
|
||||||
|
require 'digest'
|
||||||
|
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
dir_name = File.basename(new_dir)
|
||||||
|
relative_path = Pathname.new(new_file).relative_path_from(Pathname.new(new_dir)).to_s
|
||||||
|
file_name = File.join(dir_name, relative_path)
|
||||||
|
{
|
||||||
|
"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
|
||||||
@@ -77,6 +77,10 @@ class File
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def hestia_print_info_message_to_cli(error_message)
|
||||||
|
puts "Info: #{error_message}"
|
||||||
|
end
|
||||||
|
|
||||||
def hestia_print_error_message_to_cli(error_message)
|
def hestia_print_error_message_to_cli(error_message)
|
||||||
puts "Error: #{error_message}"
|
puts "Error: #{error_message}"
|
||||||
end
|
end
|
||||||
@@ -315,3 +319,80 @@ def hestia_save_file_key_pair(file, key, value)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def hestia_change_sys_config_value(key, value)
|
||||||
|
# Privileged access check
|
||||||
|
hestia_check_privileged_user unless Process.uid == 0
|
||||||
|
|
||||||
|
config_file = "/usr/local/hestia/conf/hestia.conf"
|
||||||
|
|
||||||
|
if File.exist?(config_file)
|
||||||
|
# First pass: read entire file to check if key exists and get all lines
|
||||||
|
content = nil
|
||||||
|
|
||||||
|
File.open(config_file, "r") do |f|
|
||||||
|
content = f.read
|
||||||
|
end
|
||||||
|
|
||||||
|
if content
|
||||||
|
lines = content.split("\n")
|
||||||
|
|
||||||
|
# Check if key exists in the configuration file
|
||||||
|
existing_line_index = -1
|
||||||
|
|
||||||
|
lines.each_with_index do |line, idx|
|
||||||
|
line_stripped = line.strip
|
||||||
|
# Skip comment lines
|
||||||
|
next if line_stripped.start_with?("#")
|
||||||
|
next if line_stripped.empty?
|
||||||
|
|
||||||
|
key_match = line_stripped.match(/^\s*#{Regexp.escape(key)}='\s*(.*?)\s*$/)
|
||||||
|
if key_match
|
||||||
|
existing_line_index = idx + 1
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if existing_line_index.nil? || existing_line_index == -1
|
||||||
|
# Key doesn't exist - append new line to file
|
||||||
|
File.open(config_file, "a") do |append_f|
|
||||||
|
append_f.puts("#{key}='#{value}'")
|
||||||
|
end
|
||||||
|
OK
|
||||||
|
else
|
||||||
|
# Key exists - update value using Ruby operators (in-place edit)
|
||||||
|
# Use temporary file for safety and atomic replacement
|
||||||
|
temp_file = "#{config_file}.tmp"
|
||||||
|
|
||||||
|
# Second pass: rebuild the content with updated value
|
||||||
|
new_lines = []
|
||||||
|
|
||||||
|
lines.each do |line|
|
||||||
|
line_stripped = line.strip
|
||||||
|
# Skip comment lines
|
||||||
|
next if line_stripped.start_with?("#")
|
||||||
|
next if line_stripped.empty?
|
||||||
|
|
||||||
|
# Match and replace the key-value pair
|
||||||
|
if line.match(/^\s*#{Regexp.escape(key)}='[^']*'/)
|
||||||
|
new_lines << "#{key}='#{value}'"
|
||||||
|
else
|
||||||
|
new_lines << line
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
File.open(temp_file, "w") do |output_f|
|
||||||
|
new_lines.each { |l| output_f.puts(l) }
|
||||||
|
end
|
||||||
|
|
||||||
|
# Atomic file replacement
|
||||||
|
File.rename(temp_file, config_file)
|
||||||
|
OK
|
||||||
|
end
|
||||||
|
else
|
||||||
|
OK
|
||||||
|
end
|
||||||
|
else
|
||||||
|
check_result error_code: E_NOTEXIST, error_message: "Configuration file #{config_file} does not exist"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
passdb {
|
passdb {
|
||||||
driver = passwd-file
|
driver = passwd-file
|
||||||
args = scheme=MD5-CRYPT username_format=%n /etc/exim4/domains/%d/passwd
|
args = scheme=MD5-CRYPT username_format=%n /etc/exim/domains/%d/passwd
|
||||||
}
|
}
|
||||||
|
|
||||||
userdb {
|
userdb {
|
||||||
driver = passwd-file
|
driver = passwd-file
|
||||||
args = username_format=%n /etc/exim4/domains/%d/passwd
|
args = username_format=%n /etc/exim/domains/%d/passwd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
# It is split into several files forming the configuration hierarchy outlined
|
|
||||||
# below, all located in the /etc/apache2/ directory:
|
|
||||||
#
|
|
||||||
# /etc/apache2/
|
|
||||||
# |-- apache2.conf
|
|
||||||
# | `-- ports.conf
|
|
||||||
# |-- mods-enabled
|
|
||||||
# | |-- *.load
|
|
||||||
# | `-- *.conf
|
|
||||||
# |-- conf.d
|
|
||||||
# | `-- *
|
|
||||||
|
|
||||||
# Global configuration
|
|
||||||
PidFile ${APACHE_PID_FILE}
|
|
||||||
Timeout 30
|
|
||||||
KeepAlive Off
|
|
||||||
MaxKeepAliveRequests 100
|
|
||||||
KeepAliveTimeout 10
|
|
||||||
|
|
||||||
<IfModule mpm_prefork_module>
|
|
||||||
StartServers 8
|
|
||||||
MinSpareServers 5
|
|
||||||
MaxSpareServers 20
|
|
||||||
ServerLimit 256
|
|
||||||
MaxClients 200
|
|
||||||
MaxRequestsPerChild 4000
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
<IfModule mpm_worker_module>
|
|
||||||
StartServers 2
|
|
||||||
MinSpareThreads 25
|
|
||||||
MaxSpareThreads 75
|
|
||||||
ThreadLimit 64
|
|
||||||
ThreadsPerChild 25
|
|
||||||
MaxClients 200
|
|
||||||
MaxRequestsPerChild 4000
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
<IfModule mpm_event_module>
|
|
||||||
StartServers 2
|
|
||||||
MinSpareThreads 25
|
|
||||||
MaxSpareThreads 75
|
|
||||||
ThreadLimit 64
|
|
||||||
ThreadsPerChild 25
|
|
||||||
MaxClients 200
|
|
||||||
MaxRequestsPerChild 4000
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
# These need to be set in /etc/apache2/envvars
|
|
||||||
User ${APACHE_RUN_USER}
|
|
||||||
Group ${APACHE_RUN_GROUP}
|
|
||||||
#User www-data
|
|
||||||
#Group www-data
|
|
||||||
|
|
||||||
AccessFileName .htaccess
|
|
||||||
|
|
||||||
<Files ~ "^\.ht">
|
|
||||||
Order allow,deny
|
|
||||||
Deny from all
|
|
||||||
Satisfy all
|
|
||||||
</Files>
|
|
||||||
|
|
||||||
DefaultType None
|
|
||||||
HostnameLookups Off
|
|
||||||
|
|
||||||
ErrorLog ${APACHE_LOG_DIR}/error.log
|
|
||||||
LogLevel warn
|
|
||||||
|
|
||||||
# Include module configuration:
|
|
||||||
Include mods-enabled/*.load
|
|
||||||
Include mods-enabled/*.conf
|
|
||||||
|
|
||||||
# Include list of ports to listen on and which to use for name based vhosts
|
|
||||||
Include ports.conf
|
|
||||||
|
|
||||||
LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
|
|
||||||
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
|
|
||||||
LogFormat "%h %l %u %t \"%r\" %>s %O" common
|
|
||||||
LogFormat "%{Referer}i -> %U" referer
|
|
||||||
LogFormat "%{User-agent}i" agent
|
|
||||||
LogFormat "%b" bytes
|
|
||||||
|
|
||||||
IncludeOptional conf.d/*.conf
|
|
||||||
IncludeOptional conf.d/domains/webmail.*.conf
|
|
||||||
IncludeOptional conf.d/domains/*.conf
|
|
||||||
|
|
||||||
# Include the virtual host configurations:
|
|
||||||
#Include sites-enabled/
|
|
||||||
|
|
||||||
ErrorDocument 403 /error/403.html
|
|
||||||
ErrorDocument 404 /error/404.html
|
|
||||||
ErrorDocument 500 /error/50x.html
|
|
||||||
ErrorDocument 501 /error/50x.html
|
|
||||||
ErrorDocument 502 /error/50x.html
|
|
||||||
ErrorDocument 503 /error/50x.html
|
|
||||||
ErrorDocument 506 /error/50x.html
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<IfModule mpm_event_module>
|
|
||||||
<FilesMatch \.php$>
|
|
||||||
SetHandler "proxy:fcgi://127.0.0.1:9000"
|
|
||||||
</FilesMatch>
|
|
||||||
</IfModule>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
Listen 127.0.0.1:8081
|
|
||||||
<Location /server-status>
|
|
||||||
SetHandler server-status
|
|
||||||
Order deny,allow
|
|
||||||
Deny from all
|
|
||||||
Allow from 127.0.0.1
|
|
||||||
</Location>
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<VirtualHost directIP:directPORT>
|
|
||||||
ServerName directIP
|
|
||||||
DocumentRoot /var/www/html/
|
|
||||||
Alias /error/ /var/www/document_errors/
|
|
||||||
|
|
||||||
</VirtualHost>
|
|
||||||
|
|
||||||
<VirtualHost directIP:directSSLPORT>
|
|
||||||
ServerName directIP
|
|
||||||
DocumentRoot /var/www/html/
|
|
||||||
Alias /error/ /var/www/document_errors/
|
|
||||||
|
|
||||||
SSLEngine on
|
|
||||||
SSLVerifyClient none
|
|
||||||
SSLCertificateFile /usr/local/hestia/ssl/certificate.crt
|
|
||||||
SSLCertificateKeyFile /usr/local/hestia/ssl/certificate.key
|
|
||||||
|
|
||||||
</VirtualHost>
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
// This is the primary configuration file for the BIND DNS server named.
|
|
||||||
//
|
|
||||||
// Please read /usr/share/doc/bind9/README.Debian.gz for information on the
|
|
||||||
// structure of BIND configuration files in Debian, *BEFORE* you customize
|
|
||||||
// this configuration file.
|
|
||||||
//
|
|
||||||
// If you are just adding zones, please do that in /etc/bind/named.conf.local
|
|
||||||
|
|
||||||
include "/etc/bind/named.conf.options";
|
|
||||||
include "/etc/bind/named.conf.local";
|
|
||||||
include "/etc/bind/named.conf.default-zones";
|
|
||||||
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
options {
|
|
||||||
directory "/var/cache/bind";
|
|
||||||
// If there is a firewall between you and nameservers you want
|
|
||||||
// to talk to, you may need to fix the firewall to allow multiple
|
|
||||||
// ports to talk. See http://www.kb.cert.org/vuls/id/800113
|
|
||||||
// If your ISP provided one or more IP addresses for stable
|
|
||||||
// nameservers, you probably want to use them as forwarders.
|
|
||||||
// Uncomment the following block, and insert the addresses replacing
|
|
||||||
// the all-0's placeholder.
|
|
||||||
// forwarders {
|
|
||||||
// 0.0.0.0;
|
|
||||||
// };
|
|
||||||
//========================================================================
|
|
||||||
// If BIND logs error messages about the root key being expired,
|
|
||||||
// you will need to update your keys. See https://www.isc.org/bind-keys
|
|
||||||
//========================================================================
|
|
||||||
dnssec-validation auto;
|
|
||||||
auth-nxdomain no;
|
|
||||||
allow-recursion { 127.0.0.1; ::1; };
|
|
||||||
allow-transfer {"none";};
|
|
||||||
hostname none;
|
|
||||||
server-id none;
|
|
||||||
version none;
|
|
||||||
};
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
#Automatically Generated by clamav-daemon postinst
|
|
||||||
#To reconfigure clamd run #dpkg-reconfigure clamav-daemon
|
|
||||||
#Please read /usr/share/doc/clamav-daemon/README.Debian.gz for details
|
|
||||||
LocalSocket /run/clamav/clamd.ctl
|
|
||||||
FixStaleSocket true
|
|
||||||
LocalSocketGroup clamav
|
|
||||||
LocalSocketMode 666
|
|
||||||
# TemporaryDirectory is not set to its default /tmp here to make overriding
|
|
||||||
# the default with environment variables TMPDIR/TMP/TEMP possible
|
|
||||||
User clamav
|
|
||||||
ScanMail true
|
|
||||||
ScanArchive true
|
|
||||||
ArchiveBlockEncrypted false
|
|
||||||
MaxDirectoryRecursion 15
|
|
||||||
FollowDirectorySymlinks false
|
|
||||||
FollowFileSymlinks false
|
|
||||||
ReadTimeout 180
|
|
||||||
MaxThreads 12
|
|
||||||
MaxConnectionQueueLength 15
|
|
||||||
LogSyslog false
|
|
||||||
LogRotate true
|
|
||||||
LogFacility LOG_LOCAL6
|
|
||||||
LogClean false
|
|
||||||
LogVerbose true
|
|
||||||
PreludeEnable no
|
|
||||||
PreludeAnalyzerName ClamAV
|
|
||||||
DatabaseDirectory /var/lib/clamav
|
|
||||||
OfficialDatabaseOnly false
|
|
||||||
SelfCheck 3600
|
|
||||||
Foreground false
|
|
||||||
Debug false
|
|
||||||
ScanPE true
|
|
||||||
MaxEmbeddedPE 10M
|
|
||||||
ScanOLE2 true
|
|
||||||
ScanPDF true
|
|
||||||
ScanHTML true
|
|
||||||
MaxHTMLNormalize 10M
|
|
||||||
MaxHTMLNoTags 2M
|
|
||||||
MaxScriptNormalize 5M
|
|
||||||
MaxZipTypeRcg 1M
|
|
||||||
ScanSWF true
|
|
||||||
ExitOnOOM false
|
|
||||||
LeaveTemporaryFiles false
|
|
||||||
AlgorithmicDetection true
|
|
||||||
ScanELF true
|
|
||||||
IdleTimeout 30
|
|
||||||
CrossFilesystems true
|
|
||||||
PhishingSignatures true
|
|
||||||
PhishingScanURLs true
|
|
||||||
PhishingAlwaysBlockSSLMismatch false
|
|
||||||
PhishingAlwaysBlockCloak false
|
|
||||||
PartitionIntersection false
|
|
||||||
DetectPUA false
|
|
||||||
ScanPartialMessages false
|
|
||||||
HeuristicScanPrecedence false
|
|
||||||
StructuredDataDetection false
|
|
||||||
CommandReadTimeout 5
|
|
||||||
SendBufTimeout 200
|
|
||||||
MaxQueue 100
|
|
||||||
ExtendedDetectionInfo true
|
|
||||||
OLE2BlockMacros false
|
|
||||||
AllowAllMatchScan true
|
|
||||||
ForceToDisk false
|
|
||||||
DisableCertCheck false
|
|
||||||
DisableCache false
|
|
||||||
MaxScanTime 120000
|
|
||||||
MaxScanSize 100M
|
|
||||||
MaxFileSize 25M
|
|
||||||
MaxRecursion 16
|
|
||||||
MaxFiles 10000
|
|
||||||
MaxPartitions 50
|
|
||||||
MaxIconsPE 100
|
|
||||||
PCREMatchLimit 10000
|
|
||||||
PCRERecMatchLimit 5000
|
|
||||||
PCREMaxFileSize 25M
|
|
||||||
ScanXMLDOCS true
|
|
||||||
ScanHWP3 true
|
|
||||||
MaxRecHWP3 16
|
|
||||||
StreamMaxLength 25M
|
|
||||||
LogFile /var/log/clamav/clamav.log
|
|
||||||
LogTime true
|
|
||||||
LogFileUnlock false
|
|
||||||
LogFileMaxSize 0
|
|
||||||
Bytecode true
|
|
||||||
BytecodeSecurity TrustSigned
|
|
||||||
BytecodeTimeout 60000
|
|
||||||
PidFile /run/clamav/clamd.pid
|
|
||||||
OnAccessMaxFileSize 5M
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
|
||||||
Version: GnuPG v1.4.12 (GNU/Linux)
|
|
||||||
|
|
||||||
mQENBFJIGbEBCAC8SHOOFo7iDTbnC2GhNZ+uBGCh226Dn1QPoFZNFM/DNakHZ6rD
|
|
||||||
G3wzr8++eKz4fJual/VLllE2N9XDPuxbozb3LLkcyY1WzJqtIXbXhFGQ/SuIeT+x
|
|
||||||
QY90XU6t2Ckze2c+zUniAWmJ8GSyVmXOoc9JxAQ1u47wvGXLzrjWXc8u8PNRYXuf
|
|
||||||
fZplTL+dFu9P0d6lP8FGsV+r9wXvvazpRTz3+H8PKrGCYT55ZQIEdG9Jgamylto2
|
|
||||||
oVPFXkwGML+TLw6oeCIBuz2y2vtivphW4MJ3ifQjDj7k3n+DTIxfDFs8lB6VRhhY
|
|
||||||
2nMHCrcZC6U2mhmXmr6O4s1fu6irBVx05ejPABEBAAG0IFNlcmdoZXkgUm9kaW4g
|
|
||||||
PHNraWRAdmVzdGFjcC5jb20+iQE4BBMBAgAiBQJSSBmxAhsDBgsJCAcDAgYVCAIJ
|
|
||||||
CgsEFgIDAQIeAQIXgAAKCRBCxbITCh93FPdqB/93GjV9g+wBfeZYLHQK9MDU2wBb
|
|
||||||
VloYOJJae6IvYKYQVAJayD3PbHdpxrF8s9e23vdnmb9jKu6jX6oV54EIyqP2HPiN
|
|
||||||
QYc8wcea+eSHerznBixCtoQh8mtdWGFeN71zU/ig7L5qlOVF/EmxDVZTFUeivFxh
|
|
||||||
IV6qyBnktQKktE45585yKZyyLtfGoXA54DGK69OtJFh+wdkKEMmUXocMl7wUrxW6
|
|
||||||
Cx2CuKeEXEgvwu8mRHQi3S3T9XP456qWEn5dWyMVcP660IzEuZfSJApZusNK7zG3
|
|
||||||
WMy0/EuX7xHNY3mcNxTOUN1LsO7iHnhHD9+iKWJo9parGkMZzc92MpjDK/g7uQEN
|
|
||||||
BFJIGbEBCAC7k5QEA9WQM7E3ceNaeLMrA9lXfuzaNCcySq7ONdVAa5PxzbSKdHvz
|
|
||||||
QFoL1VFqBTYQ038lbil1XqnoM0zvIfAI3LcpS8sq92El/vPxp6jZh2Ari9Uw7x95
|
|
||||||
k2cZMgI67g+zQMGdjVRA155nFQRCgg000xU4F7JA6+WsuLlVUmccsDv7YWJExMtC
|
|
||||||
YPxiuz5DFu8RALnw4Ckts+dbwsrcvUHhkm9b6RAsdCKjjRpUZjLgdltjH83gUVvt
|
|
||||||
i1YmdjjsVpt95dtsaG+ad852g/Rk8EdxNMkjPF6HLA67CLADP9wYaj80yPcPtylS
|
|
||||||
ycvPtcclVeHkFBRVM8xZpQd4iD19MWI1ABEBAAGJAR8EGAECAAkFAlJIGbECGwwA
|
|
||||||
CgkQQsWyEwofdxQ7tQgAhB0FwTs7L8Qr63DHC2yAnXVxgtTAY1/36CccNXVculyR
|
|
||||||
+EkLcwahms9AKhz7eQb+Mud+5vH0GRohLp2npgO38CjVUfIP5d+Y6dsthmrkF6p8
|
|
||||||
XdV1dVK9vWX+i/YZSw/Mded30Cq4P2Yhq9EaemMT0rtli8lz2NnkZ9dFJZk1lzJC
|
|
||||||
CZmRpbjSNWqRU4f7qyh21lYk/OC/0XE8fh8CaO23TZ+6gBionoCztwb7NyC9OArN
|
|
||||||
qYlNnbmh9iNqdblykPS3bkjf34n2xyMgnIehNrM89tk8PY4UfNPhgT1TMD9W3Svq
|
|
||||||
ynNZvLuF/FIDwDeC1qcfjGbfDn9fXO/lMIIRooQYKQ==
|
|
||||||
=J2HJ
|
|
||||||
-----END PGP PUBLIC KEY BLOCK-----
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
bl.spamcop.net
|
|
||||||
zen.spamhaus.org
|
|
||||||
@@ -1,476 +0,0 @@
|
|||||||
######################################################################
|
|
||||||
# #
|
|
||||||
# Exim configuration file for Hestia Control Panel #
|
|
||||||
# #
|
|
||||||
######################################################################
|
|
||||||
|
|
||||||
#SPAMASSASSIN = yes
|
|
||||||
#SPAM_SCORE = 50
|
|
||||||
#SPAM_REJECT_SCORE = 100
|
|
||||||
#CLAMD = yes
|
|
||||||
|
|
||||||
smtp_banner = $smtp_active_hostname
|
|
||||||
smtp_active_hostname = ${lookup dnsdb{>: defer_never,ptr=$interface_address}{${listextract{1}{$value}}}{$primary_hostname}}
|
|
||||||
add_environment = <; PATH=/bin:/usr/bin
|
|
||||||
keep_environment =
|
|
||||||
disable_ipv6 = true
|
|
||||||
|
|
||||||
smtputf8_advertise_hosts =
|
|
||||||
domainlist local_domains = dsearch;/etc/exim4/domains/
|
|
||||||
domainlist relay_to_domains = dsearch;/etc/exim4/domains/
|
|
||||||
hostlist relay_from_hosts = 127.0.0.1
|
|
||||||
hostlist whitelist = net-iplsearch;/etc/exim4/white-blocks.conf
|
|
||||||
hostlist spammers = net-iplsearch;/etc/exim4/spam-blocks.conf
|
|
||||||
no_local_from_check
|
|
||||||
untrusted_set_sender = *
|
|
||||||
acl_smtp_connect = acl_check_spammers
|
|
||||||
acl_smtp_mail = acl_check_mail
|
|
||||||
acl_smtp_rcpt = acl_check_rcpt
|
|
||||||
acl_smtp_data = acl_check_data
|
|
||||||
acl_smtp_mime = acl_check_mime
|
|
||||||
|
|
||||||
.ifdef SPAMASSASSIN
|
|
||||||
spamd_address = 127.0.0.1 783
|
|
||||||
.endif
|
|
||||||
|
|
||||||
.ifdef CLAMD
|
|
||||||
av_scanner = clamd: /run/clamav/clamd.ctl
|
|
||||||
.endif
|
|
||||||
|
|
||||||
log_selector = +tls_sni
|
|
||||||
|
|
||||||
tls_advertise_hosts = *
|
|
||||||
|
|
||||||
# We test that $tls_in_sni is a valid domain, by an arbitrary email address foo@domain.tld .
|
|
||||||
# Then, we extract the domain with a function that would fail if the email address is invalid.
|
|
||||||
# If the certificate exists, we will use it, otherwise the default certificate in /etc/ssl will be used.
|
|
||||||
tls_certificate = \
|
|
||||||
${if and {\
|
|
||||||
{ eq {${domain:foo@$tls_in_sni}} {$tls_in_sni}}\
|
|
||||||
{ exists{/usr/local/hestia/ssl/mail/$tls_in_sni.crt} }\
|
|
||||||
}\
|
|
||||||
{/usr/local/hestia/ssl/mail/$tls_in_sni.crt}\
|
|
||||||
{/usr/local/hestia/ssl/certificate.crt}\
|
|
||||||
}
|
|
||||||
|
|
||||||
tls_privatekey = \
|
|
||||||
${if and {\
|
|
||||||
{ eq {${domain:foo@$tls_in_sni}} {$tls_in_sni}}\
|
|
||||||
{ exists{/usr/local/hestia/ssl/mail/$tls_in_sni.key} }\
|
|
||||||
}\
|
|
||||||
{/usr/local/hestia/ssl/mail/$tls_in_sni.key}\
|
|
||||||
{/usr/local/hestia/ssl/certificate.key}\
|
|
||||||
}
|
|
||||||
|
|
||||||
daemon_smtp_ports = 25 : 465 : 587
|
|
||||||
tls_on_connect_ports = 465
|
|
||||||
tls_require_ciphers = PERFORMANCE:-RSA:-VERS-ALL:+VERS-TLS1.2:+VERS-TLS1.3:%SERVER_PRECEDENCE
|
|
||||||
never_users = root
|
|
||||||
host_lookup = *
|
|
||||||
rfc1413_hosts = *
|
|
||||||
rfc1413_query_timeout = 0s
|
|
||||||
ignore_bounce_errors_after = 2d
|
|
||||||
timeout_frozen_after = 7d
|
|
||||||
|
|
||||||
DKIM_DOMAIN = ${lc:${domain:$h_from:}}
|
|
||||||
DKIM_FILE = /etc/exim4/domains/${lookup{${lc:${domain:$h_from:}}}dsearch{/etc/exim4/domains/}}/dkim.pem
|
|
||||||
DKIM_PRIVATE_KEY = ${if exists{DKIM_FILE}{DKIM_FILE}{0}}
|
|
||||||
|
|
||||||
OUTGOING_IP = /etc/exim4/domains/${lookup{$sender_address_domain}dsearch{/etc/exim4/domains}}/ip
|
|
||||||
|
|
||||||
SMTP_RELAY_FILE = ${if exists{/etc/exim4/domains/${lookup{$sender_address_domain}dsearch{/etc/exim4/domains}}/smtp_relay.conf}{/etc/exim4/domains/${lookup{$sender_address_domain}dsearch{/etc/exim4/domains}}/smtp_relay.conf}{/etc/exim4/smtp_relay.conf}}
|
|
||||||
SMTP_RELAY_HOST = ${lookup{host}lsearch{SMTP_RELAY_FILE}}
|
|
||||||
SMTP_RELAY_PORT = ${lookup{port}lsearch{SMTP_RELAY_FILE}}
|
|
||||||
SMTP_RELAY_USER = ${lookup{user}lsearch{SMTP_RELAY_FILE}}
|
|
||||||
SMTP_RELAY_PASS = ${lookup{pass}lsearch{SMTP_RELAY_FILE}}
|
|
||||||
|
|
||||||
# Custom Filter
|
|
||||||
system_filter = /etc/exim4/system.filter
|
|
||||||
system_filter_user = Debian-exim
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# ACL CONFIGURATION #
|
|
||||||
# Specifies access control lists for incoming SMTP mail #
|
|
||||||
######################################################################
|
|
||||||
|
|
||||||
acl_not_smtp = acl_not_smtp
|
|
||||||
|
|
||||||
begin acl
|
|
||||||
|
|
||||||
# Limit per user for PHP scripts
|
|
||||||
acl_not_smtp:
|
|
||||||
deny message = Website of user $authenticated_id is sending too many emails - rate overlimit = $sender_rate / $sender_rate_period
|
|
||||||
ratelimit = 200 / 1h / $authenticated_id
|
|
||||||
|
|
||||||
warn ratelimit = 100 / 1h / strict / $authenticated_id
|
|
||||||
log_message = Sender rate [limitlog]: log / account / $authenticated_id / $sender_rate / $sender_rate_period
|
|
||||||
|
|
||||||
accept
|
|
||||||
|
|
||||||
acl_check_spammers:
|
|
||||||
accept hosts = +whitelist
|
|
||||||
|
|
||||||
drop message = Your host in blacklist on this server.
|
|
||||||
log_message = Host in blacklist
|
|
||||||
hosts = +spammers
|
|
||||||
|
|
||||||
accept
|
|
||||||
|
|
||||||
|
|
||||||
acl_check_mail:
|
|
||||||
deny condition = ${if eq{$sender_helo_name}{}}
|
|
||||||
message = HELO required before MAIL
|
|
||||||
|
|
||||||
drop !authenticated = *
|
|
||||||
message = Helo name contains an IP address (HELO was $sender_helo_name) and not is valid
|
|
||||||
condition = ${if match{$sender_helo_name}{\N((\d{1,3}[.-]\d{1,3}[.-]\d{1,3}[.-]\d{1,3})|([0-9a-f]{8})|([0-9A-F]{8}))\N}{yes}{no}}
|
|
||||||
condition = ${if match {${lookup dnsdb{>: defer_never,ptr=$sender_host_address}}\}{$sender_helo_name}{no}{yes}}
|
|
||||||
delay = 45s
|
|
||||||
|
|
||||||
drop !authenticated = *
|
|
||||||
condition = ${if isip{$sender_helo_name}}
|
|
||||||
message = Access denied - Invalid HELO name (See RFC2821 4.1.3)
|
|
||||||
|
|
||||||
drop !authenticated = *
|
|
||||||
condition = ${if eq{[$interface_address]}{$sender_helo_name}}
|
|
||||||
message = $interface_address is _my_ address
|
|
||||||
|
|
||||||
accept
|
|
||||||
|
|
||||||
|
|
||||||
acl_check_rcpt:
|
|
||||||
accept hosts = :
|
|
||||||
|
|
||||||
# Limit per email account for SMTP auhenticated users
|
|
||||||
deny message = Email account $authenticated_id is sending too many emails - rate overlimit = $sender_rate / $sender_rate_period
|
|
||||||
set acl_c_msg_limit = ${if exists{/etc/exim4/domains/${lookup{${domain:$authenticated_id}}dsearch{/etc/exim4/domains/}}/limits}{${lookup {$authenticated_id} lsearch{/etc/exim4/domains/${lookup{${domain:$authenticated_id}}dsearch{/etc/exim4/domains/}}/limits}{$value}{${readfile{/etc/exim4/limit.conf}}}}}{${readfile{/etc/exim4/limit.conf}}} }
|
|
||||||
ratelimit = $acl_c_msg_limit / 1h / strict/ $authenticated_id
|
|
||||||
|
|
||||||
warn ratelimit = ${eval:$acl_c_msg_limit / 2} / 1h / strict / $authenticated_id
|
|
||||||
log_message = Sender rate [limitlog]: log / email / $authenticated_id / $sender_rate / $sender_rate_period
|
|
||||||
|
|
||||||
deny message = Restricted characters in address
|
|
||||||
domains = +local_domains
|
|
||||||
local_parts = ^[.] : ^.*[@%!/|]
|
|
||||||
|
|
||||||
deny message = Restricted characters in address
|
|
||||||
domains = !+local_domains
|
|
||||||
local_parts = ^[./|] : ^.*[@%!] : ^.*/\\.\\./
|
|
||||||
|
|
||||||
require verify = sender
|
|
||||||
|
|
||||||
accept hosts = +relay_from_hosts
|
|
||||||
control = submission
|
|
||||||
|
|
||||||
accept authenticated = *
|
|
||||||
control = submission/domain=
|
|
||||||
|
|
||||||
deny message = Rejected because $sender_host_address is in a black list at $dnslist_domain\n$dnslist_text
|
|
||||||
hosts = !+whitelist
|
|
||||||
dnslists = ${readfile {/etc/exim4/dnsbl.conf}{:}}
|
|
||||||
|
|
||||||
require message = relay not permitted
|
|
||||||
domains = +local_domains : +relay_to_domains
|
|
||||||
|
|
||||||
deny message = smtp auth required
|
|
||||||
sender_domains = +local_domains
|
|
||||||
!authenticated = *
|
|
||||||
|
|
||||||
require verify = recipient
|
|
||||||
|
|
||||||
.ifdef CLAMD
|
|
||||||
warn set acl_m0 = no
|
|
||||||
|
|
||||||
warn condition = ${if exists {/etc/exim4/domains/$domain/antivirus}{yes}{no}}
|
|
||||||
set acl_m0 = yes
|
|
||||||
.endif
|
|
||||||
|
|
||||||
.ifdef SPAMASSASSIN
|
|
||||||
warn set acl_m1 = no
|
|
||||||
set acl_m3 = no
|
|
||||||
warn condition = ${if exists {/etc/exim4/domains/$domain/antispam}{yes}{no}}
|
|
||||||
set acl_m1 = yes
|
|
||||||
warn condition = ${if exists {/etc/exim4/domains/$domain/reject_spam}{yes}{no}}
|
|
||||||
set acl_m3 = yes
|
|
||||||
.endif
|
|
||||||
|
|
||||||
accept
|
|
||||||
|
|
||||||
|
|
||||||
acl_check_data:
|
|
||||||
.ifdef CLAMD
|
|
||||||
deny message = Message contains a virus ($malware_name) and has been rejected
|
|
||||||
malware = */defer_ok
|
|
||||||
condition = ${if eq{$acl_m0}{yes}{yes}{no}}
|
|
||||||
.endif
|
|
||||||
|
|
||||||
.ifdef SPAMASSASSIN
|
|
||||||
warn !authenticated = *
|
|
||||||
hosts = !+relay_from_hosts
|
|
||||||
condition = ${if < {$message_size}{1024K}}
|
|
||||||
condition = ${if eq{$acl_m1}{yes}{yes}{no}}
|
|
||||||
spam = debian-spamd:true/defer_ok
|
|
||||||
add_header = X-Spam-Score: $spam_score_int
|
|
||||||
add_header = X-Spam-Bar: $spam_bar
|
|
||||||
add_header = X-Spam-Report: $spam_report
|
|
||||||
set acl_m2 = $spam_score_int
|
|
||||||
|
|
||||||
warn condition = ${if !eq{$acl_m2}{} {yes}{no}}
|
|
||||||
condition = ${if >{$acl_m2}{SPAM_SCORE} {yes}{no}}
|
|
||||||
add_header = X-Spam-Status: Yes
|
|
||||||
message = SpamAssassin detected spam (from $sender_address to $recipients).
|
|
||||||
|
|
||||||
# Deny spam at high score if spam score > SPAM_REJECT_SCORE and delete_spam is enabled
|
|
||||||
deny message = This message scored $spam_score spam points
|
|
||||||
spam = debian-spamd:true
|
|
||||||
condition = ${if eq{$acl_m3}{yes}{yes}{no}}
|
|
||||||
condition = ${if >{$spam_score_int}{SPAM_REJECT_SCORE}{1}{0}}
|
|
||||||
.endif
|
|
||||||
|
|
||||||
accept
|
|
||||||
|
|
||||||
|
|
||||||
acl_check_mime:
|
|
||||||
deny message = Blacklisted file extension detected
|
|
||||||
condition = ${if match {${lc:$mime_filename}}{\N(\.ace|\.ade|\.adp|\.app|\.arj|\.asp|\.aspx|\.asx|\.bas|\.bat|\.cab|\.cer|\.chm|\.cmd|\.cnt|\.com|\.cpl|\.crt|\.csh|\.der|\.diagcab|\.dll|\.efi|\.exe|\.fla|\.fon|\.fxp|\.gadget|\.grp|\.hlp|\.hpj|\.hta|\.htc|\.img|\.inf|\.ins|\.iso|\.isp|\.its|\.jar|\.jnlp|\.js|\.jse|\.ksh|\.lib|\.lnk|\.mad|\.maf|\.mag|\.mam|\.maq|\.mar|\.mas|\.mat|\.mau|\.mav|\.maw|\.mcf|\.mda|\.mdb|\.mde|\.mdt|\.mdw|\.mdz|\.msc|\.msh|\.msh1|\.msh1xml|\.msh2|\.msh2xml|\.mshxml|\.msi|\.msp|\.mst|\.msu|\.ops|\.osd|\.pcd|\.pif|\.pl|\.plg|\.prf|\.prg|\.printerexport|\.ps1|\.ps1xml|\.ps2|\.ps2xml|\.psc1|\.psc2|\.psd1|\.psdm1|\.pst|\.py|\.pyc|\.pyo|\.pyw|\.pyz|\.pyzw|\.reg|\.scf|\.scr|\.sct|\.sfx|\.shb|\.shs|\.swf|\.sys|\.theme|\.tmp|\.ttf|\.url|\.vb|\.vba|\.vbe|\.vbp|\.vbs|\.vhd|\.vhdx|\.vsmacros|\.vsw|\.vxd|\.webpnp|\.website|\.wim|\.ws|\.wsc|\.wsf|\.wsh|\.xbap|\.xll|\.xnk)$\N}{1}{0}}
|
|
||||||
|
|
||||||
accept
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# AUTHENTICATION CONFIGURATION #
|
|
||||||
######################################################################
|
|
||||||
begin authenticators
|
|
||||||
|
|
||||||
smtp_relay_login:
|
|
||||||
driver = plaintext
|
|
||||||
public_name = LOGIN
|
|
||||||
hide client_send = : SMTP_RELAY_USER : SMTP_RELAY_PASS
|
|
||||||
|
|
||||||
dovecot_plain:
|
|
||||||
driver = dovecot
|
|
||||||
public_name = PLAIN
|
|
||||||
server_socket = /run/dovecot/auth-client
|
|
||||||
server_set_id = $auth1
|
|
||||||
|
|
||||||
dovecot_login:
|
|
||||||
driver = dovecot
|
|
||||||
public_name = LOGIN
|
|
||||||
server_socket = /run/dovecot/auth-client
|
|
||||||
server_set_id = $auth1
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# ROUTERS CONFIGURATION #
|
|
||||||
# Specifies how addresses are handled #
|
|
||||||
######################################################################
|
|
||||||
begin routers
|
|
||||||
|
|
||||||
send_via_unauthenticated_smtp_relay:
|
|
||||||
driver = manualroute
|
|
||||||
address_data = SMTP_RELAY_HOST:SMTP_RELAY_PORT
|
|
||||||
domains = !+local_domains
|
|
||||||
require_files = SMTP_RELAY_FILE
|
|
||||||
condition = ${if eq{SMTP_RELAY_USER}{}}
|
|
||||||
transport = remote_smtp
|
|
||||||
route_list = * ${extract{1}{:}{$address_data}}::${extract{2}{:}{$address_data}}
|
|
||||||
no_more
|
|
||||||
no_verify
|
|
||||||
|
|
||||||
send_via_smtp_relay:
|
|
||||||
driver = manualroute
|
|
||||||
address_data = SMTP_RELAY_HOST:SMTP_RELAY_PORT
|
|
||||||
domains = !+local_domains
|
|
||||||
require_files = SMTP_RELAY_FILE
|
|
||||||
transport = smtp_relay_smtp
|
|
||||||
route_list = * ${extract{1}{:}{$address_data}}::${extract{2}{:}{$address_data}}
|
|
||||||
no_more
|
|
||||||
no_verify
|
|
||||||
|
|
||||||
dnslookup:
|
|
||||||
driver = dnslookup
|
|
||||||
domains = !+local_domains
|
|
||||||
transport = remote_smtp
|
|
||||||
no_more
|
|
||||||
|
|
||||||
userforward:
|
|
||||||
driver = redirect
|
|
||||||
check_local_user
|
|
||||||
file = $home/.forward
|
|
||||||
require_files = ${local_part}:+${home}/.forward
|
|
||||||
domains = +local_domains
|
|
||||||
allow_filter
|
|
||||||
no_verify
|
|
||||||
no_expn
|
|
||||||
check_ancestor
|
|
||||||
file_transport = address_file
|
|
||||||
pipe_transport = address_pipe
|
|
||||||
reply_transport = address_reply
|
|
||||||
|
|
||||||
procmail:
|
|
||||||
driver = accept
|
|
||||||
check_local_user
|
|
||||||
require_files = ${local_part}:+${home}/.procmailrc:/usr/bin/procmail
|
|
||||||
transport = procmail
|
|
||||||
no_verify
|
|
||||||
|
|
||||||
autoreplay:
|
|
||||||
driver = accept
|
|
||||||
require_files = /etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/autoreply.${local_part}.msg
|
|
||||||
condition = ${if exists{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/autoreply.${local_part}.msg}{yes}{no}}
|
|
||||||
retry_use_local_part
|
|
||||||
transport = userautoreply
|
|
||||||
unseen
|
|
||||||
|
|
||||||
aliases:
|
|
||||||
driver = redirect
|
|
||||||
headers_add = X-redirected: yes
|
|
||||||
data = ${extract{1}{:}{${lookup{$local_part@$domain}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/aliases}}}}
|
|
||||||
require_files = /etc/exim4/domains/$domain/aliases
|
|
||||||
redirect_router = dnslookup
|
|
||||||
pipe_transport = address_pipe
|
|
||||||
unseen
|
|
||||||
|
|
||||||
localuser_fwd_only:
|
|
||||||
driver = accept
|
|
||||||
transport = devnull
|
|
||||||
condition = ${if exists{/etc/exim4/domains/$domain/fwd_only}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/fwd_only}{true}{false}}}}
|
|
||||||
|
|
||||||
localuser_spam:
|
|
||||||
driver = accept
|
|
||||||
transport = local_spam_delivery
|
|
||||||
condition = ${if eq {${if match{$h_X-Spam-Status:}{\N^Yes\N}{yes}{no}}} {${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}{yes}{no_such_user}}}}
|
|
||||||
|
|
||||||
localuser:
|
|
||||||
driver = accept
|
|
||||||
transport = local_delivery
|
|
||||||
condition = ${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}{true}{false}}
|
|
||||||
|
|
||||||
catchall:
|
|
||||||
driver = redirect
|
|
||||||
headers_add = X-redirected: yes
|
|
||||||
require_files = /etc/exim4/domains/$domain/aliases
|
|
||||||
data = ${extract{1}{:}{${lookup{*@$domain}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/aliases}}}}
|
|
||||||
file_transport = local_delivery
|
|
||||||
redirect_router = dnslookup
|
|
||||||
condition = ${lookup{$local_part@$domain}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/aliases}{false}{true}}
|
|
||||||
|
|
||||||
terminate_alias:
|
|
||||||
driver = accept
|
|
||||||
transport = devnull
|
|
||||||
condition = ${lookup{$local_part@$domain}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/aliases}{true}{false}}
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# TRANSPORTS CONFIGURATION #
|
|
||||||
######################################################################
|
|
||||||
begin transports
|
|
||||||
|
|
||||||
smtp_relay_smtp:
|
|
||||||
driver = smtp
|
|
||||||
hosts_require_auth = $host_address
|
|
||||||
hosts_require_tls = $host_address
|
|
||||||
|
|
||||||
remote_smtp:
|
|
||||||
driver = smtp
|
|
||||||
helo_data = ${lookup dnsdb{>: defer_never,ptr=$sending_ip_address}{${listextract{1}{$value}}}{$primary_hostname}}
|
|
||||||
dkim_domain = DKIM_DOMAIN
|
|
||||||
dkim_selector = mail
|
|
||||||
dkim_private_key = DKIM_PRIVATE_KEY
|
|
||||||
dkim_canon = relaxed
|
|
||||||
dkim_strict = 0
|
|
||||||
hosts_try_fastopen = !*.l.google.com
|
|
||||||
interface = ${if exists{OUTGOING_IP}{${readfile{OUTGOING_IP}}}}
|
|
||||||
|
|
||||||
procmail:
|
|
||||||
driver = pipe
|
|
||||||
command = "/usr/bin/procmail -d $local_part"
|
|
||||||
return_path_add
|
|
||||||
delivery_date_add
|
|
||||||
envelope_to_add
|
|
||||||
user = $local_part
|
|
||||||
initgroups
|
|
||||||
return_output
|
|
||||||
|
|
||||||
local_delivery:
|
|
||||||
driver = appendfile
|
|
||||||
maildir_format
|
|
||||||
maildir_use_size_file
|
|
||||||
user = ${extract{2}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}
|
|
||||||
group = mail
|
|
||||||
create_directory
|
|
||||||
directory_mode = 770
|
|
||||||
mode = 660
|
|
||||||
use_lockfile = no
|
|
||||||
delivery_date_add
|
|
||||||
envelope_to_add
|
|
||||||
return_path_add
|
|
||||||
directory = "${extract{5}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}/mail/${lookup{$domain}dsearch{/etc/exim4/domains/}}/${lookup{$local_part}dsearch{${extract{5}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}/mail/${lookup{$domain}dsearch{/etc/exim4/domains/}}}}"
|
|
||||||
quota = ${extract{6}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}M
|
|
||||||
quota_warn_threshold = 75%
|
|
||||||
|
|
||||||
local_spam_delivery:
|
|
||||||
driver = appendfile
|
|
||||||
maildir_format
|
|
||||||
maildir_use_size_file
|
|
||||||
user = ${extract{2}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}
|
|
||||||
group = mail
|
|
||||||
create_directory
|
|
||||||
directory_mode = 770
|
|
||||||
mode = 660
|
|
||||||
use_lockfile = no
|
|
||||||
delivery_date_add
|
|
||||||
envelope_to_add
|
|
||||||
return_path_add
|
|
||||||
directory = "${extract{5}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}/mail/${lookup{$domain}dsearch{/etc/exim4/domains/}}/${lookup{$local_part}dsearch{${extract{5}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}/mail/${lookup{$domain}dsearch{/etc/exim4/domains/}}}}/.Spam"
|
|
||||||
quota = ${extract{6}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}M
|
|
||||||
quota_directory = "${extract{5}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}/mail/${lookup{$domain}dsearch{/etc/exim4/domains/}}/${lookup{$local_part}dsearch{${extract{5}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}/mail/${lookup{$domain}dsearch{/etc/exim4/domains/}}}}"
|
|
||||||
quota_warn_threshold = 75%
|
|
||||||
|
|
||||||
address_pipe:
|
|
||||||
driver = pipe
|
|
||||||
return_output
|
|
||||||
|
|
||||||
address_file:
|
|
||||||
driver = appendfile
|
|
||||||
delivery_date_add
|
|
||||||
envelope_to_add
|
|
||||||
return_path_add
|
|
||||||
|
|
||||||
address_reply:
|
|
||||||
driver = autoreply
|
|
||||||
|
|
||||||
userautoreply:
|
|
||||||
driver = autoreply
|
|
||||||
file = /etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/autoreply.${extract{1}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/accounts}}}}.msg
|
|
||||||
from = "${extract{1}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/accounts}}}}@${lookup{$domain}dsearch{/etc/exim4/domains/}}"
|
|
||||||
headers = Content-Type: text/plain; charset=utf-8;\nContent-Transfer-Encoding: 8bit
|
|
||||||
subject = "${if def:h_Subject: {Autoreply: \"${rfc2047:$h_Subject:}\"} {Autoreply Message}}"
|
|
||||||
to = "${sender_address}"
|
|
||||||
|
|
||||||
devnull:
|
|
||||||
driver = appendfile
|
|
||||||
file = /dev/null
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# RETRY CONFIGURATION #
|
|
||||||
######################################################################
|
|
||||||
begin retry
|
|
||||||
|
|
||||||
# Address or Domain Error Retries
|
|
||||||
# ----------------- ----- -------
|
|
||||||
* * F,2h,15m; G,16h,1h,1.5; F,4d,6h
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# REWRITE CONFIGURATION #
|
|
||||||
######################################################################
|
|
||||||
begin rewrite
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
@@ -1,512 +0,0 @@
|
|||||||
######################################################################
|
|
||||||
# #
|
|
||||||
# Exim configuration file for Hestia Control Panel #
|
|
||||||
# #
|
|
||||||
######################################################################
|
|
||||||
|
|
||||||
#SPAMASSASSIN = yes
|
|
||||||
#SPAM_SCORE = 50
|
|
||||||
#SPAM_REJECT_SCORE = 100
|
|
||||||
#CLAMD = yes
|
|
||||||
|
|
||||||
smtp_banner = $smtp_active_hostname
|
|
||||||
smtp_active_hostname = ${lookup dnsdb{>: defer_never,ptr=$interface_address}{${listextract{1}{$value}}}{$primary_hostname}}
|
|
||||||
add_environment = <; PATH=/bin:/usr/bin
|
|
||||||
keep_environment =
|
|
||||||
disable_ipv6 = true
|
|
||||||
|
|
||||||
SRS_SECRET = ${readfile{/etc/exim4/srs.conf}}
|
|
||||||
|
|
||||||
smtputf8_advertise_hosts =
|
|
||||||
domainlist local_domains = dsearch;/etc/exim4/domains/
|
|
||||||
domainlist relay_to_domains = dsearch;/etc/exim4/domains/
|
|
||||||
hostlist relay_from_hosts = 127.0.0.1
|
|
||||||
hostlist whitelist = net-iplsearch;/etc/exim4/white-blocks.conf
|
|
||||||
hostlist spammers = net-iplsearch;/etc/exim4/spam-blocks.conf
|
|
||||||
no_local_from_check
|
|
||||||
untrusted_set_sender = *
|
|
||||||
acl_smtp_connect = acl_check_spammers
|
|
||||||
acl_smtp_mail = acl_check_mail
|
|
||||||
acl_smtp_rcpt = acl_check_rcpt
|
|
||||||
acl_smtp_data = acl_check_data
|
|
||||||
acl_smtp_mime = acl_check_mime
|
|
||||||
|
|
||||||
.ifdef SPAMASSASSIN
|
|
||||||
spamd_address = 127.0.0.1 783
|
|
||||||
.endif
|
|
||||||
|
|
||||||
.ifdef CLAMD
|
|
||||||
av_scanner = clamd: /run/clamav/clamd.ctl
|
|
||||||
.endif
|
|
||||||
|
|
||||||
log_selector = +tls_sni
|
|
||||||
|
|
||||||
tls_advertise_hosts = *
|
|
||||||
|
|
||||||
# We test that $tls_in_sni is a valid domain, by an arbitrary email address foo@domain.tld .
|
|
||||||
# Then, we extract the domain with a function that would fail if the email address is invalid.
|
|
||||||
# If the certificate exists, we will use it, otherwise the default certificate in /etc/ssl will be used.
|
|
||||||
tls_certificate = \
|
|
||||||
${if and {\
|
|
||||||
{ eq {${domain:foo@$tls_in_sni}} {$tls_in_sni}}\
|
|
||||||
{ exists{/usr/local/hestia/ssl/mail/$tls_in_sni.crt} }\
|
|
||||||
}\
|
|
||||||
{/usr/local/hestia/ssl/mail/$tls_in_sni.crt}\
|
|
||||||
{/usr/local/hestia/ssl/certificate.crt}\
|
|
||||||
}
|
|
||||||
|
|
||||||
tls_privatekey = \
|
|
||||||
${if and {\
|
|
||||||
{ eq {${domain:foo@$tls_in_sni}} {$tls_in_sni}}\
|
|
||||||
{ exists{/usr/local/hestia/ssl/mail/$tls_in_sni.key} }\
|
|
||||||
}\
|
|
||||||
{/usr/local/hestia/ssl/mail/$tls_in_sni.key}\
|
|
||||||
{/usr/local/hestia/ssl/certificate.key}\
|
|
||||||
}
|
|
||||||
|
|
||||||
daemon_smtp_ports = 25 : 465 : 587
|
|
||||||
tls_on_connect_ports = 465
|
|
||||||
tls_require_ciphers = PERFORMANCE:-RSA:-VERS-ALL:+VERS-TLS1.2:+VERS-TLS1.3:%SERVER_PRECEDENCE
|
|
||||||
never_users = root
|
|
||||||
host_lookup = *
|
|
||||||
rfc1413_hosts = *
|
|
||||||
rfc1413_query_timeout = 0s
|
|
||||||
ignore_bounce_errors_after = 2d
|
|
||||||
timeout_frozen_after = 7d
|
|
||||||
|
|
||||||
DKIM_DOMAIN = ${lc:${domain:$h_from:}}
|
|
||||||
DKIM_FILE = /etc/exim4/domains/${lookup{${lc:${domain:$h_from:}}}dsearch{/etc/exim4/domains/}}/dkim.pem
|
|
||||||
DKIM_PRIVATE_KEY = ${if exists{DKIM_FILE}{DKIM_FILE}{0}}
|
|
||||||
|
|
||||||
OUTGOING_IP = /etc/exim4/domains/${lookup{$sender_address_domain}dsearch{/etc/exim4/domains}}/ip
|
|
||||||
|
|
||||||
SMTP_RELAY_FILE = ${if exists{/etc/exim4/domains/${lookup{$sender_address_domain}dsearch{/etc/exim4/domains}}/smtp_relay.conf}{/etc/exim4/domains/${lookup{$sender_address_domain}dsearch{/etc/exim4/domains}}/smtp_relay.conf}{/etc/exim4/smtp_relay.conf}}
|
|
||||||
SMTP_RELAY_HOST = ${lookup{host}lsearch{SMTP_RELAY_FILE}}
|
|
||||||
SMTP_RELAY_PORT = ${lookup{port}lsearch{SMTP_RELAY_FILE}}
|
|
||||||
SMTP_RELAY_USER = ${lookup{user}lsearch{SMTP_RELAY_FILE}}
|
|
||||||
SMTP_RELAY_PASS = ${lookup{pass}lsearch{SMTP_RELAY_FILE}}
|
|
||||||
|
|
||||||
# Custom Filter
|
|
||||||
system_filter = /etc/exim4/system.filter
|
|
||||||
system_filter_user = Debian-exim
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# ACL CONFIGURATION #
|
|
||||||
# Specifies access control lists for incoming SMTP mail #
|
|
||||||
######################################################################
|
|
||||||
|
|
||||||
acl_not_smtp = acl_not_smtp
|
|
||||||
|
|
||||||
begin acl
|
|
||||||
|
|
||||||
# Limit per user for PHP scripts
|
|
||||||
acl_not_smtp:
|
|
||||||
deny message = Website of user $authenticated_id is sending too many emails - rate overlimit = $sender_rate / $sender_rate_period
|
|
||||||
ratelimit = 200 / 1h / $authenticated_id
|
|
||||||
|
|
||||||
warn ratelimit = 100 / 1h / strict / $authenticated_id
|
|
||||||
log_message = Sender rate [limitlog]: log / account / $authenticated_id / $sender_rate / $sender_rate_period
|
|
||||||
|
|
||||||
accept
|
|
||||||
|
|
||||||
acl_check_spammers:
|
|
||||||
accept hosts = +whitelist
|
|
||||||
|
|
||||||
drop message = Your host in blacklist on this server.
|
|
||||||
log_message = Host in blacklist
|
|
||||||
hosts = +spammers
|
|
||||||
|
|
||||||
accept
|
|
||||||
|
|
||||||
|
|
||||||
acl_check_mail:
|
|
||||||
deny condition = ${if eq{$sender_helo_name}{}}
|
|
||||||
message = HELO required before MAIL
|
|
||||||
|
|
||||||
drop !authenticated = *
|
|
||||||
message = Helo name contains an IP address (HELO was $sender_helo_name) and not is valid
|
|
||||||
condition = ${if match{$sender_helo_name}{\N((\d{1,3}[.-]\d{1,3}[.-]\d{1,3}[.-]\d{1,3})|([0-9a-f]{8})|([0-9A-F]{8}))\N}{yes}{no}}
|
|
||||||
condition = ${if match {${lookup dnsdb{>: defer_never,ptr=$sender_host_address}}\}{$sender_helo_name}{no}{yes}}
|
|
||||||
delay = 45s
|
|
||||||
|
|
||||||
drop !authenticated = *
|
|
||||||
condition = ${if isip{$sender_helo_name}}
|
|
||||||
message = Access denied - Invalid HELO name (See RFC2821 4.1.3)
|
|
||||||
|
|
||||||
drop !authenticated = *
|
|
||||||
condition = ${if eq{[$interface_address]}{$sender_helo_name}}
|
|
||||||
message = $interface_address is _my_ address
|
|
||||||
|
|
||||||
accept
|
|
||||||
|
|
||||||
|
|
||||||
acl_check_rcpt:
|
|
||||||
accept hosts = :
|
|
||||||
|
|
||||||
# Limit per email account for SMTP auhenticated users
|
|
||||||
deny message = Email account $authenticated_id is sending too many emails - rate overlimit = $sender_rate / $sender_rate_period
|
|
||||||
set acl_c_msg_limit = ${if exists{/etc/exim4/domains/${lookup{${domain:$authenticated_id}}dsearch{/etc/exim4/domains/}}/limits}{${lookup {$authenticated_id} lsearch{/etc/exim4/domains/${lookup{${domain:$authenticated_id}}dsearch{/etc/exim4/domains/}}/limits}{$value}{${readfile{/etc/exim4/limit.conf}}}}}{${readfile{/etc/exim4/limit.conf}}} }
|
|
||||||
ratelimit = $acl_c_msg_limit / 1h / strict/ $authenticated_id
|
|
||||||
|
|
||||||
warn ratelimit = ${eval:$acl_c_msg_limit / 2} / 1h / strict / $authenticated_id
|
|
||||||
log_message = Sender rate [limitlog]: log / email / $authenticated_id / $sender_rate / $sender_rate_period
|
|
||||||
|
|
||||||
deny message = Restricted characters in address
|
|
||||||
domains = +local_domains
|
|
||||||
local_parts = ^[.] : ^.*[@%!/|]
|
|
||||||
|
|
||||||
deny message = Restricted characters in address
|
|
||||||
domains = !+local_domains
|
|
||||||
local_parts = ^[./|] : ^.*[@%!] : ^.*/\\.\\./
|
|
||||||
|
|
||||||
require verify = sender
|
|
||||||
|
|
||||||
accept hosts = +relay_from_hosts
|
|
||||||
control = submission
|
|
||||||
|
|
||||||
accept authenticated = *
|
|
||||||
control = submission/domain=
|
|
||||||
|
|
||||||
deny message = Rejected because $sender_host_address is in a black list at $dnslist_domain\n$dnslist_text
|
|
||||||
hosts = !+whitelist
|
|
||||||
dnslists = ${readfile {/etc/exim4/dnsbl.conf}{:}}
|
|
||||||
|
|
||||||
require message = relay not permitted
|
|
||||||
domains = +local_domains : +relay_to_domains
|
|
||||||
|
|
||||||
deny message = smtp auth required
|
|
||||||
sender_domains = +local_domains
|
|
||||||
!authenticated = *
|
|
||||||
|
|
||||||
require verify = recipient
|
|
||||||
|
|
||||||
.ifdef CLAMD
|
|
||||||
warn set acl_m0 = no
|
|
||||||
|
|
||||||
warn condition = ${if exists {/etc/exim4/domains/$domain/antivirus}{yes}{no}}
|
|
||||||
set acl_m0 = yes
|
|
||||||
.endif
|
|
||||||
|
|
||||||
.ifdef SPAMASSASSIN
|
|
||||||
warn set acl_m1 = no
|
|
||||||
set acl_m3 = no
|
|
||||||
warn condition = ${if exists {/etc/exim4/domains/$domain/antispam}{yes}{no}}
|
|
||||||
set acl_m1 = yes
|
|
||||||
warn condition = ${if exists {/etc/exim4/domains/$domain/reject_spam}{yes}{no}}
|
|
||||||
set acl_m3 = yes
|
|
||||||
.endif
|
|
||||||
|
|
||||||
accept
|
|
||||||
|
|
||||||
|
|
||||||
acl_check_data:
|
|
||||||
.ifdef CLAMD
|
|
||||||
deny message = Message contains a virus ($malware_name) and has been rejected
|
|
||||||
malware = */defer_ok
|
|
||||||
condition = ${if eq{$acl_m0}{yes}{yes}{no}}
|
|
||||||
.endif
|
|
||||||
|
|
||||||
.ifdef SPAMASSASSIN
|
|
||||||
warn !authenticated = *
|
|
||||||
hosts = !+relay_from_hosts
|
|
||||||
condition = ${if < {$message_size}{1024K}}
|
|
||||||
condition = ${if eq{$acl_m1}{yes}{yes}{no}}
|
|
||||||
spam = debian-spamd:true/defer_ok
|
|
||||||
add_header = X-Spam-Score: $spam_score_int
|
|
||||||
add_header = X-Spam-Bar: $spam_bar
|
|
||||||
add_header = X-Spam-Report: $spam_report
|
|
||||||
set acl_m2 = $spam_score_int
|
|
||||||
|
|
||||||
warn condition = ${if !eq{$acl_m2}{} {yes}{no}}
|
|
||||||
condition = ${if >{$acl_m2}{SPAM_SCORE} {yes}{no}}
|
|
||||||
add_header = X-Spam-Status: Yes
|
|
||||||
message = SpamAssassin detected spam (from $sender_address to $recipients).
|
|
||||||
|
|
||||||
# Deny spam at high score if spam score > SPAM_REJECT_SCORE and delete_spam is enabled
|
|
||||||
deny message = This message scored $spam_score spam points
|
|
||||||
spam = debian-spamd:true
|
|
||||||
condition = ${if eq{$acl_m3}{yes}{yes}{no}}
|
|
||||||
condition = ${if >{$spam_score_int}{SPAM_REJECT_SCORE}{1}{0}}
|
|
||||||
.endif
|
|
||||||
|
|
||||||
accept
|
|
||||||
|
|
||||||
|
|
||||||
acl_check_mime:
|
|
||||||
deny message = Blacklisted file extension detected
|
|
||||||
condition = ${if match {${lc:$mime_filename}}{\N(\.ace|\.ade|\.adp|\.app|\.arj|\.asp|\.aspx|\.asx|\.bas|\.bat|\.cab|\.cer|\.chm|\.cmd|\.cnt|\.com|\.cpl|\.crt|\.csh|\.der|\.diagcab|\.dll|\.efi|\.exe|\.fla|\.fon|\.fxp|\.gadget|\.grp|\.hlp|\.hpj|\.hta|\.htc|\.img|\.inf|\.ins|\.iso|\.isp|\.its|\.jar|\.jnlp|\.js|\.jse|\.ksh|\.lib|\.lnk|\.mad|\.maf|\.mag|\.mam|\.maq|\.mar|\.mas|\.mat|\.mau|\.mav|\.maw|\.mcf|\.mda|\.mdb|\.mde|\.mdt|\.mdw|\.mdz|\.msc|\.msh|\.msh1|\.msh1xml|\.msh2|\.msh2xml|\.mshxml|\.msi|\.msp|\.mst|\.msu|\.ops|\.osd|\.pcd|\.pif|\.pl|\.plg|\.prf|\.prg|\.printerexport|\.ps1|\.ps1xml|\.ps2|\.ps2xml|\.psc1|\.psc2|\.psd1|\.psdm1|\.pst|\.py|\.pyc|\.pyo|\.pyw|\.pyz|\.pyzw|\.reg|\.scf|\.scr|\.sct|\.sfx|\.shb|\.shs|\.swf|\.sys|\.theme|\.tmp|\.ttf|\.url|\.vb|\.vba|\.vbe|\.vbp|\.vbs|\.vhd|\.vhdx|\.vsmacros|\.vsw|\.vxd|\.webpnp|\.website|\.wim|\.ws|\.wsc|\.wsf|\.wsh|\.xbap|\.xll|\.xnk)$\N}{1}{0}}
|
|
||||||
|
|
||||||
accept
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# AUTHENTICATION CONFIGURATION #
|
|
||||||
######################################################################
|
|
||||||
begin authenticators
|
|
||||||
|
|
||||||
smtp_relay_login:
|
|
||||||
driver = plaintext
|
|
||||||
public_name = LOGIN
|
|
||||||
hide client_send = : SMTP_RELAY_USER : SMTP_RELAY_PASS
|
|
||||||
|
|
||||||
dovecot_plain:
|
|
||||||
driver = dovecot
|
|
||||||
public_name = PLAIN
|
|
||||||
server_socket = /run/dovecot/auth-client
|
|
||||||
server_set_id = $auth1
|
|
||||||
|
|
||||||
dovecot_login:
|
|
||||||
driver = dovecot
|
|
||||||
public_name = LOGIN
|
|
||||||
server_socket = /run/dovecot/auth-client
|
|
||||||
server_set_id = $auth1
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# ROUTERS CONFIGURATION #
|
|
||||||
# Specifies how addresses are handled #
|
|
||||||
######################################################################
|
|
||||||
begin routers
|
|
||||||
|
|
||||||
send_via_unauthenticated_smtp_relay:
|
|
||||||
driver = manualroute
|
|
||||||
address_data = SMTP_RELAY_HOST:SMTP_RELAY_PORT
|
|
||||||
domains = !+local_domains
|
|
||||||
require_files = SMTP_RELAY_FILE
|
|
||||||
condition = ${if eq{SMTP_RELAY_USER}{}}
|
|
||||||
transport = remote_smtp
|
|
||||||
route_list = * ${extract{1}{:}{$address_data}}::${extract{2}{:}{$address_data}}
|
|
||||||
no_more
|
|
||||||
no_verify
|
|
||||||
|
|
||||||
send_via_smtp_relay:
|
|
||||||
driver = manualroute
|
|
||||||
address_data = SMTP_RELAY_HOST:SMTP_RELAY_PORT
|
|
||||||
domains = !+local_domains
|
|
||||||
require_files = SMTP_RELAY_FILE
|
|
||||||
transport = smtp_relay_smtp
|
|
||||||
route_list = * ${extract{1}{:}{$address_data}}::${extract{2}{:}{$address_data}}
|
|
||||||
no_more
|
|
||||||
no_verify
|
|
||||||
|
|
||||||
dnslookup:
|
|
||||||
driver = dnslookup
|
|
||||||
# if outbound, and forwarding has been done, use an alternate transport
|
|
||||||
domains = ! +local_domains
|
|
||||||
transport = ${if eq {$local_part@$domain} \
|
|
||||||
{$original_local_part@$original_domain} \
|
|
||||||
{remote_smtp} {remote_forwarded_smtp}}
|
|
||||||
no_more
|
|
||||||
|
|
||||||
userforward:
|
|
||||||
driver = redirect
|
|
||||||
check_local_user
|
|
||||||
file = $home/.forward
|
|
||||||
require_files = ${local_part}:+${home}/.forward
|
|
||||||
domains = +local_domains
|
|
||||||
allow_filter
|
|
||||||
no_verify
|
|
||||||
no_expn
|
|
||||||
check_ancestor
|
|
||||||
file_transport = address_file
|
|
||||||
pipe_transport = address_pipe
|
|
||||||
reply_transport = address_reply
|
|
||||||
|
|
||||||
procmail:
|
|
||||||
driver = accept
|
|
||||||
check_local_user
|
|
||||||
require_files = ${local_part}:+${home}/.procmailrc:/usr/bin/procmail
|
|
||||||
transport = procmail
|
|
||||||
no_verify
|
|
||||||
|
|
||||||
autoreplay:
|
|
||||||
driver = accept
|
|
||||||
require_files = /etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/autoreply.${local_part}.msg
|
|
||||||
condition = ${if exists{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/autoreply.${local_part}.msg}{yes}{no}}
|
|
||||||
retry_use_local_part
|
|
||||||
transport = userautoreply
|
|
||||||
unseen
|
|
||||||
|
|
||||||
inbound_srs:
|
|
||||||
driver = redirect
|
|
||||||
senders = :
|
|
||||||
domains = +local_domains
|
|
||||||
# detect inbound bounces which are SRS'd, and decode them
|
|
||||||
condition = ${if inbound_srs {$local_part} {SRS_SECRET}}
|
|
||||||
data = $srs_recipient
|
|
||||||
|
|
||||||
inbound_srs_failure:
|
|
||||||
driver = redirect
|
|
||||||
senders = :
|
|
||||||
domains = +local_domains
|
|
||||||
# detect inbound bounces which look SRS'd but are invalid
|
|
||||||
condition = ${if inbound_srs {$local_part} {}}
|
|
||||||
allow_fail
|
|
||||||
data = :fail: Invalid SRS recipient address
|
|
||||||
|
|
||||||
aliases:
|
|
||||||
driver = redirect
|
|
||||||
headers_add = X-redirected: yes
|
|
||||||
data = ${extract{1}{:}{${lookup{$local_part@$domain}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/aliases}}}}
|
|
||||||
require_files = /etc/exim4/domains/$domain/aliases
|
|
||||||
redirect_router = dnslookup
|
|
||||||
pipe_transport = address_pipe
|
|
||||||
unseen
|
|
||||||
|
|
||||||
localuser_fwd_only:
|
|
||||||
driver = accept
|
|
||||||
transport = devnull
|
|
||||||
condition = ${if exists{/etc/exim4/domains/$domain/fwd_only}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/fwd_only}{true}{false}}}}
|
|
||||||
|
|
||||||
localuser_spam:
|
|
||||||
driver = accept
|
|
||||||
transport = local_spam_delivery
|
|
||||||
condition = ${if eq {${if match{$h_X-Spam-Status:}{\N^Yes\N}{yes}{no}}} {${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}{yes}{no_such_user}}}}
|
|
||||||
|
|
||||||
localuser:
|
|
||||||
driver = accept
|
|
||||||
transport = local_delivery
|
|
||||||
condition = ${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}{true}{false}}
|
|
||||||
|
|
||||||
catchall:
|
|
||||||
driver = redirect
|
|
||||||
headers_add = X-redirected: yes
|
|
||||||
require_files = /etc/exim4/domains/$domain/aliases
|
|
||||||
data = ${extract{1}{:}{${lookup{*@$domain}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/aliases}}}}
|
|
||||||
file_transport = local_delivery
|
|
||||||
redirect_router = dnslookup
|
|
||||||
condition = ${lookup{$local_part@$domain}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/aliases}{false}{true}}
|
|
||||||
|
|
||||||
terminate_alias:
|
|
||||||
driver = accept
|
|
||||||
transport = devnull
|
|
||||||
condition = ${lookup{$local_part@$domain}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/aliases}{true}{false}}
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# TRANSPORTS CONFIGURATION #
|
|
||||||
######################################################################
|
|
||||||
begin transports
|
|
||||||
|
|
||||||
smtp_relay_smtp:
|
|
||||||
driver = smtp
|
|
||||||
hosts_require_auth = $host_address
|
|
||||||
hosts_require_tls = $host_address
|
|
||||||
|
|
||||||
remote_smtp:
|
|
||||||
driver = smtp
|
|
||||||
helo_data = ${lookup dnsdb{>: defer_never,ptr=$sending_ip_address}{${listextract{1}{$value}}}{$primary_hostname}}
|
|
||||||
dkim_domain = DKIM_DOMAIN
|
|
||||||
dkim_selector = mail
|
|
||||||
dkim_private_key = DKIM_PRIVATE_KEY
|
|
||||||
dkim_canon = relaxed
|
|
||||||
dkim_strict = 0
|
|
||||||
hosts_try_fastopen = !*.l.google.com
|
|
||||||
interface = ${if exists{OUTGOING_IP}{${readfile{OUTGOING_IP}}}}
|
|
||||||
|
|
||||||
remote_forwarded_smtp:
|
|
||||||
driver = smtp
|
|
||||||
helo_data = ${lookup dnsdb{>: defer_never,ptr=$sending_ip_address}{${listextract{1}{$value}}}{$primary_hostname}}
|
|
||||||
dkim_domain = DKIM_DOMAIN
|
|
||||||
dkim_selector = mail
|
|
||||||
dkim_private_key = DKIM_PRIVATE_KEY
|
|
||||||
dkim_canon = relaxed
|
|
||||||
dkim_strict = 0
|
|
||||||
hosts_try_fastopen = !*.l.google.com
|
|
||||||
interface = ${if exists{OUTGOING_IP}{${readfile{OUTGOING_IP}}}}
|
|
||||||
# modify the envelope from, for mails that we forward
|
|
||||||
max_rcpt = 1
|
|
||||||
return_path = ${srs_encode {SRS_SECRET} {$return_path} {$original_domain}}
|
|
||||||
|
|
||||||
procmail:
|
|
||||||
driver = pipe
|
|
||||||
command = "/usr/bin/procmail -d $local_part"
|
|
||||||
return_path_add
|
|
||||||
delivery_date_add
|
|
||||||
envelope_to_add
|
|
||||||
user = $local_part
|
|
||||||
initgroups
|
|
||||||
return_output
|
|
||||||
|
|
||||||
local_delivery:
|
|
||||||
driver = appendfile
|
|
||||||
maildir_format
|
|
||||||
maildir_use_size_file
|
|
||||||
user = ${extract{2}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}
|
|
||||||
group = mail
|
|
||||||
create_directory
|
|
||||||
directory_mode = 770
|
|
||||||
mode = 660
|
|
||||||
use_lockfile = no
|
|
||||||
delivery_date_add
|
|
||||||
envelope_to_add
|
|
||||||
return_path_add
|
|
||||||
directory = "${extract{5}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}/mail/${lookup{$domain}dsearch{/etc/exim4/domains/}}/${lookup{$local_part}dsearch{${extract{5}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}/mail/${lookup{$domain}dsearch{/etc/exim4/domains/}}}}"
|
|
||||||
quota = ${extract{6}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}M
|
|
||||||
quota_warn_threshold = 75%
|
|
||||||
|
|
||||||
local_spam_delivery:
|
|
||||||
driver = appendfile
|
|
||||||
maildir_format
|
|
||||||
maildir_use_size_file
|
|
||||||
user = ${extract{2}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}
|
|
||||||
group = mail
|
|
||||||
create_directory
|
|
||||||
directory_mode = 770
|
|
||||||
mode = 660
|
|
||||||
use_lockfile = no
|
|
||||||
delivery_date_add
|
|
||||||
envelope_to_add
|
|
||||||
return_path_add
|
|
||||||
directory = "${extract{5}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}/mail/${lookup{$domain}dsearch{/etc/exim4/domains/}}/${lookup{$local_part}dsearch{${extract{5}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}/mail/${lookup{$domain}dsearch{/etc/exim4/domains/}}}}/.Spam"
|
|
||||||
quota = ${extract{6}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}M
|
|
||||||
quota_directory = "${extract{5}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}/mail/${lookup{$domain}dsearch{/etc/exim4/domains/}}/${lookup{$local_part}dsearch{${extract{5}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}/mail/${lookup{$domain}dsearch{/etc/exim4/domains/}}}}"
|
|
||||||
quota_warn_threshold = 75%
|
|
||||||
|
|
||||||
address_pipe:
|
|
||||||
driver = pipe
|
|
||||||
return_output
|
|
||||||
|
|
||||||
address_file:
|
|
||||||
driver = appendfile
|
|
||||||
delivery_date_add
|
|
||||||
envelope_to_add
|
|
||||||
return_path_add
|
|
||||||
|
|
||||||
address_reply:
|
|
||||||
driver = autoreply
|
|
||||||
|
|
||||||
userautoreply:
|
|
||||||
driver = autoreply
|
|
||||||
file = /etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/autoreply.${extract{1}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/accounts}}}}.msg
|
|
||||||
from = "${extract{1}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/accounts}}}}@${lookup{$domain}dsearch{/etc/exim4/domains/}}"
|
|
||||||
headers = Content-Type: text/plain; charset=utf-8;\nContent-Transfer-Encoding: 8bit
|
|
||||||
subject = "${if def:h_Subject: {Autoreply: \"${rfc2047:$h_Subject:}\"} {Autoreply Message}}"
|
|
||||||
to = "${sender_address}"
|
|
||||||
|
|
||||||
devnull:
|
|
||||||
driver = appendfile
|
|
||||||
file = /dev/null
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# RETRY CONFIGURATION #
|
|
||||||
######################################################################
|
|
||||||
begin retry
|
|
||||||
|
|
||||||
# Address or Domain Error Retries
|
|
||||||
# ----------------- ----- -------
|
|
||||||
* * F,2h,15m; G,16h,1h,1.5; F,4d,6h
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# REWRITE CONFIGURATION #
|
|
||||||
######################################################################
|
|
||||||
begin rewrite
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
@@ -1,478 +0,0 @@
|
|||||||
######################################################################
|
|
||||||
# #
|
|
||||||
# Exim configuration file for Hestia Control Panel #
|
|
||||||
# #
|
|
||||||
######################################################################
|
|
||||||
|
|
||||||
#SPAMASSASSIN = yes
|
|
||||||
#SPAM_SCORE = 50
|
|
||||||
#SPAM_REJECT_SCORE = 100
|
|
||||||
#CLAMD = yes
|
|
||||||
|
|
||||||
smtp_banner = $smtp_active_hostname
|
|
||||||
smtp_active_hostname = ${lookup dnsdb{>: defer_never,ptr=$interface_address}{${listextract{1}{$value}}}{$primary_hostname}}
|
|
||||||
add_environment = <; PATH=/bin:/usr/bin
|
|
||||||
keep_environment =
|
|
||||||
disable_ipv6 = true
|
|
||||||
|
|
||||||
smtputf8_advertise_hosts =
|
|
||||||
domainlist local_domains = dsearch;/etc/exim4/domains/
|
|
||||||
domainlist relay_to_domains = dsearch;/etc/exim4/domains/
|
|
||||||
hostlist relay_from_hosts = 127.0.0.1
|
|
||||||
hostlist whitelist = net-iplsearch;/etc/exim4/white-blocks.conf
|
|
||||||
hostlist spammers = net-iplsearch;/etc/exim4/spam-blocks.conf
|
|
||||||
no_local_from_check
|
|
||||||
untrusted_set_sender = *
|
|
||||||
acl_smtp_connect = acl_check_spammers
|
|
||||||
acl_smtp_mail = acl_check_mail
|
|
||||||
acl_smtp_rcpt = acl_check_rcpt
|
|
||||||
acl_smtp_data = acl_check_data
|
|
||||||
acl_smtp_mime = acl_check_mime
|
|
||||||
|
|
||||||
.ifdef SPAMASSASSIN
|
|
||||||
spamd_address = 127.0.0.1 783
|
|
||||||
.endif
|
|
||||||
|
|
||||||
.ifdef CLAMD
|
|
||||||
av_scanner = clamd: /run/clamav/clamd.ctl
|
|
||||||
.endif
|
|
||||||
|
|
||||||
log_selector = +tls_sni
|
|
||||||
|
|
||||||
tls_advertise_hosts = *
|
|
||||||
|
|
||||||
# We test that $tls_in_sni is a valid domain, by an arbitrary email address foo@domain.tld .
|
|
||||||
# Then, we extract the domain with a function that would fail if the email address is invalid.
|
|
||||||
# If the certificate exists, we will use it, otherwise the default certificate in /etc/ssl will be used.
|
|
||||||
tls_certificate = \
|
|
||||||
${if and {\
|
|
||||||
{ eq {${domain:foo@$tls_in_sni}} {$tls_in_sni}}\
|
|
||||||
{ exists{/usr/local/hestia/ssl/mail/$tls_in_sni.crt} }\
|
|
||||||
}\
|
|
||||||
{/usr/local/hestia/ssl/mail/$tls_in_sni.crt}\
|
|
||||||
{/usr/local/hestia/ssl/certificate.crt}\
|
|
||||||
}
|
|
||||||
|
|
||||||
tls_privatekey = \
|
|
||||||
${if and {\
|
|
||||||
{ eq {${domain:foo@$tls_in_sni}} {$tls_in_sni}}\
|
|
||||||
{ exists{/usr/local/hestia/ssl/mail/$tls_in_sni.key} }\
|
|
||||||
}\
|
|
||||||
{/usr/local/hestia/ssl/mail/$tls_in_sni.key}\
|
|
||||||
{/usr/local/hestia/ssl/certificate.key}\
|
|
||||||
}
|
|
||||||
|
|
||||||
daemon_smtp_ports = 25 : 465 : 587
|
|
||||||
tls_on_connect_ports = 465
|
|
||||||
tls_require_ciphers = PERFORMANCE:-RSA:-VERS-ALL:+VERS-TLS1.2:+VERS-TLS1.3:%SERVER_PRECEDENCE
|
|
||||||
never_users = root
|
|
||||||
host_lookup = *
|
|
||||||
rfc1413_hosts = *
|
|
||||||
rfc1413_query_timeout = 0s
|
|
||||||
ignore_bounce_errors_after = 2d
|
|
||||||
timeout_frozen_after = 7d
|
|
||||||
|
|
||||||
DKIM_DOMAIN = ${lc:${domain:$h_from:}}
|
|
||||||
DKIM_FILE = /etc/exim4/domains/${lc:${domain:$h_from:}}/dkim.pem
|
|
||||||
DKIM_PRIVATE_KEY = ${if exists{DKIM_FILE}{DKIM_FILE}{0}}
|
|
||||||
|
|
||||||
OUTGOING_IP = /etc/exim4/domains/$sender_address_domain/ip
|
|
||||||
|
|
||||||
SMTP_RELAY_FILE = ${if exists{/etc/exim4/domains/${sender_address_domain}/smtp_relay.conf}{/etc/exim4/domains/$sender_address_domain/smtp_relay.conf}{/etc/exim4/smtp_relay.conf}}
|
|
||||||
SMTP_RELAY_HOST = ${lookup{host}lsearch{SMTP_RELAY_FILE}}
|
|
||||||
SMTP_RELAY_PORT = ${lookup{port}lsearch{SMTP_RELAY_FILE}}
|
|
||||||
SMTP_RELAY_USER = ${lookup{user}lsearch{SMTP_RELAY_FILE}}
|
|
||||||
SMTP_RELAY_PASS = ${lookup{pass}lsearch{SMTP_RELAY_FILE}}
|
|
||||||
|
|
||||||
# Custom Filter
|
|
||||||
system_filter = /etc/exim4/system.filter
|
|
||||||
system_filter_user = Debian-exim
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# ACL CONFIGURATION #
|
|
||||||
# Specifies access control lists for incoming SMTP mail #
|
|
||||||
######################################################################
|
|
||||||
|
|
||||||
acl_not_smtp = acl_not_smtp
|
|
||||||
|
|
||||||
begin acl
|
|
||||||
|
|
||||||
# Limit per user for PHP scripts
|
|
||||||
acl_not_smtp:
|
|
||||||
deny message = Website of user $authenticated_id is sending too many emails - rate overlimit = $sender_rate / $sender_rate_period
|
|
||||||
ratelimit = 200 / 1h / $authenticated_id
|
|
||||||
|
|
||||||
warn ratelimit = 100 / 1h / strict / $authenticated_id
|
|
||||||
log_message = Sender rate [limitlog]: log / account / $authenticated_id / $sender_rate / $sender_rate_period
|
|
||||||
|
|
||||||
accept
|
|
||||||
|
|
||||||
acl_check_spammers:
|
|
||||||
accept hosts = +whitelist
|
|
||||||
|
|
||||||
drop message = Your host in blacklist on this server.
|
|
||||||
log_message = Host in blacklist
|
|
||||||
hosts = +spammers
|
|
||||||
|
|
||||||
accept
|
|
||||||
|
|
||||||
|
|
||||||
acl_check_mail:
|
|
||||||
deny condition = ${if eq{$sender_helo_name}{}}
|
|
||||||
message = HELO required before MAIL
|
|
||||||
|
|
||||||
drop !authenticated = *
|
|
||||||
message = Helo name contains an IP address (HELO was $sender_helo_name) and not is valid
|
|
||||||
condition = ${if match{$sender_helo_name}{\N((\d{1,3}[.-]\d{1,3}[.-]\d{1,3}[.-]\d{1,3})|([0-9a-f]{8})|([0-9A-F]{8}))\N}{yes}{no}}
|
|
||||||
condition = ${if match {${lookup dnsdb{>: defer_never,ptr=$sender_host_address}}\}{$sender_helo_name}{no}{yes}}
|
|
||||||
delay = 45s
|
|
||||||
|
|
||||||
drop !authenticated = *
|
|
||||||
condition = ${if isip{$sender_helo_name}}
|
|
||||||
message = Access denied - Invalid HELO name (See RFC2821 4.1.3)
|
|
||||||
|
|
||||||
drop !authenticated = *
|
|
||||||
condition = ${if eq{[$interface_address]}{$sender_helo_name}}
|
|
||||||
message = $interface_address is _my_ address
|
|
||||||
|
|
||||||
accept
|
|
||||||
|
|
||||||
|
|
||||||
acl_check_rcpt:
|
|
||||||
accept hosts = :
|
|
||||||
|
|
||||||
# Limit per email account for SMTP auhenticated users
|
|
||||||
deny message = Email account $authenticated_id is sending too many emails - rate overlimit = $sender_rate / $sender_rate_period
|
|
||||||
set acl_c_msg_limit = ${if exists{/etc/exim4/domains/${lookup{${domain:$authenticated_id}}dsearch{/etc/exim4/domains/}}/limits}{${lookup {$authenticated_id} lsearch{/etc/exim4/domains/${lookup{${domain:$authenticated_id}}dsearch{/etc/exim4/domains/}}/limits}{$value}{${readfile{/etc/exim4/limit.conf}}}}}{${readfile{/etc/exim4/limit.conf}}} }
|
|
||||||
ratelimit = $acl_c_msg_limit / 1h / strict/ $authenticated_id
|
|
||||||
|
|
||||||
warn ratelimit = ${eval:$acl_c_msg_limit / 2} / 1h / strict / $authenticated_id
|
|
||||||
log_message = Sender rate [limitlog]: log / email / $authenticated_id / $sender_rate / $sender_rate_period
|
|
||||||
|
|
||||||
deny message = Restricted characters in address
|
|
||||||
domains = +local_domains
|
|
||||||
local_parts = ^[.] : ^.*[@%!/|]
|
|
||||||
|
|
||||||
deny message = Restricted characters in address
|
|
||||||
domains = !+local_domains
|
|
||||||
local_parts = ^[./|] : ^.*[@%!] : ^.*/\\.\\./
|
|
||||||
|
|
||||||
require verify = sender
|
|
||||||
|
|
||||||
accept hosts = +relay_from_hosts
|
|
||||||
control = submission
|
|
||||||
|
|
||||||
accept authenticated = *
|
|
||||||
control = submission/domain=
|
|
||||||
|
|
||||||
deny message = Rejected because $sender_host_address is in a black list at $dnslist_domain\n$dnslist_text
|
|
||||||
hosts = !+whitelist
|
|
||||||
dnslists = ${readfile {/etc/exim4/dnsbl.conf}{:}}
|
|
||||||
|
|
||||||
require message = relay not permitted
|
|
||||||
domains = +local_domains : +relay_to_domains
|
|
||||||
|
|
||||||
deny message = smtp auth required
|
|
||||||
sender_domains = +local_domains
|
|
||||||
!authenticated = *
|
|
||||||
|
|
||||||
require verify = recipient
|
|
||||||
|
|
||||||
.ifdef CLAMD
|
|
||||||
warn set acl_m0 = no
|
|
||||||
|
|
||||||
warn condition = ${if exists {/etc/exim4/domains/$domain/antivirus}{yes}{no}}
|
|
||||||
set acl_m0 = yes
|
|
||||||
.endif
|
|
||||||
|
|
||||||
.ifdef SPAMASSASSIN
|
|
||||||
warn set acl_m1 = no
|
|
||||||
set acl_m3 = no
|
|
||||||
warn condition = ${if exists {/etc/exim4/domains/$domain/antispam}{yes}{no}}
|
|
||||||
set acl_m1 = yes
|
|
||||||
warn condition = ${if exists {/etc/exim4/domains/$domain/reject_spam}{yes}{no}}
|
|
||||||
set acl_m3 = yes
|
|
||||||
.endif
|
|
||||||
|
|
||||||
|
|
||||||
accept
|
|
||||||
|
|
||||||
|
|
||||||
acl_check_data:
|
|
||||||
.ifdef CLAMD
|
|
||||||
deny message = Message contains a virus ($malware_name) and has been rejected
|
|
||||||
malware = */defer_ok
|
|
||||||
condition = ${if eq{$acl_m0}{yes}{yes}{no}}
|
|
||||||
.endif
|
|
||||||
|
|
||||||
.ifdef SPAMASSASSIN
|
|
||||||
warn !authenticated = *
|
|
||||||
hosts = !+relay_from_hosts
|
|
||||||
condition = ${if < {$message_size}{1024K}}
|
|
||||||
condition = ${if eq{$acl_m1}{yes}{yes}{no}}
|
|
||||||
spam = debian-spamd:true/defer_ok
|
|
||||||
add_header = X-Spam-Score: $spam_score_int
|
|
||||||
add_header = X-Spam-Bar: $spam_bar
|
|
||||||
add_header = X-Spam-Report: $spam_report
|
|
||||||
set acl_m2 = $spam_score_int
|
|
||||||
|
|
||||||
warn condition = ${if !eq{$acl_m2}{} {yes}{no}}
|
|
||||||
condition = ${if >{$acl_m2}{SPAM_SCORE} {yes}{no}}
|
|
||||||
add_header = X-Spam-Status: Yes
|
|
||||||
message = SpamAssassin detected spam (from $sender_address to $recipients).
|
|
||||||
|
|
||||||
# Deny spam at high score if spam score > SPAM_REJECT_SCORE and delete_spam is enabled
|
|
||||||
deny message = This message scored $spam_score spam points
|
|
||||||
spam = debian-spamd:true
|
|
||||||
condition = ${if eq{$acl_m3}{yes}{yes}{no}}
|
|
||||||
condition = ${if >{$spam_score_int}{SPAM_REJECT_SCORE}{1}{0}}
|
|
||||||
.endif
|
|
||||||
|
|
||||||
|
|
||||||
accept
|
|
||||||
|
|
||||||
|
|
||||||
acl_check_mime:
|
|
||||||
deny message = Blacklisted file extension detected
|
|
||||||
condition = ${if match {${lc:$mime_filename}}{\N(\.ace|\.ade|\.adp|\.app|\.arj|\.asp|\.aspx|\.asx|\.bas|\.bat|\.cab|\.cer|\.chm|\.cmd|\.cnt|\.com|\.cpl|\.crt|\.csh|\.der|\.diagcab|\.dll|\.efi|\.exe|\.fla|\.fon|\.fxp|\.gadget|\.grp|\.hlp|\.hpj|\.hta|\.htc|\.img|\.inf|\.ins|\.iso|\.isp|\.its|\.jar|\.jnlp|\.js|\.jse|\.ksh|\.lib|\.lnk|\.mad|\.maf|\.mag|\.mam|\.maq|\.mar|\.mas|\.mat|\.mau|\.mav|\.maw|\.mcf|\.mda|\.mdb|\.mde|\.mdt|\.mdw|\.mdz|\.msc|\.msh|\.msh1|\.msh1xml|\.msh2|\.msh2xml|\.mshxml|\.msi|\.msp|\.mst|\.msu|\.ops|\.osd|\.pcd|\.pif|\.pl|\.plg|\.prf|\.prg|\.printerexport|\.ps1|\.ps1xml|\.ps2|\.ps2xml|\.psc1|\.psc2|\.psd1|\.psdm1|\.pst|\.py|\.pyc|\.pyo|\.pyw|\.pyz|\.pyzw|\.reg|\.scf|\.scr|\.sct|\.sfx|\.shb|\.shs|\.swf|\.sys|\.theme|\.tmp|\.ttf|\.url|\.vb|\.vba|\.vbe|\.vbp|\.vbs|\.vhd|\.vhdx|\.vsmacros|\.vsw|\.vxd|\.webpnp|\.website|\.wim|\.ws|\.wsc|\.wsf|\.wsh|\.xbap|\.xll|\.xnk)$\N}{1}{0}}
|
|
||||||
|
|
||||||
accept
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# AUTHENTICATION CONFIGURATION #
|
|
||||||
######################################################################
|
|
||||||
begin authenticators
|
|
||||||
|
|
||||||
smtp_relay_login:
|
|
||||||
driver = plaintext
|
|
||||||
public_name = LOGIN
|
|
||||||
hide client_send = : SMTP_RELAY_USER : SMTP_RELAY_PASS
|
|
||||||
|
|
||||||
dovecot_plain:
|
|
||||||
driver = dovecot
|
|
||||||
public_name = PLAIN
|
|
||||||
server_socket = /run/dovecot/auth-client
|
|
||||||
server_set_id = $auth1
|
|
||||||
|
|
||||||
dovecot_login:
|
|
||||||
driver = dovecot
|
|
||||||
public_name = LOGIN
|
|
||||||
server_socket = /run/dovecot/auth-client
|
|
||||||
server_set_id = $auth1
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# ROUTERS CONFIGURATION #
|
|
||||||
# Specifies how addresses are handled #
|
|
||||||
######################################################################
|
|
||||||
begin routers
|
|
||||||
|
|
||||||
send_via_unauthenticated_smtp_relay:
|
|
||||||
driver = manualroute
|
|
||||||
address_data = SMTP_RELAY_HOST:SMTP_RELAY_PORT
|
|
||||||
domains = !+local_domains
|
|
||||||
require_files = SMTP_RELAY_FILE
|
|
||||||
condition = ${if eq{SMTP_RELAY_USER}{}}
|
|
||||||
transport = remote_smtp
|
|
||||||
route_list = * ${extract{1}{:}{$address_data}}::${extract{2}{:}{$address_data}}
|
|
||||||
no_more
|
|
||||||
no_verify
|
|
||||||
|
|
||||||
send_via_smtp_relay:
|
|
||||||
driver = manualroute
|
|
||||||
address_data = SMTP_RELAY_HOST:SMTP_RELAY_PORT
|
|
||||||
domains = !+local_domains
|
|
||||||
require_files = SMTP_RELAY_FILE
|
|
||||||
transport = smtp_relay_smtp
|
|
||||||
route_list = * ${extract{1}{:}{$address_data}}::${extract{2}{:}{$address_data}}
|
|
||||||
no_more
|
|
||||||
no_verify
|
|
||||||
|
|
||||||
dnslookup:
|
|
||||||
driver = dnslookup
|
|
||||||
domains = !+local_domains
|
|
||||||
transport = remote_smtp
|
|
||||||
no_more
|
|
||||||
|
|
||||||
userforward:
|
|
||||||
driver = redirect
|
|
||||||
check_local_user
|
|
||||||
file = $home/.forward
|
|
||||||
require_files = ${local_part}:+${home}/.forward
|
|
||||||
domains = +local_domains
|
|
||||||
allow_filter
|
|
||||||
no_verify
|
|
||||||
no_expn
|
|
||||||
check_ancestor
|
|
||||||
file_transport = address_file
|
|
||||||
pipe_transport = address_pipe
|
|
||||||
reply_transport = address_reply
|
|
||||||
|
|
||||||
procmail:
|
|
||||||
driver = accept
|
|
||||||
check_local_user
|
|
||||||
require_files = ${local_part}:+${home}/.procmailrc:/usr/bin/procmail
|
|
||||||
transport = procmail
|
|
||||||
no_verify
|
|
||||||
|
|
||||||
autoreplay:
|
|
||||||
driver = accept
|
|
||||||
require_files = /etc/exim4/domains/$domain/autoreply.${local_part}.msg
|
|
||||||
condition = ${if exists{/etc/exim4/domains/$domain/autoreply.${local_part}.msg}{yes}{no}}
|
|
||||||
retry_use_local_part
|
|
||||||
transport = userautoreply
|
|
||||||
unseen
|
|
||||||
|
|
||||||
aliases:
|
|
||||||
driver = redirect
|
|
||||||
headers_add = X-redirected: yes
|
|
||||||
data = ${extract{1}{:}{${lookup{$local_part@$domain}lsearch{/etc/exim4/domains/$domain/aliases}}}}
|
|
||||||
require_files = /etc/exim4/domains/$domain/aliases
|
|
||||||
redirect_router = dnslookup
|
|
||||||
pipe_transport = address_pipe
|
|
||||||
unseen
|
|
||||||
|
|
||||||
localuser_fwd_only:
|
|
||||||
driver = accept
|
|
||||||
transport = devnull
|
|
||||||
condition = ${if exists{/etc/exim4/domains/$domain/fwd_only}{${lookup{$local_part}lsearch{/etc/exim4/domains/$domain/fwd_only}{true}{false}}}}
|
|
||||||
|
|
||||||
localuser_spam:
|
|
||||||
driver = accept
|
|
||||||
transport = local_spam_delivery
|
|
||||||
condition = ${if eq {${if match{$h_X-Spam-Status:}{\N^Yes\N}{yes}{no}}} {${lookup{$local_part}lsearch{/etc/exim4/domains/$domain/passwd}{yes}{no_such_user}}}}
|
|
||||||
|
|
||||||
localuser:
|
|
||||||
driver = accept
|
|
||||||
transport = local_delivery
|
|
||||||
condition = ${lookup{$local_part}lsearch{/etc/exim4/domains/$domain/passwd}{true}{false}}
|
|
||||||
|
|
||||||
catchall:
|
|
||||||
driver = redirect
|
|
||||||
headers_add = X-redirected: yes
|
|
||||||
require_files = /etc/exim4/domains/$domain/aliases
|
|
||||||
data = ${extract{1}{:}{${lookup{*@$domain}lsearch{/etc/exim4/domains/$domain/aliases}}}}
|
|
||||||
file_transport = local_delivery
|
|
||||||
redirect_router = dnslookup
|
|
||||||
condition = ${lookup{$local_part@$domain}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/aliases}{false}{true}}
|
|
||||||
|
|
||||||
terminate_alias:
|
|
||||||
driver = accept
|
|
||||||
transport = devnull
|
|
||||||
condition = ${lookup{$local_part@$domain}lsearch{/etc/exim4/domains/$domain/aliases}{true}{false}}
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# TRANSPORTS CONFIGURATION #
|
|
||||||
######################################################################
|
|
||||||
begin transports
|
|
||||||
|
|
||||||
smtp_relay_smtp:
|
|
||||||
driver = smtp
|
|
||||||
hosts_require_auth = $host_address
|
|
||||||
hosts_require_tls = $host_address
|
|
||||||
|
|
||||||
remote_smtp:
|
|
||||||
driver = smtp
|
|
||||||
helo_data = ${lookup dnsdb{>: defer_never,ptr=$sending_ip_address}{${listextract{1}{$value}}}{$primary_hostname}}
|
|
||||||
dkim_domain = DKIM_DOMAIN
|
|
||||||
dkim_selector = mail
|
|
||||||
dkim_private_key = DKIM_PRIVATE_KEY
|
|
||||||
dkim_canon = relaxed
|
|
||||||
dkim_strict = 0
|
|
||||||
hosts_try_fastopen = !*.l.google.com
|
|
||||||
interface = ${if exists{OUTGOING_IP}{${readfile{OUTGOING_IP}}}}
|
|
||||||
|
|
||||||
procmail:
|
|
||||||
driver = pipe
|
|
||||||
command = "/usr/bin/procmail -d $local_part"
|
|
||||||
return_path_add
|
|
||||||
delivery_date_add
|
|
||||||
envelope_to_add
|
|
||||||
user = $local_part
|
|
||||||
initgroups
|
|
||||||
return_output
|
|
||||||
|
|
||||||
local_delivery:
|
|
||||||
driver = appendfile
|
|
||||||
maildir_format
|
|
||||||
maildir_use_size_file
|
|
||||||
user = ${extract{2}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/$domain/passwd}}}}
|
|
||||||
group = mail
|
|
||||||
create_directory
|
|
||||||
directory_mode = 770
|
|
||||||
mode = 660
|
|
||||||
use_lockfile = no
|
|
||||||
delivery_date_add
|
|
||||||
envelope_to_add
|
|
||||||
return_path_add
|
|
||||||
directory = "${extract{5}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/$domain/passwd}}}}/mail/$domain/$local_part"
|
|
||||||
quota = ${extract{6}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/$domain/passwd}}}}M
|
|
||||||
quota_warn_threshold = 75%
|
|
||||||
|
|
||||||
local_spam_delivery:
|
|
||||||
driver = appendfile
|
|
||||||
maildir_format
|
|
||||||
maildir_use_size_file
|
|
||||||
user = ${extract{2}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/$domain/passwd}}}}
|
|
||||||
group = mail
|
|
||||||
create_directory
|
|
||||||
directory_mode = 770
|
|
||||||
mode = 660
|
|
||||||
use_lockfile = no
|
|
||||||
delivery_date_add
|
|
||||||
envelope_to_add
|
|
||||||
return_path_add
|
|
||||||
directory = "${extract{5}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/$domain/passwd}}}}/mail/$domain/$local_part/.Spam"
|
|
||||||
quota = ${extract{6}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/$domain/passwd}}}}M
|
|
||||||
quota_directory = "${extract{5}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/$domain/passwd}}}}/mail/$domain/$local_part"
|
|
||||||
quota_warn_threshold = 75%
|
|
||||||
|
|
||||||
address_pipe:
|
|
||||||
driver = pipe
|
|
||||||
return_output
|
|
||||||
|
|
||||||
address_file:
|
|
||||||
driver = appendfile
|
|
||||||
delivery_date_add
|
|
||||||
envelope_to_add
|
|
||||||
return_path_add
|
|
||||||
|
|
||||||
address_reply:
|
|
||||||
driver = autoreply
|
|
||||||
|
|
||||||
userautoreply:
|
|
||||||
driver = autoreply
|
|
||||||
file = /etc/exim4/domains/$domain/autoreply.${local_part}.msg
|
|
||||||
from = "${local_part}@${domain}"
|
|
||||||
headers = Content-Type: text/plain; charset=utf-8;\nContent-Transfer-Encoding: 8bit
|
|
||||||
subject = "${if def:h_Subject: {Autoreply: \"${rfc2047:$h_Subject:}\"} {Autoreply Message}}"
|
|
||||||
to = "${sender_address}"
|
|
||||||
|
|
||||||
devnull:
|
|
||||||
driver = appendfile
|
|
||||||
file = /dev/null
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# RETRY CONFIGURATION #
|
|
||||||
######################################################################
|
|
||||||
begin retry
|
|
||||||
|
|
||||||
# Address or Domain Error Retries
|
|
||||||
# ----------------- ----- -------
|
|
||||||
* * F,2h,15m; G,16h,1h,1.5; F,4d,6h
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# REWRITE CONFIGURATION #
|
|
||||||
######################################################################
|
|
||||||
begin rewrite
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
200
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
TOBEREPLACED
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
if $h_X-Spam-Status: contains "Yes"
|
|
||||||
then
|
|
||||||
headers add "Old-Subject: $h_subject"
|
|
||||||
headers remove "Subject"
|
|
||||||
headers add "Subject: *** SPAM *** $h_old-subject"
|
|
||||||
headers remove "Old-Subject"
|
|
||||||
endif
|
|
||||||
|
|
||||||
# X-Anti-Virus: infected
|
|
||||||
if $h_X-Anti-Virus: contains "infected"
|
|
||||||
then
|
|
||||||
headers add "Old-Subject: $h_subject"
|
|
||||||
headers remove "Subject"
|
|
||||||
headers add "Subject: *** VIRUS *** $h_old-subject"
|
|
||||||
headers remove "Old-Subject"
|
|
||||||
endif
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
# Fail2Ban configuration file for hestia
|
|
||||||
|
|
||||||
[Definition]
|
|
||||||
|
|
||||||
actionstart = /usr/local/hestia/bin/v-add-firewall-chain <name>
|
|
||||||
actionstop = /usr/local/hestia/bin/v-delete-firewall-chain <name>
|
|
||||||
actioncheck = iptables -n -L INPUT | grep -q 'fail2ban-<name>[ \t]'
|
|
||||||
actionban = /usr/local/hestia/bin/v-add-firewall-ban <ip> <name>
|
|
||||||
actionunban = /usr/local/hestia/bin/v-delete-firewall-ban <ip> <name>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
# Fail2Ban filter for unsuccessful hestia authentication attempts
|
|
||||||
#
|
|
||||||
|
|
||||||
[INCLUDES]
|
|
||||||
before = common.conf
|
|
||||||
|
|
||||||
[Definition]
|
|
||||||
failregex = .* <HOST> failed to login
|
|
||||||
ignoreregex =
|
|
||||||
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
[ssh-iptables]
|
|
||||||
enabled = true
|
|
||||||
filter = sshd
|
|
||||||
action = hestia[name=SSH]
|
|
||||||
logpath = /var/log/auth.log
|
|
||||||
maxretry = 5
|
|
||||||
|
|
||||||
[vsftpd-iptables]
|
|
||||||
enabled = false
|
|
||||||
filter = vsftpd
|
|
||||||
action = hestia[name=FTP]
|
|
||||||
logpath = /var/log/vsftpd.log
|
|
||||||
maxretry = 5
|
|
||||||
|
|
||||||
[exim-iptables]
|
|
||||||
enabled = true
|
|
||||||
filter = exim
|
|
||||||
action = hestia[name=MAIL]
|
|
||||||
logpath = /var/log/exim4/mainlog
|
|
||||||
|
|
||||||
[dovecot-iptables]
|
|
||||||
enabled = true
|
|
||||||
filter = dovecot
|
|
||||||
action = hestia[name=MAIL]
|
|
||||||
logpath = /var/log/dovecot.log
|
|
||||||
|
|
||||||
[mysqld-iptables]
|
|
||||||
enabled = false
|
|
||||||
filter = mysqld-auth
|
|
||||||
action = hestia[name=DB]
|
|
||||||
logpath = /var/log/mysql/error.log
|
|
||||||
maxretry = 5
|
|
||||||
|
|
||||||
[hestia-iptables]
|
|
||||||
enabled = true
|
|
||||||
filter = hestia
|
|
||||||
action = hestia[name=HESTIA]
|
|
||||||
logpath = /var/log/hestia/auth.log
|
|
||||||
maxretry = 5
|
|
||||||
|
|
||||||
[roundcube-auth]
|
|
||||||
enabled = false
|
|
||||||
filter = roundcube-auth
|
|
||||||
action = hestia[name=WEB]
|
|
||||||
logpath = /var/log/roundcube/errors.log
|
|
||||||
maxretry = 5
|
|
||||||
|
|
||||||
[phpmyadmin-auth]
|
|
||||||
enabled = true
|
|
||||||
filter = phpmyadmin-syslog
|
|
||||||
action = hestia[name=WEB]
|
|
||||||
logpath = /var/log/auth.log
|
|
||||||
maxretry = 5
|
|
||||||
|
|
||||||
[recidive]
|
|
||||||
enabled = true
|
|
||||||
filter = recidive
|
|
||||||
action = hestia[name=RECIDIVE]
|
|
||||||
logpath = /var/log/fail2ban.log
|
|
||||||
maxretry = 5
|
|
||||||
findtime = 86400
|
|
||||||
bantime = 864000
|
|
||||||
|
|
||||||
#Uncomment and add your IPs and or domains to the Whitelist
|
|
||||||
#[DEFAULT]
|
|
||||||
#ignoreip = 111.111.111.111 222.222.222.222 subdomain.example.tld example.tld 333.333.333.333
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Filegator\Services\Archiver\Adapters;
|
|
||||||
|
|
||||||
use Filegator\Container\Container;
|
|
||||||
use Filegator\Services\Archiver\ArchiverInterface;
|
|
||||||
use Filegator\Services\Service;
|
|
||||||
use Filegator\Services\Storage\Filesystem as Storage;
|
|
||||||
use Filegator\Services\Tmpfs\TmpfsInterface;
|
|
||||||
use function Hestiacp\quoteshellarg\quoteshellarg;
|
|
||||||
|
|
||||||
class HestiaZipArchiver extends ZipArchiver implements Service, ArchiverInterface {
|
|
||||||
protected $container;
|
|
||||||
|
|
||||||
public function __construct(TmpfsInterface $tmpfs, Container $container) {
|
|
||||||
$this->tmpfs = $tmpfs;
|
|
||||||
$this->container = $container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uncompress(string $source, string $destination, Storage $storage) {
|
|
||||||
$auth = $this->container->get("Filegator\Services\Auth\AuthInterface");
|
|
||||||
|
|
||||||
$v_user = basename($auth->user()->getUsername());
|
|
||||||
|
|
||||||
if (!strlen($v_user)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strpos($source, "/home") === false) {
|
|
||||||
$source = "/home/$v_user/" . $source;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strpos($destination, "/home") === false) {
|
|
||||||
$destination = "/home/$v_user/" . $destination;
|
|
||||||
}
|
|
||||||
|
|
||||||
exec(
|
|
||||||
"sudo /usr/local/hestia/bin/v-extract-fs-archive " .
|
|
||||||
quoteshellarg($v_user) .
|
|
||||||
" " .
|
|
||||||
quoteshellarg($source) .
|
|
||||||
" " .
|
|
||||||
quoteshellarg($destination),
|
|
||||||
$output,
|
|
||||||
$return_var,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the FileGator package.
|
|
||||||
*
|
|
||||||
* (c) Milos Stojanovic <alcalbg@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE file
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Filegator\Services\Auth\Adapters;
|
|
||||||
|
|
||||||
use Filegator\Services\Auth\AuthInterface;
|
|
||||||
use Filegator\Services\Auth\User;
|
|
||||||
use Filegator\Services\Auth\UsersCollection;
|
|
||||||
use Filegator\Services\Service;
|
|
||||||
use function Hestiacp\quoteshellarg\quoteshellarg;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @codeCoverageIgnore
|
|
||||||
*/
|
|
||||||
class HestiaAuth implements Service, AuthInterface {
|
|
||||||
protected $permissions = [];
|
|
||||||
|
|
||||||
protected $private_repos = false;
|
|
||||||
|
|
||||||
protected $hestia_user = "";
|
|
||||||
|
|
||||||
public function init(array $config = []) {
|
|
||||||
if (isset($_SESSION["user"])) {
|
|
||||||
$v_user = $_SESSION["user"];
|
|
||||||
}
|
|
||||||
if (!empty($_SESSION["look"])) {
|
|
||||||
if (isset($_SESSION["look"]) && $_SESSION["userContext"] === "admin") {
|
|
||||||
$v_user = $_SESSION["look"];
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
$_SESSION["look"] == "admin" &&
|
|
||||||
$_SESSION["POLICY_SYSTEM_PROTECTED_ADMIN"] == "yes"
|
|
||||||
) {
|
|
||||||
// Go away do not login
|
|
||||||
header("Location: /");
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->hestia_user = $v_user;
|
|
||||||
$this->permissions = isset($config["permissions"]) ? (array) $config["permissions"] : [];
|
|
||||||
$this->private_repos = isset($config["private_repos"])
|
|
||||||
? (bool) $config["private_repos"]
|
|
||||||
: false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function user(): ?User {
|
|
||||||
$cmd = "/usr/bin/sudo /usr/local/hestia/bin/v-list-user";
|
|
||||||
exec($cmd . " " . quoteshellarg($this->hestia_user) . " json", $output, $return_var);
|
|
||||||
|
|
||||||
if ($return_var == 0) {
|
|
||||||
$data = json_decode(implode("", $output), true);
|
|
||||||
$hestia_user_info = $data[$this->hestia_user];
|
|
||||||
return $this->transformUser($hestia_user_info);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->getGuest();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function transformUser($hstuser): User {
|
|
||||||
$user = new User();
|
|
||||||
$user->setUsername($this->hestia_user);
|
|
||||||
$user->setName($this->hestia_user . " (" . $hstuser["NAME"] . ")");
|
|
||||||
$user->setRole("user");
|
|
||||||
$user->setPermissions($this->permissions);
|
|
||||||
$user->setHomedir("/");
|
|
||||||
return $user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function authenticate($username, $password): bool {
|
|
||||||
# Auth is handled by Hestia
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function forget() {
|
|
||||||
// Logout return to Hestia
|
|
||||||
return $this->getGuest();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function store(User $user) {
|
|
||||||
return null; // not used
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update($username, User $user, $password = ""): User {
|
|
||||||
// Password change is handled by Hestia
|
|
||||||
return $this->user();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function add(User $user, $password): User {
|
|
||||||
return new User(); // not used
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete(User $user) {
|
|
||||||
return true; // not used
|
|
||||||
}
|
|
||||||
|
|
||||||
public function find($username): ?User {
|
|
||||||
return null; // not used
|
|
||||||
}
|
|
||||||
|
|
||||||
public function allUsers(): UsersCollection {
|
|
||||||
return new UsersCollection(); // not used
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getGuest(): User {
|
|
||||||
$guest = new User();
|
|
||||||
|
|
||||||
$guest->setUsername("guest");
|
|
||||||
$guest->setName("Guest");
|
|
||||||
$guest->setRole("guest");
|
|
||||||
$guest->setHomedir("/");
|
|
||||||
$guest->setPermissions([]);
|
|
||||||
|
|
||||||
return $guest;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the FileGator package.
|
|
||||||
*
|
|
||||||
* (c) Milos Stojanovic <alcalbg@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE file
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Filegator\Services\Session\Adapters;
|
|
||||||
|
|
||||||
use Filegator\Kernel\Request;
|
|
||||||
use Filegator\Services\Service;
|
|
||||||
use Filegator\Services\Session\Session;
|
|
||||||
use Filegator\Services\Session\SessionStorageInterface;
|
|
||||||
|
|
||||||
class SessionStorage implements Service, SessionStorageInterface {
|
|
||||||
protected $request;
|
|
||||||
|
|
||||||
protected $config;
|
|
||||||
|
|
||||||
public function __construct(Request $request) {
|
|
||||||
$this->request = $request;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function init(array $config = []) {
|
|
||||||
// we don't have a previous session attached
|
|
||||||
if (!$this->getSession()) {
|
|
||||||
$handler = $config["handler"];
|
|
||||||
$session = new Session($handler());
|
|
||||||
//$session->setName('filegator');
|
|
||||||
$this->setSession($session);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function save() {
|
|
||||||
$this->getSession()->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function set(string $key, $data) {
|
|
||||||
return $this->getSession()->set($key, $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function get(string $key, $default = null) {
|
|
||||||
return $this->getSession() ? $this->getSession()->get($key, $default) : $default;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function invalidate() {
|
|
||||||
if (!$this->getSession()->isStarted()) {
|
|
||||||
$this->getSession()->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->getSession()->invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function setSession(Session $session) {
|
|
||||||
return $this->request->setSession($session);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getSession(): ?Session {
|
|
||||||
return $this->request->getSession();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "filegator/filegator",
|
|
||||||
"description": "Filegator",
|
|
||||||
"license": "MIT",
|
|
||||||
"type": "project",
|
|
||||||
"config": {
|
|
||||||
"platform": {
|
|
||||||
"php": "7.2.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"php": "^7.2",
|
|
||||||
"monolog/monolog": "^1.24",
|
|
||||||
"nikic/fast-route": "^1.3",
|
|
||||||
"symfony/security-csrf": "^4.4",
|
|
||||||
"symfony/http-foundation": "^4.4",
|
|
||||||
"dibi/dibi": "^4.1",
|
|
||||||
"php-di/php-di": "^6.0",
|
|
||||||
"rakit/validation": "^1.1",
|
|
||||||
"league/flysystem": "^1.1",
|
|
||||||
"league/flysystem-ziparchive": "^1.0",
|
|
||||||
"league/flysystem-sftp": "^1.0",
|
|
||||||
"hestiacp/phpquoteshellarg": "^1.0"
|
|
||||||
},
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Milos Stojanovic",
|
|
||||||
"email": "alcalbg@gmail.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Filegator\\": "backend"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload-dev": {
|
|
||||||
"psr-4": {
|
|
||||||
"Tests\\": "tests/backend/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"phpunit/phpunit": "^8.0",
|
|
||||||
"symfony/var-dumper": "^4.4",
|
|
||||||
"league/flysystem-memory": "^1.0",
|
|
||||||
"phpstan/phpstan": "^0.11.8"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
4771
install/deb/filemanager/filegator/composer.lock
generated
4771
install/deb/filemanager/filegator/composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,175 +0,0 @@
|
|||||||
<?php
|
|
||||||
use function Hestiacp\quoteshellarg\quoteshellarg;
|
|
||||||
|
|
||||||
$dist_config = require __DIR__ . "/configuration_sample.php";
|
|
||||||
|
|
||||||
$dist_config["public_path"] = "/fm/";
|
|
||||||
$dist_config["frontend_config"]["app_name"] = "File Manager - Hestia Control Panel";
|
|
||||||
$dist_config["frontend_config"]["logo"] = "../images/logo.svg";
|
|
||||||
$dist_config["frontend_config"]["editable"] = [
|
|
||||||
".txt",
|
|
||||||
".css",
|
|
||||||
".js",
|
|
||||||
".ts",
|
|
||||||
".html",
|
|
||||||
".php",
|
|
||||||
".py",
|
|
||||||
".yml",
|
|
||||||
".xml",
|
|
||||||
".md",
|
|
||||||
".log",
|
|
||||||
".csv",
|
|
||||||
".conf",
|
|
||||||
".config",
|
|
||||||
".ini",
|
|
||||||
".scss",
|
|
||||||
".sh",
|
|
||||||
".env",
|
|
||||||
".example",
|
|
||||||
".htaccess",
|
|
||||||
".twig",
|
|
||||||
".tpl",
|
|
||||||
".yaml",
|
|
||||||
];
|
|
||||||
$dist_config["frontend_config"]["guest_redirection"] = "/login/";
|
|
||||||
$dist_config["frontend_config"]["upload_max_size"] = 1024 * 1024 * 1024;
|
|
||||||
|
|
||||||
$dist_config["services"]["Filegator\Services\Storage\Filesystem"]["config"][
|
|
||||||
"adapter"
|
|
||||||
] = function () {
|
|
||||||
if (!empty($_SESSION["INACTIVE_SESSION_TIMEOUT"])) {
|
|
||||||
if ($_SESSION["INACTIVE_SESSION_TIMEOUT"] * 60 + $_SESSION["LAST_ACTIVITY"] < time()) {
|
|
||||||
$v_user = quoteshellarg($_SESSION["user"]);
|
|
||||||
$v_session_id = quoteshellarg($_SESSION["token"]);
|
|
||||||
exec(
|
|
||||||
"/usr/local/hestia/bin/v-log-user-logout " . $v_user . " " . $v_session_id,
|
|
||||||
$output,
|
|
||||||
$return_var,
|
|
||||||
);
|
|
||||||
unset($_SESSION);
|
|
||||||
session_unset();
|
|
||||||
session_destroy();
|
|
||||||
session_start();
|
|
||||||
echo '<meta http-equiv="refresh" content="0; url=/">';
|
|
||||||
exit();
|
|
||||||
} else {
|
|
||||||
$_SESSION["LAST_ACTIVITY"] = time();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
echo '<meta http-equiv="refresh" content="0; url=/">';
|
|
||||||
}
|
|
||||||
if (isset($_SESSION["user"])) {
|
|
||||||
$v_user = $_SESSION["user"];
|
|
||||||
}
|
|
||||||
if (!empty($_SESSION["look"])) {
|
|
||||||
if (isset($_SESSION["look"]) && $_SESSION["userContext"] === "admin") {
|
|
||||||
$v_user = $_SESSION["look"];
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
isset($_SESSION["look"]) &&
|
|
||||||
$_SESSION["look"] == "admin" &&
|
|
||||||
$_SESSION["POLICY_SYSTEM_PROTECTED_ADMIN"] == "yes"
|
|
||||||
) {
|
|
||||||
header("Location: /");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# Create filemanager sftp key if missing and trash it after 30 min
|
|
||||||
if (!file_exists("/home/" . basename($v_user) . "/.ssh/hst-filemanager-key")) {
|
|
||||||
exec(
|
|
||||||
"sudo /usr/local/hestia/bin/v-add-user-sftp-key " .
|
|
||||||
quoteshellarg(basename($v_user)) .
|
|
||||||
" 30",
|
|
||||||
$output,
|
|
||||||
$return_var,
|
|
||||||
);
|
|
||||||
// filemanager also requires .ssh chmod o+x ... hopefully we can improve it to g+x or u+x someday
|
|
||||||
// current minimum for filemanager: chmod 0701 .ssh
|
|
||||||
shell_exec("sudo chmod o+x " . quoteshellarg("/home/" . basename($v_user) . "/.ssh"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isset($_SESSION["SFTP_PORT"])) {
|
|
||||||
exec("sudo /usr/local/hestia/bin/v-list-sys-sshd-port json", $output, $result);
|
|
||||||
$port = json_decode(implode("", $output));
|
|
||||||
if (is_numeric($port[0]) && $port[0] > 0) {
|
|
||||||
$_SESSION["SFTP_PORT"] = $port[0];
|
|
||||||
} elseif (
|
|
||||||
preg_match('/^\s*Port\s+(\d+)$/im', file_get_contents("/etc/ssh/sshd_config"), $matches)
|
|
||||||
) {
|
|
||||||
$_SESSION["SFTP_PORT"] = $matches[1] ?? 22;
|
|
||||||
} else {
|
|
||||||
$_SESSION["SFTP_PORT"] = 22;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preg_match(
|
|
||||||
'/(Hestia SFTP Chroot\nMatch User)(.*)/i',
|
|
||||||
file_get_contents("/etc/ssh/sshd_config"),
|
|
||||||
$matches,
|
|
||||||
);
|
|
||||||
$user_list = explode(",", $matches[2]);
|
|
||||||
if (in_array($v_user, $user_list)) {
|
|
||||||
$root = "/";
|
|
||||||
} else {
|
|
||||||
$root = "/home/" . $v_user;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new \League\Flysystem\Sftp\SftpAdapter([
|
|
||||||
"host" => "127.0.0.1",
|
|
||||||
"port" => intval($_SESSION["SFTP_PORT"]),
|
|
||||||
"username" => basename($v_user),
|
|
||||||
"privateKey" => "/home/" . basename($v_user) . "/.ssh/hst-filemanager-key",
|
|
||||||
"root" => $root,
|
|
||||||
"timeout" => 10,
|
|
||||||
"directoryPerm" => 0755,
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
$dist_config["services"]["Filegator\Services\Archiver\ArchiverInterface"] = [
|
|
||||||
"handler" => "\Filegator\Services\Archiver\Adapters\HestiaZipArchiver",
|
|
||||||
"config" => [],
|
|
||||||
];
|
|
||||||
|
|
||||||
$dist_config["services"]["Filegator\Services\Auth\AuthInterface"] = [
|
|
||||||
"handler" => "\Filegator\Services\Auth\Adapters\HestiaAuth",
|
|
||||||
"config" => [
|
|
||||||
"permissions" => ["read", "write", "upload", "download", "batchdownload", "zip"],
|
|
||||||
"private_repos" => false,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
$dist_config["services"]["Filegator\Services\View\ViewInterface"]["config"] = [
|
|
||||||
"add_to_head" => '
|
|
||||||
<style>
|
|
||||||
.logo {
|
|
||||||
width: 46px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
',
|
|
||||||
"add_to_body" => '
|
|
||||||
<script>
|
|
||||||
var checkVueLoaded = setInterval(function() {
|
|
||||||
if (document.getElementsByClassName("container").length) {
|
|
||||||
clearInterval(checkVueLoaded);
|
|
||||||
var navProfile = document.getElementsByClassName("navbar-item profile")[0]; navProfile.replaceWith(navProfile.cloneNode(true))
|
|
||||||
document.getElementsByClassName("navbar-item logout")[0].text="Exit to Control Panel \u00BB";
|
|
||||||
div = document.getElementsByClassName("container")[0];
|
|
||||||
callback = function(){
|
|
||||||
if (document.getElementsByClassName("navbar-item logout")[0]){
|
|
||||||
if ( document.getElementsByClassName("navbar-item logout")[0].text != "Exit to Control Panel \u00BB" ){
|
|
||||||
var navProfile = document.getElementsByClassName("navbar-item profile")[0]; navProfile.replaceWith(navProfile.cloneNode(true))
|
|
||||||
document.getElementsByClassName("navbar-item logout")[0].text="Exit to Control Panel \u00BB";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
config = {
|
|
||||||
childList:true,
|
|
||||||
subtree:true
|
|
||||||
}
|
|
||||||
observer = new MutationObserver(callback);
|
|
||||||
observer.observe(div,config);
|
|
||||||
}
|
|
||||||
}, 200);
|
|
||||||
</script>',
|
|
||||||
];
|
|
||||||
|
|
||||||
return $dist_config;
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Checking root permissions
|
|
||||||
if [ "x$(id -u)" != 'x0' ]; then
|
|
||||||
echo "Error: Script can be run executed only by root"
|
|
||||||
exit 10
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$HESTIA" ]; then
|
|
||||||
HESTIA="/usr/local/hestia"
|
|
||||||
fi
|
|
||||||
|
|
||||||
user='admin'
|
|
||||||
fm_error='no'
|
|
||||||
source $HESTIA/func/main.sh
|
|
||||||
source $HESTIA/install/upgrade/upgrade.conf
|
|
||||||
|
|
||||||
if [ -z "$HOMEDIR" ] || [ -z "$HESTIA_INSTALL_DIR" ]; then
|
|
||||||
echo "Error: Hestia environment vars not present"
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
FM_INSTALL_DIR="$HESTIA/web/fm"
|
|
||||||
|
|
||||||
FM_FILE="filegator_latest"
|
|
||||||
FM_URL="https://github.com/filegator/static/raw/master/builds/filegator_latest.zip"
|
|
||||||
|
|
||||||
COMPOSER_BIN="$HOMEDIR/$user/.composer/composer"
|
|
||||||
if [ ! -f "$COMPOSER_BIN" ]; then
|
|
||||||
$BIN/v-add-user-composer "$user"
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
$BIN/v-add-user-notification admin 'Composer installation failed!' '<p class="u-text-bold">The File Manager will not work without Composer.</p><p>Please try running the installer from a shell session:<br><code>bash $HESTIA/install/deb/filemanager/install-fm.sh</code></p><p>If this issue continues, please <a href="https://github.com/hestiacp/hestiacp/issues" target="_blank">open an issue on GitHub</a>.</p>'
|
|
||||||
fm_error='yes'
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$fm_error" != "yes" ]; then
|
|
||||||
rm --recursive --force "$FM_INSTALL_DIR"
|
|
||||||
mkdir -p "$FM_INSTALL_DIR"
|
|
||||||
cd "$FM_INSTALL_DIR"
|
|
||||||
|
|
||||||
[ ! -f "${FM_INSTALL_DIR}/${FM_FILE}" ] && wget "$FM_URL" --quiet -O "${FM_INSTALL_DIR}/${FM_FILE}.zip"
|
|
||||||
|
|
||||||
unzip -qq "${FM_INSTALL_DIR}/${FM_FILE}.zip"
|
|
||||||
mv --force ${FM_INSTALL_DIR}/filegator/* "${FM_INSTALL_DIR}"
|
|
||||||
rm --recursive --force ${FM_INSTALL_DIR}/${FM_FILE}
|
|
||||||
[[ -f "${FM_INSTALL_DIR}/${FM_FILE}" ]] && rm "${FM_INSTALL_DIR}/${FM_FILE}"
|
|
||||||
|
|
||||||
cp --recursive --force ${HESTIA_INSTALL_DIR}/filemanager/filegator/* "${FM_INSTALL_DIR}"
|
|
||||||
|
|
||||||
chown $user: -R "${FM_INSTALL_DIR}"
|
|
||||||
|
|
||||||
# Check if php7.3 is available and run the installer
|
|
||||||
if [ -f "/usr/bin/php7.3" ]; then
|
|
||||||
COMPOSER_HOME="$HOMEDIR/$user/.config/composer" user_exec /usr/bin/php7.3 $COMPOSER_BIN --quiet --no-dev install
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
$BIN/v-add-user-notification admin 'File Manager installation failed!' '<p>Please try running the installer from a shell session:<br><code>bash $HESTIA/install/deb/filemanager/install-fm.sh</code></p><p>If this issue continues, please <a href="https://github.com/hestiacp/hestiacp/issues" target="_blank">open an issue on GitHub</a>.</p>'
|
|
||||||
fm_error="yes"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
$BIN/v-add-user-notification admin 'File Manager installation failed!' '<p class="u-text-bold">Unable to proceed with installation of File Manager.</p><p>Package <span class="u-text-bold">php7.3-cli</span> is missing from your system. Please check your PHP installation and environment settings.</p>'
|
|
||||||
fm_error="yes"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$fm_error" != "yes" ]; then
|
|
||||||
chown root: -R "${FM_INSTALL_DIR}"
|
|
||||||
chown $user: "${FM_INSTALL_DIR}/private"
|
|
||||||
chown $user: "${FM_INSTALL_DIR}/private/logs"
|
|
||||||
chown $user: "${FM_INSTALL_DIR}/repository"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
/var/log/apache2/*.log /var/log/apache2/domains/*log {
|
|
||||||
rotate 4
|
|
||||||
weekly
|
|
||||||
missingok
|
|
||||||
notifempty
|
|
||||||
compress
|
|
||||||
delaycompress
|
|
||||||
create 640
|
|
||||||
sharedscripts
|
|
||||||
postrotate
|
|
||||||
/etc/init.d/apache2 reload > /dev/null || true
|
|
||||||
[ ! -f /run/nginx.pid ] || kill -USR1 `cat /run/nginx.pid`
|
|
||||||
endscript
|
|
||||||
prerotate
|
|
||||||
if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
|
|
||||||
run-parts /etc/logrotate.d/httpd-prerotate; \
|
|
||||||
fi; \
|
|
||||||
endscript
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user