Compare commits

...

3 Commits

Author SHA1 Message Date
n0mad1k ef3d556b6d Isolate mock_paths fixture from host MOUNT_ROOT config; simplify probe_target mount check
mock_paths patched TARGET_DIR/PENDING_DIR/etc but not MOUNT_ROOT, so tests leaked the host's real ~/pCloudDrive mount_root config and failed probe_target's mountpoint check. Also drop redundant equality clause since is_relative_to already covers equal paths, and document the mount precondition.
2026-07-10 14:40:36 -04:00
n0mad1k 22b1415b43 Guard probe_target against creating phantom mountpoint dir
probe_target() unconditionally ran mkdir(parents=True) on TARGET_DIR. When
TARGET_DIR sits under an unmounted FUSE drive, this materialized the mount
root (e.g. ~/pCloudDrive) as a plain local directory, occupying the
mountpoint and blocking the pCloud client from ever mounting.

Add optional mount_root config key (env COE_MIRROR_MOUNT_ROOT). When set,
probe_target requires the path to be a live mountpoint and TARGET_DIR to be
under it before any mkdir; otherwise it returns False and the run queues to
pending. Unset preserves prior behavior for local targets.
2026-07-10 14:37:40 -04:00
n0mad1k 814a2b97e2 Guard probe_target against creating phantom local dirs when mount_root is unmounted 2026-07-10 14:36:20 -04:00
3 changed files with 73 additions and 1 deletions
+19 -1
View File
@@ -81,6 +81,8 @@ PENDING_DIR: Path = Path(
str(Path.home() / ".local" / "share" / "coe-mirror" / "pending"),
)
).expanduser()
_MOUNT_ROOT_VAL = _resolve("COE_MIRROR_MOUNT_ROOT", _CFG.get("mount_root"), "")
MOUNT_ROOT: Optional[Path] = Path(_MOUNT_ROOT_VAL).expanduser() if _MOUNT_ROOT_VAL else None
LOG_DIR = Path.home() / ".local" / "share" / "coe-mirror"
LOG_FILE = LOG_DIR / "coe-mirror.log"
LOCK_NAME = "coe-mirror.lock"
@@ -166,8 +168,19 @@ def release_lock(lock_fd: int) -> None:
def probe_target() -> bool:
"""Probe target directory health. Write tiny file, read back, delete. Return True if healthy."""
"""Probe target directory health. Write tiny file, read back, delete. Return True if healthy.
When MOUNT_ROOT is configured, first verify it is a live mountpoint and TARGET_DIR is under it; return False (queue to pending) otherwise.
"""
try:
if MOUNT_ROOT is not None:
if not MOUNT_ROOT.exists() or not os.path.ismount(str(MOUNT_ROOT)):
logger.warning("mount_root %s is not a live mountpoint; target unavailable; queuing locally", MOUNT_ROOT)
return False
if not TARGET_DIR.is_relative_to(MOUNT_ROOT):
logger.warning("target_dir %s is not under configured mount_root %s; misconfig; queuing locally", TARGET_DIR, MOUNT_ROOT)
return False
probe_file = TARGET_DIR / ".write_probe"
TARGET_DIR.mkdir(parents=True, exist_ok=True)
@@ -673,6 +686,11 @@ api_base = "https://git.churchofmalware.org"
# Where mirrored zips are stored. One subdirectory per owner is created here.
target_dir = "~/coe-mirror"
# Optional: require this path to be a live mountpoint before writing. If set and
# not currently mounted, the run queues to pending instead of creating a phantom
# local directory at the mountpoint. Leave blank to disable.
# mount_root = "~/pCloudDrive"
# Optional: local staging when target_dir is unavailable.
# pending_dir = "~/.local/share/coe-mirror/pending"
+8
View File
@@ -20,3 +20,11 @@
- Env overrides: `COE_MIRROR_MATRIX_ROOM`, `COE_MIRROR_MATRIX_HOMESERVER`.
- Pre-deploy step: invite `@intel-bot:m.mealeyfamily.com` to General once Synapse is back. Without joined membership, sends will 403.
- DevTrack: #1172.
## 2026-07-10 — mount_root guard (phantom pCloudDrive fix)
- **Bug**: `probe_target()` did `TARGET_DIR.mkdir(parents=True, exist_ok=True)` unconditionally. When pCloud FUSE was unmounted, this created `~/pCloudDrive/...` as plain LOCAL dirs, occupying the mountpoint and blocking the pCloud client from mounting. Live state on carbon confirmed: `~/pCloudDrive` was a plain dir, not a mountpoint, with 22 stray zips inside.
- **Fix**: new optional config key `mount_root` (env `COE_MIRROR_MOUNT_ROOT`). When set, `probe_target()` requires `os.path.ismount(mount_root)` AND that `TARGET_DIR` is under `mount_root` before any mkdir; otherwise returns False (queues to pending). Never creates the mount root. Empty/unset = old behavior (local targets like `~/coe-mirror`).
- Local config sets `mount_root = "~/pCloudDrive"`.
- **Recovery run**: stopped/masked timer, staged the 22 zips aside, removed the local `~/pCloudDrive`, relaunched pCloud, verified mountpoint, moved zips onto the live mount, unmasked timer.
- DevTrack: #1307.
+46
View File
@@ -37,6 +37,7 @@ def mock_paths(tmp_path, monkeypatch):
pending_dir.mkdir()
log_dir.mkdir()
monkeypatch.setattr(coe_mirror, "MOUNT_ROOT", None)
monkeypatch.setattr(coe_mirror, "TARGET_DIR", target_dir)
monkeypatch.setattr(coe_mirror, "PENDING_DIR", pending_dir)
monkeypatch.setattr(coe_mirror, "LOG_DIR", log_dir)
@@ -595,6 +596,51 @@ class TestPlaceIntoPloudSameFileIsNoop:
assert test_file.stat().st_size > 0
class TestMountRootGuard:
"""Test probe_target() mount-root verification (phantom-dir regression guard)."""
def test_no_mount_root_configured_unchanged_behavior(self, mock_paths, setup_logging, monkeypatch):
"""MOUNT_ROOT None + writable TARGET_DIR -> True (unchanged behavior)."""
monkeypatch.setattr(coe_mirror, "MOUNT_ROOT", None)
assert coe_mirror.probe_target() is True
def test_mount_root_live_target_under_it_returns_true(self, mock_paths, setup_logging, monkeypatch, tmp_path):
"""MOUNT_ROOT set, live mount, TARGET_DIR a subdir of it -> True."""
mount_root = tmp_path / "pCloudDrive"
mount_root.mkdir()
target_dir = mount_root / "Hacking" / "ChurchOfMalware"
monkeypatch.setattr(coe_mirror, "MOUNT_ROOT", mount_root)
monkeypatch.setattr(coe_mirror, "TARGET_DIR", target_dir)
monkeypatch.setattr(coe_mirror.os.path, "ismount", lambda p: True)
assert coe_mirror.probe_target() is True
def test_mount_not_live_does_not_create_phantom_dir(self, mock_paths, setup_logging, monkeypatch, tmp_path):
"""MOUNT_ROOT set but not mounted -> False, and TARGET_DIR is never created."""
mount_root = tmp_path / "pCloudDrive"
mount_root.mkdir()
target_dir = mount_root / "Hacking" / "ChurchOfMalware"
monkeypatch.setattr(coe_mirror, "MOUNT_ROOT", mount_root)
monkeypatch.setattr(coe_mirror, "TARGET_DIR", target_dir)
monkeypatch.setattr(coe_mirror.os.path, "ismount", lambda p: False)
assert coe_mirror.probe_target() is False
assert not target_dir.exists()
def test_target_dir_not_under_mount_root_returns_false(self, mock_paths, setup_logging, monkeypatch, tmp_path):
"""MOUNT_ROOT set, live mount, but TARGET_DIR is unrelated -> False, no dir created."""
mount_root = tmp_path / "pCloudDrive"
mount_root.mkdir()
target_dir = tmp_path / "unrelated" / "target"
monkeypatch.setattr(coe_mirror, "MOUNT_ROOT", mount_root)
monkeypatch.setattr(coe_mirror, "TARGET_DIR", target_dir)
monkeypatch.setattr(coe_mirror.os.path, "ismount", lambda p: True)
assert coe_mirror.probe_target() is False
assert not target_dir.exists()
# Tests for Matrix notification implementation.
# Note: Tests requiring monkeypatched subprocess.run must use raising=False because
# subprocess.run is not a class attribute on the coe_mirror module by default