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
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -733,9 +733,11 @@ nginx-optimizer v0.10.0-beta
- Conflict detection (warn if directive already exists) — **blocked on the AST parser**
- Profile system (`--profile conservative|balanced|aggressive`) — not started, and
independent of the parser work
- Partial rollback (undo single feature) — **partly built**: `feature_remove()` deletes
one template file and its include line. It cannot remove multi-template features,
hard-fails on template-less ones, and reverses no in-place edits. See ROADMAP.md.
- Partial rollback (undo single feature) — **mostly built**: `feature_remove()` splits
comma-joined `FEATURE_TEMPLATE` lists and removes every file + include line, and
`feature_remove_custom_*` hooks cover the template-less features (server-tuning,
php-fpm-tuning, redis). Remaining gap: in-place http3 `listen` edits are not
reversed. See ROADMAP.md.

### v1.0.0 - Production Release
- Python crossplane integration for proper nginx config parsing
Expand Down
17 changes: 9 additions & 8 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ input validation, auto-rollback safety, and pre-flight checks.
## v0.10.x - Polish & Robustness

### Commands
- [ ] `remove` command - Cleanly uninstall optimizations
- [x] `remove` command - Cleanly uninstall optimizations
- [ ] `diff` command - Show exact changes before applying
- [x] `doctor` command - Diagnose common issues

Expand Down Expand Up @@ -85,13 +85,14 @@ input validation, auto-rollback safety, and pre-flight checks.

### Features
- [ ] Partial rollback (undo single feature).
**Partly built, further from done than it looks.** `cmd_remove()` delegates to
`feature_remove()`, which only deletes one template file and sed-drops lines
naming it. Three holes: no `feature_remove_custom_*` exists anywhere in the
tree; multi-template features are unremovable because `FEATURE_TEMPLATE` is a
comma-joined string that `feature_remove` never splits (security,
fastcgi-cache); template-less features hard-fail (server-tuning,
php-fpm-tuning, redis). It also reverses no in-place edits at all.
**Mostly built.** `feature_remove()` splits comma-joined `FEATURE_TEMPLATE`
lists (security, fastcgi-cache) and removes every file + include line, and
`feature_remove_custom_*` hooks cover the three template-less features:
server-tuning strips its `worker_*` lines (or restores an interrupted-apply
`.tuning-bak`), php-fpm-tuning restores `.before-tuning` or comments its
`pm.*` lines behind a fresh backup, redis drops the compose service and its
container. Remaining gap: in-place `listen ... quic` edits from http3 are
still not reversed.
- [ ] Config diff visualization
- [x] Missing core optimizations (worker_processes, open_file_cache, sendfile, etc.)
— **shipped** as the `server-tuning` and `open-file-cache` features.
Expand Down
8 changes: 4 additions & 4 deletions docs/PRODUCTION-READINESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,10 +245,10 @@ each, printed on every `test-corpus.sh` run.
- [ ] **`remove` command** - cleanly uninstall optimizations
- [ ] **`doctor` command** - diagnose common issues
- [ ] **`export` command** - export current config as template
- [ ] **Partial rollback** - undo just one feature. *Partly built:* `feature_remove()` deletes one
template file and its include line only. Cannot remove multi-template features (`FEATURE_TEMPLATE`
is comma-joined and never split), hard-fails on template-less ones (server-tuning, php-fpm-tuning,
redis), and reverses no in-place edits. No `feature_remove_custom_*` exists anywhere in the tree.
- [ ] **Partial rollback** - undo just one feature. *Mostly built:* `feature_remove()` splits
comma-joined `FEATURE_TEMPLATE` lists and removes every file + include line, and
`feature_remove_custom_*` hooks cover the template-less features (server-tuning,
php-fpm-tuning, redis). Remaining gap: in-place http3 `listen` edits are not reversed.

### 4.2 Output/Feedback Issues
- [ ] No JSON output mode for tooling integration
Expand Down
104 changes: 104 additions & 0 deletions lib/features/php-fpm-tuning.sh
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,110 @@ feature_apply_custom_php_fpm_tuning() {
return 0
}

################################################################################
# Custom Remove Logic
################################################################################

