Updated CLEANUP_TODO.md with comprehensive documentation of all 8 critical issues identified by Codex review (conv-1763166192078-1076) and their resolutions. Key updates: - Added detailed problem/fix/impact for each issue - Updated status to 'Codex review complete, ready for deployment testing' - Documented all commits in implementation history - Added Codex review summary section - Marked phases 1-6 as complete, phase 7 (testing) as pending This provides complete audit trail of cleanup implementation work.
10 KiB
Node Cleanup Implementation - CODEX REVIEW COMPLETE
Status: ✅ Implementation complete, Codex review passed, ready for deployment testing.
Latest Update: Addressed all 8 critical issues found by Codex review (conv-1763166192078-1076).
Implementation History
Commit b192c60e9: Relocate binaries to /opt/pulse/sensor-proxy/
- ✅ Binary path moved from /usr/local/bin to /opt/pulse/sensor-proxy/bin
- ✅ All systemd ExecStart paths updated
- ✅ Guarantees cleanup works on read-only /usr systems
Commit 6692228e0: Full cleanup script implementation
- ✅ SSH key removal (working)
- ✅ Service uninstallation (via isolated systemd-run transient unit)
- ✅ API token deletion (JSON first, filtered table fallback)
- ✅ Bind mount removal (scans all LXC configs)
- ✅ pulse-monitor user deletion
- ✅ flock serialization (prevents concurrent runs)
- ✅ Immediate request file deletion (prevents loops)
Commits ed48d7555, 17d2e6876: Bug fixes during testing
- ✅ Fixed directory creation order (create before binary download)
- ✅ Fixed SHARE_DIR unbound variable
Commit bcd8d4e0f: Critical Codex review fixes #1-4
- ✅ Host detection now includes hostname/FQDN (not just IP)
- ✅ Systemd sandbox relaxed (/etc/pve and /etc/systemd/system writable)
- ✅ Uninstaller called with --purge flag for complete removal
- ✅ All /usr/local references migrated to /opt paths
- ✅ UUID used for transient unit names (prevents collisions)
- ✅ --wait and --collect flags capture uninstaller exit code
Commit fe53d6473: Remaining Codex review fixes #5-8
- ✅ LXC bind mounts removed via
pct set -delete(not sed) - ✅ API token parsing: three-tier fallback (pveum JSON → pvesh JSON → table)
- ✅ Retry logic: rename to .processing, delete only on success
Issues Discovered and Resolved
1. Host Detection Failure (CRITICAL) - ✅ FIXED
Problem: Cleanup script only compared against IPs from hostname -I, missing nodes configured as https://hostname:8006. This caused localhost cleanup (API tokens, bind mounts, service removal) to be skipped entirely.
Fix: Check against hostname, hostname -f, and all IPs. Now catches all localhost variations.
Impact: Without this fix, cleanup would only remove remote SSH keys, leaving services/binaries/tokens intact.
2. Systemd Sandbox Blocked Critical Operations - ✅ FIXED
Problem: Cleanup service ran with ProtectSystem=strict and ReadWritePaths=/var/lib/pulse-sensor-proxy /root/.ssh, blocking writes to /etc/pve (Proxmox configs) and /etc/systemd/system.
Fix: Added /etc/pve and /etc/systemd/system to ReadWritePaths.
Impact: pveum token deletion and pct set -delete would fail with "read-only file system".
3. Incomplete Purging - ✅ FIXED
Problem: Uninstaller called without --purge, leaving /var/lib/pulse-sensor-proxy, service user, and SSH private keys on disk. Request file deleted before work completed, preventing retry on failure.
Fix: Added --purge flag, added --wait --collect to capture exit code, fail cleanup if uninstaller fails.
Impact: Claimed "cleanup completed successfully" even when artifacts remained.
4. Incomplete Path Migration - ✅ FIXED
Problem: After relocating binaries to /opt, three references still pointed to /usr/local:
- Forced command in SSH authorized_keys:
/usr/local/bin/pulse-sensor-wrapper.sh - Self-heal script:
/usr/local/share/pulse/install-sensor-proxy.sh - Backend removal helpers:
/usr/local/bin/pulse-sensor-cleanup.sh
Fix: Updated all three locations. Go backend now checks both paths (new + legacy).
Impact: New installs would lose telemetry immediately (SSH command not found). UI-triggered cleanup wouldn't find helpers.
5. Transient Unit Name Collisions - ✅ FIXED
Problem: Used date +%s for unit names. If two cleanup requests fired within same second, second would fail (unit already exists). Error suppressed, logged as "started" anyway.
Fix: Use /proc/sys/kernel/random/uuid for unique names.
Impact: Multiple concurrent cleanups could race, with silent failures.
6. Bind Mount Removal Too Broad - ✅ FIXED
Problem: Used sed -i '/pulse-sensor-proxy/d' which would delete ANY line mentioning the substring (including unrelated comments/hooks). Also couldn't run inside systemd sandbox.
Fix: Use pct set <ctid> -delete mp<N> which validates syntax and is sandbox-compatible.
Impact: Could break container configs. Sed approach would fail anyway due to sandbox.
7. API Token Parsing Fragile - ✅ FIXED
Problem: Table parser filtered only │┌└╞, failing on other Unicode borders or locales. "User not found" vs "feature unsupported" both treated as "no tokens".
Fix: Three-tier fallback:
pveum --output-format jsonwith python3 parsingpvesh get /access/users/pulse-monitor@pam/token(always JSON)- Improved table parser with better filtering
Impact: Non-English locales or Proxmox versions with different table formatting would silently skip token cleanup.
8. No Retry on Failure - ✅ FIXED
Problem: Request file deleted immediately. Any crash left no way to retry.
Fix: Rename to .processing, delete only on success. Failures leave .processing file for manual investigation/retry.
Impact: Transient failures (network issues, systemd hiccups) couldn't be retried automatically.
Read-Only Filesystem
Problem: Proxmox VE can mount /usr as read-only (hardened setups, boot-from-snapshots, appliance builds).
Solution: All binaries relocated to /opt/pulse/sensor-proxy/ where we control permissions.
Process Isolation During Uninstall
Problem: Cleanup script runs as systemd service. When it calls the uninstaller which stops the proxy service, systemd kills the cleanup service with SIGTERM.
Solution: Use transient systemd unit with:
- UUID-based unique name
Conflicts=pulse-sensor-proxy.service--wait --collectto capture exit code--purgeflag for complete removal
Implementation Status
Phase 1: Relocate Binaries ✅ COMPLETE
- ✅ Update installer to use
/opt/pulse/sensor-proxy/bin/ - ✅ Update systemd unit ExecStart path
- ✅ Update cleanup script to use new paths
- ✅ Update forced command in SSH authorized_keys
- ✅ Update self-heal script paths
- ✅ Update Go backend removal helpers (supports both new and legacy paths)
Phase 2: Fix Uninstall Orchestration ✅ COMPLETE
- ✅ Cleanup script spawns transient systemd unit via systemd-run
- ✅ Added
Conflicts=pulse-sensor-proxy.serviceto transient unit - ✅ UUID-based unit names prevent collisions
- ✅ Added
--wait --collectto capture exit code - ✅ Uninstaller runs with
--purge --quietflags - ✅ Cleanup fails if uninstaller exits non-zero
Phase 3: Prevent Cleanup Loops ✅ COMPLETE
- ✅ Added
flockserialization via exec 200>lockfile - ✅ Rename request file to
.processing(allows retry on failure) - ✅ Delete
.processingonly on successful completion - ✅
.pathunit uses PathChanged/PathModified (correct semantics) - ✅ Comprehensive logging at info/warn/error levels
Phase 4: Improve API Token Handling ✅ COMPLETE
- ✅ Three-tier fallback: pveum JSON → pvesh JSON → table parsing
- ✅ Python3 JSON parsing for robustness
- ✅ Better table filtering (handles more Unicode characters)
- ✅ Error handling for token deletion failures (logs warnings, continues)
- ✅ Logs each token removal attempt
Phase 5: LXC Bind Mount Removal ✅ COMPLETE
- ✅ Use
pct set -deleteinstead of sed (validates syntax) - ✅ Sandbox-compatible (works with ProtectSystem=strict + /etc/pve writable)
- ✅ Only removes mounts containing "pulse-sensor-proxy"
Phase 6: Host Detection ✅ COMPLETE
- ✅ Check against hostname, FQDN, and all local IPs
- ✅ Localhost detection works for
https://hostname:8006URLs - ✅ Ensures full cleanup runs for all localhost variations
Phase 7: Testing & Validation ⏸️ PENDING
- ⏸️ Deploy updated installer to test host
- ⏸️ Test node removal via Pulse UI
- ⏸️ Verify complete cleanup:
- ⏸️ No systemd units
- ⏸️ No binaries in /opt or /usr/local
- ⏸️ No bind mounts in LXC configs
- ⏸️ No API tokens or pulse-monitor user
- ⏸️ No SSH keys in authorized_keys
- ⏸️ No /var/lib/pulse-sensor-proxy directory
- ⏸️ Test retry logic (simulate failure, verify .processing file persists)
- ⏸️ Test on fresh Proxmox VE install
- ⏸️ Test on hardened PVE with read-only
/usr - ⏸️ Test cluster vs standalone scenarios
Codex Review Summary
Review Session: conv-1763166192078-1076
Findings: 8 critical issues identified:
- ❌ Host detection only checked IPs, skipped cleanup for hostname-based URLs
- ❌ Systemd sandbox blocked /etc/pve and /etc/systemd writes
- ❌ Uninstaller missing --purge, request file deleted too early
- ❌ Incomplete /usr/local → /opt migration (SSH forced command, self-heal, backend)
- ❌ Timestamp-based unit names caused collisions
- ❌ Brittle sed-based bind mount removal, Unicode table parsing
- ❌ API token parsing failed on locales, couldn't distinguish error types
- ❌ No retry mechanism for transient failures
Resolution: All 8 issues fixed in commits bcd8d4e0f and fe53d6473.
References
- Initial commit:
ed65fda74"Extend node cleanup to fully remove Pulse footprint" - Binary relocation:
b192c60e9"Relocate binaries to /opt/pulse/sensor-proxy/" - Full implementation:
6692228e0"Full cleanup script implementation" - Bug fixes:
ed48d7555,17d2e6876 - Codex review fixes #1-4:
bcd8d4e0f - Codex review fixes #5-8:
fe53d6473 - Codex review: conv-1763166192078-1076
- Related files:
scripts/install-sensor-proxy.sh(installer + cleanup script generation)internal/api/config_handlers.go(triggerPVEHostCleanup + manual removal)cmd/pulse-sensor-proxy/cleanup.go(handleRequestCleanup)
Priority
High: Implementation complete and Codex-reviewed. Ready for deployment testing. All critical security and correctness issues addressed.