mirror of
https://github.com/trustedsec/hate_crack.git
synced 2026-06-12 11:01:40 -07:00
fix(docker): fix E2E torrent test and transmission-remote compatibility
- Add transmission-cli package (provides transmission-remote binary) - Fix SETUPTOOLS_SCM_PRETEND_VERSION placement before RUN make install - Fix PATH to use /root/.local/bin and include venv bin - Switch TransmissionSession.add() from watch-dir polling to transmission-remote -a - Remove --no-auth flag (unrecognized in transmission-remote 4.1.0) - Add test_docker_torrent_downloads_wordlists E2E test Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+4
-1
@@ -13,6 +13,7 @@ RUN apt-get update \
|
||||
ocl-icd-libopencl1 \
|
||||
pocl-opencl-icd \
|
||||
p7zip-full \
|
||||
transmission-cli \
|
||||
transmission-daemon \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
@@ -20,9 +21,11 @@ RUN python -m pip install -q uv==0.9.28
|
||||
|
||||
COPY . /workspace
|
||||
|
||||
ENV SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0
|
||||
|
||||
RUN make install
|
||||
|
||||
ENV PATH="${HOME}/.local/bin:${PATH}"
|
||||
ENV PATH="/workspace/.venv/bin:/root/.local/bin:${PATH}"
|
||||
ENV HATE_CRACK_SKIP_INIT=1
|
||||
|
||||
CMD ["bash", "-lc", "${HOME}/.local/bin/hate_crack --help >/tmp/hc_help.txt && ./hate_crack.py --help >/tmp/hc_script_help.txt"]
|
||||
|
||||
+26
-16
@@ -259,7 +259,6 @@ class TransmissionSession:
|
||||
self.startup_timeout = startup_timeout
|
||||
self.shutdown_timeout = shutdown_timeout
|
||||
self._cfg_dir = ""
|
||||
self._watch_dir = ""
|
||||
self._port = 0
|
||||
self._rpc = ""
|
||||
self._proc = None
|
||||
@@ -270,8 +269,6 @@ class TransmissionSession:
|
||||
import subprocess
|
||||
|
||||
self._cfg_dir = tempfile.mkdtemp(prefix="hate_crack_transmission_")
|
||||
self._watch_dir = os.path.join(self._cfg_dir, "watch")
|
||||
os.makedirs(self._watch_dir, exist_ok=True)
|
||||
self._port = _pick_free_port()
|
||||
self._rpc = f"127.0.0.1:{self._port}"
|
||||
self._proc = subprocess.Popen(
|
||||
@@ -288,8 +285,7 @@ class TransmissionSession:
|
||||
"--download-dir",
|
||||
self.save_dir,
|
||||
"--no-portmap",
|
||||
"--watch-dir",
|
||||
self._watch_dir,
|
||||
"--no-watch-dir",
|
||||
],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
@@ -345,22 +341,38 @@ class TransmissionSession:
|
||||
return None
|
||||
|
||||
def add(self, torrent_path: str) -> int:
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
before_ids = {e["id"] for e in self.list()}
|
||||
shutil.copy2(torrent_path, self._watch_dir)
|
||||
deadline = time.monotonic() + 30.0
|
||||
while time.monotonic() < deadline:
|
||||
after_entries = self.list()
|
||||
new_ids = [e["id"] for e in after_entries if e["id"] not in before_ids]
|
||||
if new_ids:
|
||||
return new_ids[0]
|
||||
time.sleep(0.5)
|
||||
result = subprocess.run(
|
||||
[
|
||||
"transmission-remote",
|
||||
self._rpc,
|
||||
"-a",
|
||||
torrent_path,
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
out = result.stdout or ""
|
||||
m = re.search(r"Added torrent.*\n.*ID:\s*(\d+)", out)
|
||||
if m:
|
||||
return int(m.group(1))
|
||||
m = re.search(r"torrent added\s*\(id\s+(\d+)\)", out, re.IGNORECASE)
|
||||
if m:
|
||||
return int(m.group(1))
|
||||
after_entries = self.list()
|
||||
new_ids = [e["id"] for e in after_entries if e["id"] not in before_ids]
|
||||
if new_ids:
|
||||
return new_ids[0]
|
||||
raise RuntimeError(f"Failed to add torrent: {torrent_path}")
|
||||
|
||||
def list(self) -> list:
|
||||
import subprocess
|
||||
|
||||
result = subprocess.run(
|
||||
["transmission-remote", self._rpc, "--no-auth", "-l"],
|
||||
["transmission-remote", self._rpc, "-l"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
@@ -417,7 +429,6 @@ class TransmissionSession:
|
||||
[
|
||||
"transmission-remote",
|
||||
self._rpc,
|
||||
"--no-auth",
|
||||
f"-t{torrent_id}",
|
||||
"--info-files",
|
||||
],
|
||||
@@ -461,7 +472,6 @@ class TransmissionSession:
|
||||
[
|
||||
"transmission-remote",
|
||||
self._rpc,
|
||||
"--no-auth",
|
||||
f"-t{torrent_id}",
|
||||
"--remove",
|
||||
],
|
||||
|
||||
+20
-23
@@ -140,44 +140,41 @@ class TestTransmissionSession:
|
||||
with pytest.raises(RuntimeError, match="Transmission daemon failed"):
|
||||
ts.__enter__()
|
||||
|
||||
def test_add_copies_to_watch_dir_and_returns_new_id(self, tmp_path):
|
||||
def test_add_uses_transmission_remote_and_returns_new_id(self, tmp_path):
|
||||
ts = TransmissionSession(str(tmp_path))
|
||||
ts._rpc = "127.0.0.1:9999"
|
||||
ts._watch_dir = str(tmp_path / "watch")
|
||||
# Before: IDs 3 and 5. After first poll: ID 7 appears.
|
||||
# Before: IDs 3 and 5. After add: ID 7 appears.
|
||||
list_calls = iter([
|
||||
[{"id": 3}, {"id": 5}],
|
||||
[{"id": 3}, {"id": 5}, {"id": 7}],
|
||||
])
|
||||
with patch("shutil.copy2"), patch.object(ts, "list", side_effect=list_calls), \
|
||||
patch("time.sleep"), patch("time.monotonic", side_effect=[0.0, 1.0]):
|
||||
run_result = MagicMock(returncode=0, stdout="", stderr="")
|
||||
with patch("subprocess.run", return_value=run_result), \
|
||||
patch.object(ts, "list", side_effect=list_calls):
|
||||
tid = ts.add("/tmp/foo.torrent")
|
||||
assert tid == 7
|
||||
|
||||
def test_add_polls_until_daemon_picks_up_torrent(self, tmp_path):
|
||||
def test_add_parses_id_from_output(self, tmp_path):
|
||||
ts = TransmissionSession(str(tmp_path))
|
||||
ts._rpc = "127.0.0.1:9999"
|
||||
ts._watch_dir = str(tmp_path / "watch")
|
||||
# First two polls: no new ID. Third poll: ID 9 appears.
|
||||
list_calls = iter([
|
||||
[{"id": 1}],
|
||||
[{"id": 1}],
|
||||
[{"id": 1}],
|
||||
[{"id": 1}, {"id": 9}],
|
||||
])
|
||||
monotonic_vals = iter([0.0, 1.0, 2.0, 3.0, 4.0])
|
||||
with patch("shutil.copy2"), patch.object(ts, "list", side_effect=list_calls), \
|
||||
patch("time.sleep"), patch("time.monotonic", side_effect=monotonic_vals):
|
||||
before_list = [{"id": 1}]
|
||||
run_result = MagicMock(
|
||||
returncode=0,
|
||||
stdout="torrent added (id 42)\n",
|
||||
stderr="",
|
||||
)
|
||||
with patch("subprocess.run", return_value=run_result), \
|
||||
patch.object(ts, "list", return_value=before_list):
|
||||
tid = ts.add("/tmp/foo.torrent")
|
||||
assert tid == 9
|
||||
assert tid == 42
|
||||
|
||||
def test_add_raises_on_timeout(self, tmp_path):
|
||||
def test_add_raises_when_torrent_not_added(self, tmp_path):
|
||||
ts = TransmissionSession(str(tmp_path))
|
||||
ts._rpc = "127.0.0.1:9999"
|
||||
ts._watch_dir = str(tmp_path / "watch")
|
||||
# list never returns a new ID; monotonic jumps past the 10s deadline.
|
||||
with patch("shutil.copy2"), patch.object(ts, "list", return_value=[{"id": 1}]), \
|
||||
patch("time.sleep"), patch("time.monotonic", side_effect=[0.0, 100.0]):
|
||||
# list returns the same IDs before and after; output has no ID.
|
||||
run_result = MagicMock(returncode=1, stdout="", stderr="error")
|
||||
with patch("subprocess.run", return_value=run_result), \
|
||||
patch.object(ts, "list", return_value=[{"id": 1}]):
|
||||
with pytest.raises(RuntimeError):
|
||||
ts.add("/tmp/foo.torrent")
|
||||
|
||||
|
||||
@@ -97,3 +97,45 @@ def test_docker_hashcat_cracks_simple_password(docker_image):
|
||||
assert run.returncode == 0, (
|
||||
f"Docker hashcat crack failed. stdout={run.stdout} stderr={run.stderr}"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.timeout(300)
|
||||
def test_docker_torrent_downloads_wordlists(docker_image, tmp_path):
|
||||
downloads_dir = tmp_path / "downloads"
|
||||
downloads_dir.mkdir()
|
||||
|
||||
py_cmd = (
|
||||
"from hate_crack.api import fetch_torrent_metadata, run_torrent_session; "
|
||||
"t1 = fetch_torrent_metadata('ignis-10K.txt'); "
|
||||
"t2 = fetch_torrent_metadata('hashmob.net_2025.micro.found'); "
|
||||
"files = [f for f in (t1, t2) if f]; "
|
||||
"run_torrent_session(files, '/downloads')"
|
||||
)
|
||||
|
||||
try:
|
||||
run = subprocess.run(
|
||||
[
|
||||
"docker", "run", "--rm",
|
||||
"-v", f"{downloads_dir}:/downloads",
|
||||
docker_image,
|
||||
"bash", "-lc", f"/workspace/.venv/bin/python -c \"{py_cmd}\"",
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=300,
|
||||
)
|
||||
except subprocess.TimeoutExpired as exc:
|
||||
pytest.fail(f"Docker torrent test timed out after {exc.timeout}s")
|
||||
|
||||
assert run.returncode == 0, (
|
||||
f"Torrent session failed. stdout={run.stdout} stderr={run.stderr}"
|
||||
)
|
||||
|
||||
ignis10k = downloads_dir / "ignis-10K.txt"
|
||||
micro = downloads_dir / "hashmob.net_2025.micro.found"
|
||||
assert ignis10k.exists() and ignis10k.stat().st_size > 0, (
|
||||
f"ignis-10K.txt missing/empty. stdout={run.stdout} stderr={run.stderr}"
|
||||
)
|
||||
assert micro.exists() and micro.stat().st_size > 0, (
|
||||
f"hashmob.net_2025.micro.found missing/empty. stdout={run.stdout} stderr={run.stderr}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user