# Remove PHP-FPM tuning written by feature_apply_custom_php_fpm_tuning.
#
# Apply keeps a pristine copy at ${pool}.before-tuning — restoring it is the
# clean remove. If that backup is gone but the pool still carries our
# signature (uncommented pm.process_idle_timeout / pm.max_requests — stock
# www.conf only ships them commented), the pm.* lines this tool manages are
# commented back out so FPM falls back to its compiled defaults — and only
# after writing a fresh ${pool}.remove-bak. A live pool is never rewritten
# without a backup.
# Args: $1 = target_site (optional, ignored for global feature)
# Returns: 0 on success, 1 if nothing was applied
feature_remove_custom_php_fpm_tuning() {
# shellcheck disable=SC2034 # target_site reserved for API compatibility (global feature)
local target_site="${1:-}"

local pool_file=""
if type -t _fpm_find_pool_config &>/dev/null; then
pool_file=$(_fpm_find_pool_config)
fi

local before_bak="${pool_file}.before-tuning"
local has_before=false has_marker=false
if [[ -n "$pool_file" ]] && [[ -f "$before_bak" ]]; then
has_before=true
fi
if [[ -n "$pool_file" ]] && [[ -f "$pool_file" ]] && \
grep -qE '^[[:space:]]*pm\.(process_idle_timeout|max_requests)[[:space:]]*=' "$pool_file" 2>/dev/null; then
has_marker=true
fi

if [[ "$has_before" == false ]] && [[ "$has_marker" == false ]]; then
if [ "${DRY_RUN:-false}" = true ]; then
return 0
fi
if type -t log_info &>/dev/null; then
log_info "PHP-FPM tuning not applied — nothing to remove"
fi
return 1
fi

if [ "${DRY_RUN:-false}" = true ]; then
if type -t ui_step_path &>/dev/null; then
if [[ "$has_before" == true ]]; then
ui_step_path "Would restore" "$before_bak"
else
ui_step_path "Would comment out pm.* tuning in" "$pool_file"
fi
fi
return 0
fi

local SUDO=""
[[ ! -w "$pool_file" ]] && SUDO="sudo"

if [[ "$has_before" == true ]]; then
if $SUDO cp "$before_bak" "$pool_file"; then
$SUDO rm -f "$before_bak"
if type -t ui_step_path &>/dev/null; then
ui_step_path "Restored" "PHP-FPM pool from ${before_bak}"
fi
return 0
fi
return 1
fi

# Backup missing but our signature is present: back up the live pool
# BEFORE touching it, then comment out the directives this tool manages.
if ! $SUDO cp "$pool_file" "${pool_file}.remove-bak"; then
return 1
fi
$SUDO sed -i.rmback \
-e 's|^[[:space:]]*pm[[:space:]]*=|; &|' \
-e 's|^[[:space:]]*pm\.max_children[[:space:]]*=|; &|' \
-e 's|^[[:space:]]*pm\.start_servers[[:space:]]*=|; &|' \
-e 's|^[[:space:]]*pm\.min_spare_servers[[:space:]]*=|; &|' \
-e 's|^[[:space:]]*pm\.max_spare_servers[[:space:]]*=|; &|' \
-e 's|^[[:space:]]*pm\.process_idle_timeout[[:space:]]*=|; &|' \
-e 's|^[[:space:]]*pm\.max_requests[[:space:]]*=|; &|' \
"$pool_file" 2>/dev/null || \
$SUDO sed -i '' \
-e 's|^[[:space:]]*pm[[:space:]]*=|; &|' \
-e 's|^[[:space:]]*pm\.max_children[[:space:]]*=|; &|' \
-e 's|^[[:space:]]*pm\.start_servers[[:space:]]*=|; &|' \
-e 's|^[[:space:]]*pm\.min_spare_servers[[:space:]]*=|; &|' \
-e 's|^[[:space:]]*pm\.max_spare_servers[[:space:]]*=|; &|' \
-e 's|^[[:space:]]*pm\.process_idle_timeout[[:space:]]*=|; &|' \
-e 's|^[[:space:]]*pm\.max_requests[[:space:]]*=|; &|' \
"$pool_file" 2>/dev/null
$SUDO rm -f "${pool_file}.rmback" 2>/dev/null

if type -t ui_step_path &>/dev/null; then
ui_step_path "Reverted PHP-FPM tuning" "${pool_file} (backup: ${pool_file}.remove-bak)"
fi
if type -t log_to_file &>/dev/null; then
log_to_file "INFO" "PHP-FPM tuning commented out; pre-remove copy kept at ${pool_file}.remove-bak"
fi

return 0
}

