fix: recover durable route paths after restart
This commit is contained in:
@@ -66,6 +66,11 @@ def map_path(container: dict[str, Any], path: str) -> Mapping:
|
||||
return max(matches, key=lambda item: item[0])[1]
|
||||
|
||||
|
||||
def container_user(container: dict[str, Any]) -> str | None:
|
||||
value = container.get("Config", {}).get("User")
|
||||
return value if isinstance(value, str) and value else None
|
||||
|
||||
|
||||
def require_same_path(label: str, left: Mapping, right: Mapping) -> None:
|
||||
if left.source.resolve(strict=False) != right.source.resolve(strict=False):
|
||||
raise CheckFailure(
|
||||
@@ -169,7 +174,9 @@ raise SystemExit(0 if all(p.state == 1 for p in probes) else 1)'''
|
||||
print(result.stdout.strip())
|
||||
|
||||
|
||||
def run_hardlink_probe(container: str, qb_root: str, route_root: str) -> None:
|
||||
def run_hardlink_probe(
|
||||
container: str, qb_root: str, route_root: str, user: str | None = None,
|
||||
) -> None:
|
||||
"""Prove that the daemon can hard-link from qB data into automatic routes."""
|
||||
|
||||
program = r'''import os,sys,tempfile
|
||||
@@ -196,8 +203,12 @@ finally:
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
'''
|
||||
command = ["docker", "exec"]
|
||||
if user:
|
||||
command.extend(["--user", user])
|
||||
command.extend([container, "python", "-c", program, qb_root, route_root])
|
||||
result = subprocess.run(
|
||||
["docker", "exec", container, "python", "-c", program, qb_root, route_root],
|
||||
command,
|
||||
check=False, text=True, capture_output=True,
|
||||
)
|
||||
if result.returncode:
|
||||
@@ -206,6 +217,51 @@ finally:
|
||||
print(result.stdout.strip())
|
||||
|
||||
|
||||
def run_existing_route_directory_check(container: str, config: str) -> None:
|
||||
"""Reject a mount migration that hides an already configured route folder."""
|
||||
|
||||
program = r'''import json,os,sys
|
||||
from pathlib import Path,PurePosixPath
|
||||
from archive_clients.config import ClientConfig
|
||||
from archive_clients.syncthing import SyncthingHttp
|
||||
config=ClientConfig.load(Path(sys.argv[1]))
|
||||
transport=SyncthingHttp(config.syncthing)
|
||||
route_root=config.syncthing.api_root / "routes"
|
||||
missing=[]
|
||||
checked=[]
|
||||
for folder in transport.get_json("/rest/config").get("folders",[]):
|
||||
if not isinstance(folder,dict) or not str(folder.get("label","")).startswith("archive-control:"):
|
||||
continue
|
||||
raw=folder.get("path")
|
||||
if not isinstance(raw,str) or not raw:
|
||||
continue
|
||||
api=PurePosixPath(raw)
|
||||
if api.parts and api.parts[0] == "~":
|
||||
api=config.syncthing.api_root.joinpath(*api.parts[1:])
|
||||
try:
|
||||
api.relative_to(route_root)
|
||||
except ValueError:
|
||||
continue
|
||||
local=config.syncthing.roots.api_to_local(api.as_posix())
|
||||
checked.append(str(local))
|
||||
if not local.is_dir():
|
||||
missing.append({"folder_id":folder.get("id",""),"path":str(local)})
|
||||
print(json.dumps({"checked":checked,"missing":missing},sort_keys=True))
|
||||
raise SystemExit(1 if missing else 0)
|
||||
'''
|
||||
result = subprocess.run(
|
||||
["docker", "exec", container, "python", "-c", program, config],
|
||||
check=False, text=True, capture_output=True,
|
||||
)
|
||||
if result.returncode:
|
||||
detail = result.stderr.strip() or result.stdout.strip() or "failed"
|
||||
raise CheckFailure(
|
||||
"an existing Archive Control Syncthing route directory is missing; "
|
||||
f"complete the route-directory migration before starting jobs: {detail}"
|
||||
)
|
||||
print(result.stdout.strip())
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Read-only Archive Control Docker deployment preflight"
|
||||
@@ -248,7 +304,11 @@ def main(argv: list[str] | None = None) -> int:
|
||||
"mounts; hard-link staging would be unavailable"
|
||||
)
|
||||
run_hardlink_probe(
|
||||
args.client_container, qb["local_root"], route_local_path(sync)
|
||||
args.client_container, qb["local_root"], route_local_path(sync),
|
||||
container_user(client),
|
||||
)
|
||||
run_existing_route_directory_check(
|
||||
args.client_container, args.container_config
|
||||
)
|
||||
for key in ("shared_token_file",):
|
||||
require_regular_secret(map_path(client, config[key]).source)
|
||||
|
||||
Reference in New Issue
Block a user