Files
hestiacp/bin/v-bunkerweb-module-install
Alexey Berezhok 54f549db62 Fixes2
2026-05-02 17:50:56 +03:00

463 lines
13 KiB
Ruby
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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_PASSWORD = SecureRandom.alphanumeric(24) + "!@#"
ADMIN_PASSWORD = SecureRandom.alphanumeric(24) + "!@#"
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")
# 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: "/bw",
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}/bw"
else
puts " http://#{server_ip_addr}/bw"
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