################################################################################
# Helper Functions
################################################################################
Expand Down
123 changes: 123 additions & 0 deletions lib/features/redis.sh
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,129 @@ _redis_system_show_wp_config() {
fi
}

################################################################################
# Custom Remove
################################################################################

# Remove the Redis pieces feature_apply_custom_redis wrote:
# wp-test — the `redis:` service block in each site's docker-compose.yml and
# the redis-<site> container (named via container_name).
# system — left running on purpose: if redis-server was already up when
# apply ran it is not ours to stop, and no marker tells the two
# apart. Reported instead.
# Args: $1 = target_site (optional)
# Returns: 0 on success/dry-run, 1 if nothing was applied
feature_remove_custom_redis() {
local target_site="${1:-}"

if type -t log_to_file &>/dev/null; then
log_to_file "INFO" "Removing Redis Object Cache..."
fi

local removed=false
local wp_test_sites="${WP_TEST_SITES:-$HOME/.wp-test/sites}"

if [ -d "$wp_test_sites" ]; then
if [ -n "$target_site" ] && [ -d "$wp_test_sites/$target_site" ]; then
_redis_remove_site "$target_site" && removed=true
elif [ -z "$target_site" ]; then
local site_dir site
for site_dir in "$wp_test_sites"/*; do
[ -d "$site_dir" ] || continue
site=$(basename "$site_dir")
_redis_remove_site "$site" && removed=true
done
fi
fi

if command -v redis-cli &>/dev/null && redis-cli ping &>/dev/null 2>&1; then
if [ "${DRY_RUN:-false}" = true ]; then
if type -t ui_step &>/dev/null; then
ui_step "Would leave system Redis running (may predate this tool)"
fi
elif type -t log_info &>/dev/null; then
log_info "System Redis left running — stop manually if this tool installed it"
fi
fi

if [ "${DRY_RUN:-false}" = true ]; then
return 0
fi
[ "$removed" = true ]
}

# Remove the redis service block from one wp-test site's docker-compose.yml
# and delete its container.
# Args: $1 = site name
# Returns: 0 if the service was present (removed, or would-be in dry-run)
_redis_remove_site() {
local site="$1"
local wp_test_sites="${WP_TEST_SITES:-$HOME/.wp-test/sites}"
local compose_file="$wp_test_sites/$site/docker-compose.yml"

[ -f "$compose_file" ] || return 1
grep -qE '^[[:space:]]*redis:[[:space:]]*$' "$compose_file" 2>/dev/null || return 1

if [ "${DRY_RUN:-false}" = true ]; then
if type -t ui_step_path &>/dev/null; then
ui_step_path "Would remove Redis service from" "$site/docker-compose.yml"
ui_step_path "Would remove container" "redis-${site}"
fi
return 0
fi

# Remove the container this tool added (container_name: redis-<site>)
if command -v docker &>/dev/null; then
docker rm -f "redis-${site}" >/dev/null 2>&1 || true
fi

cp "$compose_file" "${compose_file}.remove-bak" || return 1

# Drop the `redis:` service block: every following line indented deeper
# than the service key belongs to it.
local temp_file
temp_file=$(mktemp)
awk '
/^[[:space:]]*redis:[[:space:]]*$/ {
bind = match($0, /[^[:space:]]/) - 1
inblock = 1
next
}
inblock {
if ($0 ~ /^[[:space:]]*$/) next
if (match($0, /[^[:space:]]/) - 1 > bind) next
inblock = 0
}
{ print }
' "$compose_file" > "$temp_file"

# Validate the resulting YAML when docker-compose is available, same as
# safe_add_docker_service does on the apply path
if command -v docker-compose &>/dev/null; then
if ! docker-compose -f "$temp_file" config -q >/dev/null 2>&1; then
rm -f "$temp_file"
if type -t log_error &>/dev/null; then
log_error "Compose validation failed after removing redis — kept ${compose_file}.remove-bak"
fi
return 1
fi
fi

if cp "$temp_file" "$compose_file"; then
rm -f "$temp_file" "${compose_file}.remove-bak"
if type -t ui_step_path &>/dev/null; then
ui_step_path "Removed Redis service from" "$site"
fi
if type -t log_to_file &>/dev/null; then
log_to_file "INFO" "Removed redis service from $site (wp-config.php Redis defines should be removed manually)"
fi
return 0
fi

rm -f "$temp_file"
return 1
}

################################################################################
# Helper Functions
################################################################################
Expand Down
Loading
Loading