#!/usr/bin/env python3 """office_probe.py — R-1281's OnlyOffice half on a release box: with FileBrowser on its own `felhom-files` network, a linked OnlyOffice still joins it, both directions answer, and the document path works — the household asks the file manager for the OnlyOffice editor config of a .docx, and the OnlyOffice container fetches the document URL that config names. A THROWAWAY OnlyOffice and a throwaway file; everything removed through the product at the end. Endpoint-level. No secret is printed (the document URL is shown as scheme://host/path only). usage: SC= SWEEP_EV= TARGET=tester-1 FB_SOURCE=Adatlemez python3 office_probe.py""" import io, json, os, re, sys, time, zipfile from urllib.parse import urlsplit sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../catalog-sweep-2026-10-24/tools")) import sweep as sw # noqa: E402 w = sw.w APP, SUB = "onlyoffice", "office" SRC = os.environ.get("FB_SOURCE", "Adatlemez") DOC = "/cc-r1281-probe.docx" Q = "path=" + DOC.replace("/", "%2F") + "&" + "sou" + "rce=" + SRC def docx_bytes(): b = io.BytesIO() with zipfile.ZipFile(b, "w", zipfile.ZIP_DEFLATED) as z: z.writestr("[Content_Types].xml", '') z.writestr("_rels/.rels", '') z.writestr("word/document.xml", 'R-1281 probe') return b.getvalue() def files_cookie(): sess = open(f"{w.SC}/sess{os.getpid()}.txt").read().strip() r = w.sh(["curl", "-sk", "-D", "-", "-o", "/dev/null", "-H", w.HOSTHDR, "-H", f"Cookie: {sess}", f"{w.BASE}/__files/open?to=%2F"]) m = re.search(r"https?://[^/]+(/_felhom/sso\?[^\s]+)", w._loc(r.stdout)) r = w.sh(["curl", "-sk", "-D", "-", "-o", "/dev/null", "-H", f"Host: files.{w.DOMAIN}", f"{w.BASE}{m.group(1)}"]) return re.search(r"(?im)^set-cookie:\s*(felhom_files=[^;\r\n]+)", r.stdout or "").group(1) def files(method, path, ck, body_file=None): args = ["curl", "-sk", "-X", method, "-w", "\n%{http_code}", "-H", f"Host: files.{w.DOMAIN}", "-H", f"Cookie: {ck}"] if body_file: args += ["--data-binary", "@" + body_file] r = w.sh(args + [f"{w.BASE}{path}"]) body, _, code = (r.stdout or "").rpartition("\n") return code.strip(), body def nets(c): return sw.g("docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}' " + c + " 2>&1").strip() def main(): rec = {} say = print w.say = say w.login() tmp = os.path.join(w.SC, "probe.docx") open(tmp, "wb").write(docx_bytes()) ck = None try: rec["install"] = w.deploy(APP, SUB) and w.wait_app(SUB, "/", tries=120, delay=5) say("1. OnlyOffice installed and answering:", rec["install"]) code, body = w.ctl("POST", f"/api/integrations/{APP}/filebrowser", {"enabled": True}, raw=True) say("2. link switched on ->", code, body[:120]) for _ in range(30): time.sleep(10) if "felhom-files" in nets("onlyoffice"): break say("3. onlyoffice networks:", nets("onlyoffice"), "| filebrowser:", nets("filebrowser")) say("4. filebrowser -> onlyoffice /healthcheck:", sw.g("docker exec filebrowser wget -qO- -T 5 http://onlyoffice:80/healthcheck 2>&1 | head -c 40").strip()) say("5. onlyoffice -> filebrowser /health:", sw.g("docker exec onlyoffice curl -s -o /dev/null -m 5 -w %{http_code} http://filebrowser:80/health 2>&1").strip()) say("6. control: paperless-webserver -> filebrowser:", sw.g("docker exec paperless-webserver curl -s -o /dev/null -m 5 -w %{http_code} http://filebrowser:80/health 2>&1").strip()) ck = files_cookie() code, _ = files("POST", f"/api/resources?{Q}&override=true", ck, tmp) say("7. household uploads the .docx ->", code) code, body = files("GET", f"/api/office/config?{Q}", ck) url = "" try: cfg = json.loads(body) url = ((cfg.get("document") or {}).get("url")) or "" except ValueError: pass u = urlsplit(url) if url else None say("8. editor config ->", code, "| document url:", f"{u.scheme}://{u.netloc}{u.path}" if u else "(none)") if url: got = sw.g("docker exec onlyoffice curl -s -o /dev/null -m 10 -w %{http_code} '" + url.replace("'", "") + "' 2>&1").strip() say("9. OnlyOffice fetches that document ->", got) finally: if ck: say("10. delete the .docx ->", files("DELETE", f"/api/resources?{Q}", ck)[0]) say("11. link off ->", w.ctl("POST", f"/api/integrations/{APP}/filebrowser", {"enabled": False}, raw=True)[0]) sw.remove(APP, keep=False, say=say) say("12. leftovers:", sw.leftovers(APP, SUB)) say("13. filebrowser networks after:", nets("filebrowser")) os.remove(tmp) if __name__ == "__main__": main()