Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ input validation, auto-rollback safety, and pre-flight checks.
- [ ] Clean up dry-run output in interactive mode

### Bug Fixes
- [ ] Review sudo usage (~48 calls, minimize surface)
- [x] Review sudo usage (~48 calls, minimize surface) — features and template
deployment now go through `smart_copy`/`smart_mkdir`/`smart_write` in
`lib/core/helpers.sh` (sudo only when the target isn't writable).
honeypot/compiler/warning-fixer/install/backup sudo remains — those
targets are genuinely root-owned (issue #14 scope).

---

Expand Down
47 changes: 46 additions & 1 deletion lib/core/helpers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,58 @@ smart_copy() {

dst_dir=$(dirname "$dst")

if [ -w "$dst_dir" ]; then
# Overwriting an existing writable file needs no dir write permission
if [ -w "$dst_dir" ] || [ -w "$dst" ]; then
cp "$src" "$dst"
else
sudo cp "$src" "$dst"
fi
}

# Create a directory tree with automatic sudo handling
# Walks up to the nearest existing ancestor to test writability and uses
# sudo only when that ancestor is not writable
# Args: $1 = directory path
# Returns: 0 on success, 1 on failure
smart_mkdir() {
local dir="$1"
local parent

[ -z "$dir" ] && return 1
[ -d "$dir" ] && return 0

parent="$dir"
while [ ! -e "$parent" ]; do
parent=$(dirname "$parent")
done

if [ -w "$parent" ]; then
mkdir -p "$dir"
else
sudo mkdir -p "$dir"
fi
}

# Write stdin to a file with automatic sudo handling
# Replaces the 'sudo tee' pattern: writes directly when the destination
# file or its directory is writable, elevates only otherwise
# Args: $1 = destination path
# Returns: 0 on success, 1 on failure
smart_write() {
local dst="$1"
local dst_dir

[ -z "$dst" ] && return 1

dst_dir=$(dirname "$dst")

if [ -w "$dst_dir" ] || [ -w "$dst" ]; then
cat > "$dst"
else
sudo tee "$dst" > /dev/null
fi
}

################################################################################
# Dry-Run Helpers
################################################################################
Expand Down
6 changes: 3 additions & 3 deletions lib/core/templates.sh
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ template_deploy_to_confd() {
return 0
fi

if sudo cp "$source" "$dest" 2>/dev/null; then
if smart_copy "$source" "$dest" 2>/dev/null; then
if type -t ui_step_path &>/dev/null; then
ui_step_path "Deployed" "conf.d/${name}"
fi
Expand Down Expand Up @@ -99,8 +99,8 @@ template_deploy_to_snippets() {
return 0
fi

sudo mkdir -p /etc/nginx/snippets 2>/dev/null
if sudo cp "$source" "$dest" 2>/dev/null; then
smart_mkdir /etc/nginx/snippets 2>/dev/null
if smart_copy "$source" "$dest" 2>/dev/null; then
if type -t ui_step_path &>/dev/null; then
ui_step_path "Deployed" "snippets/${name}"
fi
Expand Down
6 changes: 1 addition & 5 deletions lib/features/bad-bot-blocker.sh
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,7 @@ feature_apply_custom_bad_bot_blocker() {
return 0
fi

if [[ -w "$confd_dir" ]]; then
cp "$src" "$dst"
else
sudo cp "$src" "$dst"
fi
smart_copy "$src" "$dst"

if [[ -f "$dst" ]]; then
if type -t ui_step_path &>/dev/null; then
Expand Down
8 changes: 2 additions & 6 deletions lib/features/brotli.sh
Original file line number Diff line number Diff line change
Expand Up @@ -185,12 +185,8 @@ feature_apply_custom_brotli() {
return 0
fi

# Deploy with smart sudo
if [ -w "$confd_dir" ]; then
cp "$src" "$dst"
else
sudo cp "$src" "$dst"
fi
# Deploy (elevates via sudo only if conf.d is not writable)
smart_copy "$src" "$dst"

if type -t ui_step_path &>/dev/null; then
ui_step_path "Deployed" "conf.d/compression.conf"
Expand Down
6 changes: 1 addition & 5 deletions lib/features/cloudflare-realip.sh
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,7 @@ feature_apply_custom_cloudflare_realip() {
return 0
fi

if [[ -w "$confd_dir" ]]; then
cp "$src" "$dst"
else
sudo cp "$src" "$dst"
fi
smart_copy "$src" "$dst"

if [[ -f "$dst" ]]; then
if type -t ui_step_path &>/dev/null; then
Expand Down
38 changes: 9 additions & 29 deletions lib/features/fastcgi-cache.sh
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@ _fastcgi_create_cache_dir() {
return 0
fi

# Try system directory first
if sudo mkdir -p "$cache_dir" 2>/dev/null; then
# Try system directory first (elevates only if the parent is not writable)
if smart_mkdir "$cache_dir" 2>/dev/null; then
sudo chown -R www-data:www-data "$cache_dir" 2>/dev/null || \
sudo chown -R _www:_www "$cache_dir" 2>/dev/null || \
sudo chown -R nginx:nginx "$cache_dir" 2>/dev/null || true
Expand Down Expand Up @@ -223,14 +223,8 @@ _fastcgi_deploy_system() {
else
local template_dir="${TEMPLATE_DIR:-nginx-optimizer-templates}"

# Create snippets directory with sudo if needed
if [ ! -d "$snippets_dir" ]; then
if [ -w "$(dirname "$snippets_dir")" ]; then
mkdir -p "$snippets_dir" 2>/dev/null
else
sudo mkdir -p "$snippets_dir" 2>/dev/null
fi
fi
# Create snippets directory, elevating only if needed
smart_mkdir "$snippets_dir" 2>/dev/null

# Deploy templates: server-level skip rules + location-level cache directives + purge endpoint
local template_name
Expand All @@ -239,13 +233,7 @@ _fastcgi_deploy_system() {
local dst="${snippets_dir}/${template_name}"

if [ -f "$src" ]; then
if type -t smart_copy &>/dev/null; then
smart_copy "$src" "$dst"
elif [ -w "$snippets_dir" ]; then
cp "$src" "$dst" 2>/dev/null
else
sudo cp "$src" "$dst" 2>/dev/null
fi
smart_copy "$src" "$dst" 2>/dev/null

if [ -f "$dst" ]; then
if type -t ui_step_path &>/dev/null; then
Expand Down Expand Up @@ -299,12 +287,8 @@ _fastcgi_deploy_confd() {
fi

if [ -f "$src" ]; then
# Create directory with sudo if needed
if [ -w "$confd_dir" ]; then
mkdir -p "$confd_dir" 2>/dev/null
else
sudo mkdir -p "$confd_dir" 2>/dev/null
fi
# Create directory, elevating only if needed
smart_mkdir "$confd_dir" 2>/dev/null

# Get RAM-aware zone sizes
local keys_zone="100m"
Expand All @@ -320,11 +304,7 @@ _fastcgi_deploy_confd() {
sed -e "s|keys_zone=WORDPRESS:100m|keys_zone=WORDPRESS:${keys_zone}|g" \
-e "s|max_size=512m|max_size=${max_size}|g" "$src" > "$temp_file"

if [ -w "$confd_dir" ]; then
cp "$temp_file" "$dst" 2>/dev/null
else
sudo cp "$temp_file" "$dst" 2>/dev/null
fi
smart_copy "$temp_file" "$dst" 2>/dev/null
rm -f "$temp_file"

if [ -f "$dst" ]; then
Expand Down Expand Up @@ -448,7 +428,7 @@ _fastcgi_inject_system() {
}
}' "$site_conf" > "$temp_file"

if sudo cp "$temp_file" "$site_conf" 2>/dev/null; then
if smart_copy "$temp_file" "$site_conf" 2>/dev/null; then
if type -t ui_step_path &>/dev/null; then
ui_step_path "Configured site" "$(basename "$site_conf")"
fi
Expand Down
12 changes: 6 additions & 6 deletions lib/features/http3.sh
Original file line number Diff line number Diff line change
Expand Up @@ -174,12 +174,12 @@ _http3_inject_system_nginx() {
local backup="${site_conf}.http3bak"

# Smart sudo: only use if file not writable
local use_sudo=""
local SUDO=""
if [ ! -w "$site_conf" ]; then
use_sudo="sudo"
SUDO="sudo"
fi

$use_sudo cp "$site_conf" "$backup"
$SUDO cp "$site_conf" "$backup"

awk -v quic="$quic_directive" -v quic_v6="$quic_directive_v6" '
{
Expand All @@ -199,15 +199,15 @@ _http3_inject_system_nginx() {
}
}' "$site_conf" > "${site_conf}.tmp"

$use_sudo mv "${site_conf}.tmp" "$site_conf"
$SUDO mv "${site_conf}.tmp" "$site_conf"

# Validate
if nginx -t 2>&1 | grep -q "test failed\|emerg"; then
$use_sudo mv "$backup" "$site_conf"
$SUDO mv "$backup" "$site_conf"
continue
fi

$use_sudo rm -f "$backup"
$SUDO rm -f "$backup"
if type -t ui_step_path &>/dev/null; then
ui_step_path "Configured HTTP/3" "$(basename "$site_conf")"
fi
Expand Down
6 changes: 1 addition & 5 deletions lib/features/log-tuning.sh
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,7 @@ feature_apply_custom_log_tuning() {
return 0
fi

if [[ -w "$confd_dir" ]]; then
cp "$src" "$dst"
else
sudo cp "$src" "$dst"
fi
smart_copy "$src" "$dst"

if [[ -f "$dst" ]]; then
if type -t ui_step_path &>/dev/null; then
Expand Down
4 changes: 2 additions & 2 deletions lib/features/opcache.sh
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,8 @@ _opcache_deploy_config() {
if [ -d "$php_conf_dir" ]; then
local dest="${php_conf_dir}/99-opcache-optimized.ini"

# Try with sudo first
if printf '%s\n' "$content" | sudo tee "$dest" >/dev/null 2>&1; then
# Write directly when the conf.d dir is writable; elevate only if not
if printf '%s\n' "$content" | smart_write "$dest" >/dev/null 2>&1; then
if type -t ui_step_path &>/dev/null; then
ui_step_path "Configured OpCache" "PHP ${php_version}"
fi
Expand Down
6 changes: 1 addition & 5 deletions lib/features/open-file-cache.sh
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,7 @@ open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
"
if [[ -w "$confd_dir" ]]; then
printf '%s' "$tuned_content" > "$dst"
else
printf '%s' "$tuned_content" | sudo tee "$dst" > /dev/null
fi
printf '%s' "$tuned_content" | smart_write "$dst"

if [[ -f "$dst" ]]; then
if type -t ui_step_path &>/dev/null; then
Expand Down
8 changes: 4 additions & 4 deletions lib/features/php-fpm-tuning.sh
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,11 @@ feature_apply_custom_php_fpm_tuning() {
fi

# Backup original
local use_sudo=""
local SUDO=""
if [[ ! -w "$pool_file" ]]; then
use_sudo="sudo"
SUDO="sudo"
fi
$use_sudo cp "$pool_file" "${pool_file}.before-tuning"
$SUDO cp "$pool_file" "${pool_file}.before-tuning"

# Apply tuning with sed
# Use a temp file approach for safe atomic replacement
Expand All @@ -194,7 +194,7 @@ feature_apply_custom_php_fpm_tuning() {
_fpm_set_directive "$temp_file" "pm.max_requests" "500"

# Apply
$use_sudo cp "$temp_file" "$pool_file"
$SUDO cp "$temp_file" "$pool_file"
rm -f "$temp_file"

if type -t ui_step_path &>/dev/null; then
Expand Down
14 changes: 2 additions & 12 deletions lib/features/security.sh
Original file line number Diff line number Diff line change
Expand Up @@ -121,20 +121,10 @@ _security_deploy_ddos_protection() {
sed -e "s|limit_conn addr 50|limit_conn addr ${conn_per_ip}|g" \
-e "s|limit_conn perserver 10000|limit_conn perserver ${conn_per_server}|g" "$src" > "$temp_file"

if [[ ! -d "$snippets_dir" ]]; then
if [[ -w "$(dirname "$snippets_dir")" ]]; then
mkdir -p "$snippets_dir" 2>/dev/null
else
sudo mkdir -p "$snippets_dir" 2>/dev/null
fi
fi
smart_mkdir "$snippets_dir" 2>/dev/null

local dst="${snippets_dir}/ddos-protection.conf"
if [[ -w "$snippets_dir" ]]; then
cp "$temp_file" "$dst"
else
sudo cp "$temp_file" "$dst"
fi
smart_copy "$temp_file" "$dst"
rm -f "$temp_file"

if [[ -f "$dst" ]]; then
Expand Down
12 changes: 6 additions & 6 deletions lib/features/server-tuning.sh
Original file line number Diff line number Diff line change
Expand Up @@ -210,31 +210,31 @@ feature_apply_custom_server_tuning() {
}' "$nginx_conf" > "$temp_file"

# Smart sudo: only use if file not writable
local use_sudo=""
local SUDO=""
if [[ ! -w "$nginx_conf" ]]; then
use_sudo="sudo"
SUDO="sudo"
fi

# Backup original
$use_sudo cp "$nginx_conf" "${nginx_conf}.tuning-bak"
$SUDO cp "$nginx_conf" "${nginx_conf}.tuning-bak"

# Apply changes
$use_sudo cp "$temp_file" "$nginx_conf"
$SUDO cp "$temp_file" "$nginx_conf"
rm -f "$temp_file"

# Validate
if command -v nginx &>/dev/null; then
if ! nginx -t 2>&1 | grep -q "test is successful\|syntax is ok"; then
# Rollback on failure
$use_sudo mv "${nginx_conf}.tuning-bak" "$nginx_conf"
$SUDO mv "${nginx_conf}.tuning-bak" "$nginx_conf"
if type -t log_warn &>/dev/null; then
log_warn "nginx -t failed after tuning, rolled back"
fi
return 1
fi
fi

$use_sudo rm -f "${nginx_conf}.tuning-bak"
$SUDO rm -f "${nginx_conf}.tuning-bak"

if type -t ui_step_path &>/dev/null; then
ui_step_path "Tuned nginx.conf" "${ram_mb}MB RAM → worker_connections ${worker_conn}"
Expand Down
10 changes: 2 additions & 8 deletions lib/features/upstream-keepalive.sh
Original file line number Diff line number Diff line change
Expand Up @@ -230,13 +230,7 @@ _keepalive_deploy_upstream() {
# Replace the default socket path and keepalive value with tuned ones
sed -e "s|unix:/var/run/php-fpm.sock|${fpm_socket}|g" -e "s|keepalive 16|keepalive ${keepalive_conns}|g" "$src" > "$temp_file"

if type -t smart_copy &>/dev/null; then
smart_copy "$temp_file" "$dst"
elif [[ -w "$confd_dir" ]]; then
cp "$temp_file" "$dst"
else
sudo cp "$temp_file" "$dst"
fi
smart_copy "$temp_file" "$dst"
rm -f "$temp_file"

if [[ -f "$dst" ]]; then
Expand Down Expand Up @@ -334,7 +328,7 @@ _keepalive_inject_sites() {
}
}' "$site_conf" > "$temp_file"

if sudo cp "$temp_file" "$site_conf" 2>/dev/null; then
if smart_copy "$temp_file" "$site_conf" 2>/dev/null; then
if type -t ui_step_path &>/dev/null; then
ui_step_path "Configured site" "$(basename "$site_conf")"
fi
Expand Down
Loading
Loading