|
46 | 46 | # File in the overlay base that records which version was materialized. |
47 | 47 | # Used to detect version changes that require re-materialization. |
48 | 48 | _VERSION_MARKER = "materialized_version" |
| 49 | +_SITE_INODE_MARKER = "site_inode" |
49 | 50 |
|
50 | 51 |
|
51 | 52 | def _run_omd_via_sudo(site_name: str, command: str) -> None: |
@@ -74,6 +75,46 @@ def _site_overlay_dir(site_root: Path) -> Path: |
74 | 75 | return _OVERLAY_BASE / site_root.name |
75 | 76 |
|
76 | 77 |
|
| 78 | +def _wipe_stale_overlay(site_root: Path) -> bool: |
| 79 | + """Detect and wipe stale overlay data from a previous site installation. |
| 80 | +
|
| 81 | + Compares the current inode of the site root directory against the stored |
| 82 | + value. When they differ, the site was reinstalled (``omd rm`` + ``omd |
| 83 | + create``), so the old upper/work/markers are stale and must be removed. |
| 84 | +
|
| 85 | + Returns True if stale data was wiped. |
| 86 | + """ |
| 87 | + site_overlay = _site_overlay_dir(site_root) |
| 88 | + marker = site_overlay / _SITE_INODE_MARKER |
| 89 | + if not marker.is_file(): |
| 90 | + return False |
| 91 | + try: |
| 92 | + stored_ino = int(marker.read_text().strip()) |
| 93 | + except (ValueError, OSError): |
| 94 | + return False |
| 95 | + current_ino = site_root.stat().st_ino |
| 96 | + if current_ino == stored_ino: |
| 97 | + return False |
| 98 | + |
| 99 | + output.warn( |
| 100 | + f"Site inode changed ({stored_ino} -> {current_ino}), " |
| 101 | + "wiping stale overlay data from previous installation..." |
| 102 | + ) |
| 103 | + # Remove everything except the site overlay base dir itself |
| 104 | + for child in site_overlay.iterdir(): |
| 105 | + if child.is_dir(): |
| 106 | + run_as_root(["rm", "-rf", str(child)]) |
| 107 | + else: |
| 108 | + child.unlink(missing_ok=True) |
| 109 | + return True |
| 110 | + |
| 111 | + |
| 112 | +def _save_site_inode(site_root: Path, inode: int) -> None: |
| 113 | + """Persist the site root directory's inode for reinstall detection.""" |
| 114 | + marker = _site_overlay_dir(site_root) / _SITE_INODE_MARKER |
| 115 | + marker.write_text(str(inode)) |
| 116 | + |
| 117 | + |
77 | 118 | def _ensure_overlay_dirs(site_overlay: Path) -> None: |
78 | 119 | """Ensure the site overlay directory exists and is writable by the deploy user. |
79 | 120 |
|
@@ -352,6 +393,13 @@ def ensure_overlay(site_root: Path, state: SSHState) -> None: |
352 | 393 | # subsequent file operations (mkdir, marker writes) work without root. |
353 | 394 | _ensure_overlay_dirs(site_overlay) |
354 | 395 |
|
| 396 | + # Detect site reinstall: if the site root inode changed, the old overlay |
| 397 | + # data is stale and must be wiped before proceeding. |
| 398 | + _wipe_stale_overlay(site_root) |
| 399 | + |
| 400 | + # Capture the bare site inode before mounting (overlay changes st_ino). |
| 401 | + bare_site_inode = site_root.stat().st_ino |
| 402 | + |
355 | 403 | resuming = upper.exists() and any(upper.iterdir()) |
356 | 404 |
|
357 | 405 | # Create directories |
@@ -417,6 +465,10 @@ def ensure_overlay(site_root: Path, state: SSHState) -> None: |
417 | 465 | output.info(f"Starting site {site_name}...") |
418 | 466 | _run_omd_via_sudo(site_name, "start") |
419 | 467 |
|
| 468 | + # Persist the bare site inode (captured before mount) so we can detect |
| 469 | + # reinstalls on the next run. |
| 470 | + _save_site_inode(site_root, bare_site_inode) |
| 471 | + |
420 | 472 | if resuming: |
421 | 473 | output.info(f"Overlay resumed on {site_root} (existing changes preserved)") |
422 | 474 | else: |
|
0 commit comments