function cmd::group::rename() { local name="" new_name="" while [[ $# -gt 0 ]]; do case "$1" in --name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;; --new-name) util::require_flag "--new-name" "${2:-}" || return 1; new_name="$2"; shift 2 ;; --help) cmd::group::help; return ;; *) log::error "Unknown flag: $1"; return 1 ;; esac done [[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1 [[ -z "$new_name" ]] && log::error "Missing required flag: --new-name" && return 1 group::require_exists "$name" || return 1 if group::exists "$new_name"; then log::error "Group already exists: ${new_name}" return 1 fi local old_file new_file old_file="$(group::path "$name")" new_file="$(group::path "$new_name")" # Update name field in file json::set "$old_file" "name" "\"$new_name\"" mv "$old_file" "$new_file" log::wg_success "Group renamed: ${name} → ${new_name}" } function cmd::group::purge_stale() { local name="" force=false all=false local dry_run=false while [[ $# -gt 0 ]]; do case "$1" in --name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;; --force) force=true; shift ;; --all) all=true; shift ;; --dry-run) dry_run=true; shift ;; --help) cmd::group::help; return ;; *) log::error "Unknown flag: $1"; return 1 ;; esac done [[ -z "$name" && "$all" == "false" ]] && \ log::error "Specify --name or --all" && return 1 # Build list of groups to process local -a groups=() if $all; then while IFS= read -r group_file; do groups+=("$(basename "$group_file" .group)") done < <(find "$(ctx::groups)" -name "*.group" 2>/dev/null | sort) else group::require_exists "$name" || return 1 groups=("$name") fi local total_removed=0 total_groups=0 for group_name in "${groups[@]}"; do [[ -z "$group_name" ]] && continue # Find stale peers — in group but no .conf file local -a stale=() while IFS= read -r peer_name; do [[ -z "$peer_name" ]] && continue if [[ ! -f "$(ctx::clients)/${peer_name}.conf" ]]; then stale+=("$peer_name") fi done < <(group::peers "$group_name" 2>/dev/null) [[ ${#stale[@]} -eq 0 ]] && continue (( total_groups++ )) || true if ! $force; then printf " Group '%s' has %d stale peer(s): %s\n" \ "$group_name" "${#stale[@]}" "${stale[*]}" read -r -p " Remove them? [y/N] " confirm case "$confirm" in [yY]*) ;; *) log::info "Skipped '${group_name}'"; continue ;; esac fi local group_file group_file="$(group::path "$group_name")" for peer_name in "${stale[@]}"; do if $dry_run; then printf " \033[2m[dry-run]\033[0m Would remove '%s' from group '%s'\n" \ "$peer_name" "$group_name" else json::remove "$group_file" "peers" "$peer_name" 2>/dev/null || true log::debug "Removed stale peer '${peer_name}' from group '${group_name}'" fi (( total_removed++ )) || true done done local action="Removed" $dry_run && action="Would remove" log::wg_success "${action} ${total_removed} stale peer(s)..." if $all; then if [[ "$total_removed" -eq 0 ]]; then log::wg_warning "No stale peers found in any group" else log::wg_success "${action} ${total_removed} stale peer(s)..." fi fi }