evo-cli 0.28.0__tar.gz → 0.28.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {evo_cli-0.28.0 → evo_cli-0.28.1}/PKG-INFO +1 -1
- evo_cli-0.28.1/evo_cli/VERSION +1 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/openvpn.py +3 -1
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/openvpn.py +53 -5
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli.egg-info/PKG-INFO +1 -1
- {evo_cli-0.28.0 → evo_cli-0.28.1}/tests/test_openvpn.py +44 -3
- evo_cli-0.28.0/evo_cli/VERSION +0 -1
- {evo_cli-0.28.0 → evo_cli-0.28.1}/Containerfile +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/HISTORY.md +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/LICENSE +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/MANIFEST.in +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/README.md +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/__init__.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/__main__.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/base.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/cli.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/__init__.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/agent_toy.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/claude_code.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/cloudflare.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/cred.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/download.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/fix_claude.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/gdrive.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/gh.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/__init__.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/_dag.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/_git.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/_model.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/_mutate.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/_paths.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/_render.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/_server.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/check.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/clone.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/edit.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/pull.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/serve.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/show.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/views.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/web/assets/index-Cs7hXRPJ.css +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/web/assets/index-CuNOlaH7.js +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/web/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/web/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/web/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/web/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/web/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/web/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/web/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/harness/web/index.html +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/hwid.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/hwid_reset.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/image.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/localproxy.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/mcp.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/miniconda.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/netcheck.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/ngrok.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/opencode.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/plantuml.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/route/__init__.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/route/_dns.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/route/_dpi.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/route/_hosts.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/route/_probe.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/route/check.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/route/fix.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/route/probe.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/route/status.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/serp.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/site2s.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/ssh.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/storage.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/sysmon.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/tts.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/update.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/commands/wifi.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/console.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/credentials/__init__.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/credentials/doctor.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/credentials/google_oauth.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/credentials/migrate.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/credentials/oauth_flow.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/credentials/registry.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/credentials/store.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/credentials/sync.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/imaging/__init__.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/imaging/align.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/imaging/core.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/imaging/creds.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/imaging/errors.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/imaging/gemini.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/imaging/ncnn.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/mcp_registry.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/serp/__init__.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/serp/client.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/serp/creds.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/serp/errors.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/serp/install.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/serp/render.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/tts/__init__.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/tts/chunking.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/tts/core.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/tts/creds.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/tts/errors.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/tts/gemini.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/tts/http.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/tts/openai.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/tts/player.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli/tts/vbee.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli.egg-info/SOURCES.txt +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli.egg-info/dependency_links.txt +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli.egg-info/entry_points.txt +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli.egg-info/requires.txt +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/evo_cli.egg-info/top_level.txt +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/pyproject.toml +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/setup.cfg +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/tests/__init__.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/tests/test_agent_toy.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/tests/test_claude_code.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/tests/test_cli.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/tests/test_console.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/tests/test_cred.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/tests/test_download.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/tests/test_fix_claude.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/tests/test_gdrive.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/tests/test_gh.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/tests/test_harness.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/tests/test_harness_clone.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/tests/test_harness_dag.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/tests/test_image.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/tests/test_mcp.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/tests/test_ngrok.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/tests/test_opencode.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/tests/test_plantuml.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/tests/test_route.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/tests/test_serp.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/tests/test_storage.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/tests/test_sysmon.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/tests/test_tts.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/tests/test_update.py +0 -0
- {evo_cli-0.28.0 → evo_cli-0.28.1}/tests/test_wifi.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.28.1
|
|
@@ -294,12 +294,14 @@ def disconnect(name):
|
|
|
294
294
|
|
|
295
295
|
def _disconnect(name):
|
|
296
296
|
state = _guard(vpn.request, name, "disconnect")
|
|
297
|
+
if state["state"] == "STOPPED" and state.get("error"):
|
|
298
|
+
click.echo(f"{name} was not running; last error: {state['error']}", err=True)
|
|
297
299
|
deadline = time.monotonic() + 20
|
|
298
300
|
while state["state"] != "STOPPED" and time.monotonic() < deadline:
|
|
299
301
|
time.sleep(0.2)
|
|
300
302
|
state = _guard(vpn.request, name)
|
|
301
303
|
if state["state"] != "STOPPED":
|
|
302
|
-
raise click.ClickException("Disconnect not confirmed; inspect evo openvpn status.")
|
|
304
|
+
raise click.ClickException(state.get("error") or "Disconnect not confirmed; inspect evo openvpn status.")
|
|
303
305
|
click.echo(f"{name}: stopped (OpenVPN Connect profiles unchanged).")
|
|
304
306
|
|
|
305
307
|
|
|
@@ -366,6 +366,58 @@ def last_profile():
|
|
|
366
366
|
return None
|
|
367
367
|
|
|
368
368
|
|
|
369
|
+
def _worker_alive(base):
|
|
370
|
+
"""The worker holds its lock for life, and the kernel releases it however the worker dies."""
|
|
371
|
+
import fcntl
|
|
372
|
+
|
|
373
|
+
try:
|
|
374
|
+
with open(str(base) + ".lock") as lock:
|
|
375
|
+
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
376
|
+
except FileNotFoundError:
|
|
377
|
+
return False
|
|
378
|
+
except BlockingIOError:
|
|
379
|
+
return True
|
|
380
|
+
return False
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def _config_pids(base):
|
|
384
|
+
"""PIDs whose command line names this session's config (OpenVPN, its sudo or wrapper); None if unknown."""
|
|
385
|
+
config = str(base) + ".ovpn"
|
|
386
|
+
try:
|
|
387
|
+
output = subprocess.run(
|
|
388
|
+
["ps", "-A", "-o", "pid=", "-o", "args="], capture_output=True, text=True, timeout=5, check=True
|
|
389
|
+
).stdout
|
|
390
|
+
except (OSError, subprocess.SubprocessError):
|
|
391
|
+
return None
|
|
392
|
+
return [line.split(None, 1)[0] for line in output.splitlines() if config in line]
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def _reap(name, last):
|
|
396
|
+
"""Finish the cleanup of a worker that died without it (SIGKILL, SIGTERM), once nothing of it runs.
|
|
397
|
+
|
|
398
|
+
Otherwise its last published state would read as UNKNOWN forever, blocking connect and disconnect.
|
|
399
|
+
"""
|
|
400
|
+
base = session_path(name)
|
|
401
|
+
if _worker_alive(base):
|
|
402
|
+
return {"profile": name, "state": "UNKNOWN", "error": "Worker unavailable; inspect OpenVPN processes."}
|
|
403
|
+
pids = _config_pids(base)
|
|
404
|
+
if pids is None:
|
|
405
|
+
return {"profile": name, "state": "UNKNOWN", "error": "Worker gone; cannot list processes to confirm."}
|
|
406
|
+
if pids:
|
|
407
|
+
listed = " ".join(pids)
|
|
408
|
+
error = f"OpenVPN still runs without its worker (PID {listed}); stop it with: sudo kill {listed}"
|
|
409
|
+
return {"profile": name, "state": "UNKNOWN", "error": error}
|
|
410
|
+
for suffix in (".sock", ".mgmt", ".ovpn"):
|
|
411
|
+
Path(str(base) + suffix).unlink(missing_ok=True)
|
|
412
|
+
state = {
|
|
413
|
+
"profile": name,
|
|
414
|
+
"state": "STOPPED",
|
|
415
|
+
"error": f"Worker exited unexpectedly (last state {last['state']}); no OpenVPN process was left running.",
|
|
416
|
+
}
|
|
417
|
+
private_write(Path(str(base) + ".json"), json.dumps(state))
|
|
418
|
+
return state
|
|
419
|
+
|
|
420
|
+
|
|
369
421
|
def request(name, command="status"):
|
|
370
422
|
endpoint = str(session_path(name)) + ".sock"
|
|
371
423
|
with socket.socket(socket.AF_UNIX) as client:
|
|
@@ -377,11 +429,7 @@ def request(name, command="status"):
|
|
|
377
429
|
if path.exists():
|
|
378
430
|
last = json.loads(path.read_text())
|
|
379
431
|
if last["state"] not in ("FAILED", "STOPPED") or last.get("cleanup_failed"):
|
|
380
|
-
return
|
|
381
|
-
"profile": name,
|
|
382
|
-
"state": "UNKNOWN",
|
|
383
|
-
"error": "Worker unavailable; inspect OpenVPN processes.",
|
|
384
|
-
}
|
|
432
|
+
return _reap(name, last)
|
|
385
433
|
if last.get("error"):
|
|
386
434
|
return {"profile": name, "state": "STOPPED", "error": last["error"]}
|
|
387
435
|
return {"profile": name, "state": "STOPPED"}
|
|
@@ -258,10 +258,51 @@ def test_plain_password_escaping_and_no_unrequested_otp():
|
|
|
258
258
|
vpn.quote("x\nsignal SIGTERM")
|
|
259
259
|
|
|
260
260
|
|
|
261
|
-
def
|
|
262
|
-
|
|
263
|
-
|
|
261
|
+
def _ps(monkeypatch, output):
|
|
262
|
+
def run(command, **kwargs):
|
|
263
|
+
assert command[0] == "ps"
|
|
264
|
+
if output is None:
|
|
265
|
+
raise OSError("no ps")
|
|
266
|
+
return subprocess.CompletedProcess(command, 0, stdout=output)
|
|
267
|
+
|
|
268
|
+
monkeypatch.setattr(vpn.subprocess, "run", run)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def test_stale_state_is_not_connected_or_confirmed_stopped(store, monkeypatch):
|
|
272
|
+
import fcntl
|
|
273
|
+
|
|
274
|
+
base = str(vpn.session_path("m1"))
|
|
275
|
+
vpn.private_write(Path(base + ".json"), '{"state":"CONNECTED"}')
|
|
276
|
+
_ps(monkeypatch, "")
|
|
277
|
+
with open(base + ".lock", "a") as held:
|
|
278
|
+
fcntl.flock(held, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
279
|
+
assert vpn.request("m1")["state"] == "UNKNOWN" # the worker lives but does not answer
|
|
280
|
+
_ps(monkeypatch, None)
|
|
264
281
|
assert vpn.request("m1")["state"] == "UNKNOWN"
|
|
282
|
+
_ps(monkeypatch, f" 4242 /usr/sbin/openvpn --config {base}.ovpn --verb 3\n 4300 -zsh\n")
|
|
283
|
+
state = vpn.request("m1")
|
|
284
|
+
assert state["state"] == "UNKNOWN" and state["error"].endswith("sudo kill 4242")
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def test_dead_worker_is_reaped_and_disconnect_confirms_it(store, monkeypatch):
|
|
288
|
+
import socket
|
|
289
|
+
|
|
290
|
+
vpn.save_profile("m1", {"config": CONFIG})
|
|
291
|
+
base = str(vpn.session_path("m1"))
|
|
292
|
+
vpn.private_write(Path(base + ".json"), '{"profile":"m1","state":"CONNECTED","vpn_ip":"10.8.0.2"}')
|
|
293
|
+
# What a SIGKILLed worker leaves: a socket nobody listens on, the management socket and the config.
|
|
294
|
+
with socket.socket(socket.AF_UNIX) as stale:
|
|
295
|
+
stale.bind(base + ".sock")
|
|
296
|
+
for suffix in (".mgmt", ".ovpn", ".lock"):
|
|
297
|
+
Path(base + suffix).touch()
|
|
298
|
+
_ps(monkeypatch, " 4300 -zsh\n")
|
|
299
|
+
result = CliRunner().invoke(cli, ["openvpn", "disconnect", "m1"])
|
|
300
|
+
assert result.exit_code == 0, result.output
|
|
301
|
+
assert "m1 was not running; last error: Worker exited unexpectedly (last state CONNECTED)" in result.output
|
|
302
|
+
assert "m1: stopped" in result.output
|
|
303
|
+
assert not any(Path(base + suffix).exists() for suffix in (".sock", ".mgmt", ".ovpn"))
|
|
304
|
+
assert json.loads(Path(base + ".json").read_text())["state"] == "STOPPED"
|
|
305
|
+
assert vpn.request("m1")["state"] == "STOPPED" # so connect and import accept the profile again
|
|
265
306
|
|
|
266
307
|
|
|
267
308
|
FAKE_OPENVPN = """
|
evo_cli-0.28.0/evo_cli/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.28.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|