From 0572efba8bde29bb753e5fb09e3b2ba6e972d783 Mon Sep 17 00:00:00 2001 From: tommal Date: Mon, 6 Jul 2026 12:34:01 +0200 Subject: [PATCH] fix the manager --- docker-run-commands.sh | 26 +++--- .../__pycache__/manager.cpython-313.pyc | Bin 25407 -> 28759 bytes manager-solution/manager/config.json | 27 ++++++ manager-solution/manager/manager.py | 77 +++++++++++++++++- 4 files changed, 115 insertions(+), 15 deletions(-) create mode 100644 manager-solution/manager/config.json diff --git a/docker-run-commands.sh b/docker-run-commands.sh index ec3a634..084272c 100755 --- a/docker-run-commands.sh +++ b/docker-run-commands.sh @@ -6,16 +6,16 @@ set -euo pipefail # The manager normally proxies user ports; the first command block matches the manager # container creation and does not publish ports. -# User: mario - manager-equivalent container -docker volume create v-conf-mario >/dev/null +# User: tomas - manager-equivalent container +docker volume create v-conf-tomas >/dev/null docker run -d \ - --name fis-mario \ + --name fis-tomas \ --restart always \ - --volume v-conf-mario:/config:rw \ + --volume v-conf-tomas:/config:rw \ --env PUID=1000 \ --env PGID=1000 \ - --env PASSWORD=mario \ - --env CUSTOM_USER=mario \ + --env PASSWORD=tomasmali92 \ + --env CUSTOM_USER=tomas \ --memory 3g \ --memory-swap 5g \ --privileged \ @@ -23,17 +23,17 @@ docker run -d \ --interactive \ fis -# Emergency direct access for mario on host port 3001: -# docker rm -f fis-mario || true +# Emergency direct access for tomas on host port 3009: +# docker rm -f fis-tomas || true # docker run -d \ -# --name fis-mario \ +# --name fis-tomas \ # --restart always \ -# -p 3001:3000 \ -# --volume v-conf-mario:/config:rw \ +# -p 3009:3000 \ +# --volume v-conf-tomas:/config:rw \ # --env PUID=1000 \ # --env PGID=1000 \ -# --env PASSWORD=mario \ -# --env CUSTOM_USER=mario \ +# --env PASSWORD=tomasmali92 \ +# --env CUSTOM_USER=tomas \ # --memory 3g \ # --memory-swap 5g \ # --privileged \ diff --git a/manager-solution/manager/__pycache__/manager.cpython-313.pyc b/manager-solution/manager/__pycache__/manager.cpython-313.pyc index d23b70ddbaa159b1786499dd495557f51a92b4d3..a6d2029a847d973380f71be09bc6a74ce63bde62 100644 GIT binary patch delta 6480 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 aB)B07ZVQf-FZdIpfuj^z*vHC375@jV`sf+} delta 3750 zcma)8dvKda5x@KNeUdC$vYp8BBYwzs{E%bEFZmVM&opuTtnp_{m-Jq}hk~-gWIBW#I>*1mKvhG!9E*)O2?5Z>kSq81lI+(`N z25rpdCMs2>Av?3fe|ac<(7_yT;xqBHjvcGtb(k{N$u}I9OkY;%hU9j(Wm{^i_)-^ZOPvvMwIrX+%dA2z1x}eSOLhB-y=HZn;8v>T zsE5y^}6VQ!DQ2i&UzE#W*!tZKcG)Ts?(ZY?a`C^+?ES;P4ax6EqOvJf_? zUSF-+{J%p9CR&7626LFW2wWtTwPS{Ns>=pg*7W?c$8APlVq#rRVUJ%Q?!nMBkcrj z;w`q)qf%_o(|Bi!zUi)xr$62Vs;?78Wa$K#nDs3RfgL#%)lg zYn1!ledcjgQTeysHTDT0btN7PhZ6jEZny2@IF@lWu9Ubfx)KYpM4VTb)GKii-JA56 z9GA?WL<3{|Mp-_c=l?ESRF;7eR-chKBf)R+g*<@snR;!zkw&cl4RqFs3$ z$On_pRaDceN09p{!WR%e2jG#|7m-F2Kz|wO$N0Z0%ayMJ`K4q@)iruZPYXw<1L3)7 zJaOB$XOq_H9)>N$qv&bqc}Rpvi6>YnrYqs-qQ+PV4dMzOs}Zp#pmi&niUf{oyuW%( zc^X(JlBcR4Q0M~Bsdx9|u{B&o^X&5gYbK6gcAORNFo&WLoEiE*w2tnk|$f7QjOR~2{4!l zSU-x)M$JMA{M;CZLs}%kF7T^u`^>KaYmFoe+CAosb`(pYAnO2vb@PetL-YcFp*zrU z7A5AOmIzGGu?r}D9pMVX8wkHb_%*^+gx~Pqo~M@aY_T2$;Z`3~`w-T*nt%dfcQ|We z3lU~TCGge z;0z|XVyHs;3rUU+)lu&f2y|tUg%X;s1X&;yVW)Aeod||S*cqGFO+nb7D7Pm5!{G+n z&;LApHD?j0XGCLKWJ||k2l?fZLi<0^#6J;E^IIdWMGvB`SuDlaUy=Vd0v;~5M)=@p znPnL1n}jDu8Q79oZ$tNaWQkI1q6viH>?u1tC z*FKYA0e>hm6J^-nn21LDMChm%T}X(kU?K?E&WuQ;oWmo>3J|bq8C$`%JeHJmx*XG> z9|`X+5f_Bd$utaL0^$v4IKY0yuN|sa(qZ0mGJVQ}*=_MR`p^+}1|bsxV}PvzGAQzT z9+^%A;Xp5nO|gwS!dmgjx+xM}G-6^82jU5T0B)0G8oR{L``yYmSn*lD=GUn?dH%j` zno~z2(o{Ok6rXC}`y||hpCWvvvsf-AQ!f6o!?h!(Da)pYV=8BpcBW=-yyu)m%}8-! zl-Z=DZqG?+JddAkE#`kaY|Hus~@qGeBQF55bM?e04f|*|KFLt}W7Rpm(5U(9D@M1LjUzoTSN` za{ZqC|95VaIW-G6yb&%!_y_$i*U0mP*om7=VT#2!o{tZXqN&0SM)Nw6vNM&*f9oi) zmJ&5DpG->p*t(Augl5Gd)h))Ai&{E@j8dV?2+R;3s3=06C% zvOGY^_Fnq#4w64d-^nH0M(I2GL>Z(X#_^{3Y|cSSPiFKt%irlM2lAYQ_V1O?IeT$% zzKQmCn$LUtI$&_Ij`nxU7whdvcT&V|MBVE5qZ59=hq7H@($l3|{Qh8c+V6*6k_#J> z=i~5#lcI4pj^SxRIL&QG8eI@24cfplhDXY{kivZANWb()!oPN;Xt@>HSOBa7p#}lt z&Ds#KHR|%=Xf$lx^5TL&juO$xM8gt&>1pJNUL|fdyz|(b2-qc str: return f"http://{host}:{port}" +def detect_lan_ip() -> str: + configured_ip = os.getenv("MANAGER_PUBLIC_IP") or os.getenv("CONFIGURATOR_PUBLIC_IP") + if configured_ip: + return configured_ip + + try: + output = subprocess.check_output( + ["ip", "route", "get", "1.1.1.1"], + stderr=subprocess.DEVNULL, + text=True, + ).strip() + parts = output.split() + if "src" in parts: + candidate = parts[parts.index("src") + 1] + if is_public_manager_ip(candidate): + return candidate + except (OSError, subprocess.SubprocessError, IndexError): + pass + + try: + output = subprocess.check_output( + ["ip", "-o", "-4", "addr", "show", "scope", "global"], + stderr=subprocess.DEVNULL, + text=True, + ) + for line in output.splitlines(): + parts = line.split() + interface = parts[1] + candidate = parts[3].split("/", 1)[0] + if is_ignored_interface(interface): + continue + if is_public_manager_ip(candidate): + return candidate + except (OSError, subprocess.SubprocessError, IndexError): + pass + + try: + output = subprocess.check_output(["hostname", "-I"], stderr=subprocess.DEVNULL, text=True).strip() + for candidate in output.split(): + if is_public_manager_ip(candidate): + return candidate + except (OSError, subprocess.SubprocessError): + pass + + try: + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock: + sock.connect(("8.8.8.8", 80)) + candidate = sock.getsockname()[0] + if is_public_manager_ip(candidate): + return candidate + except OSError: + pass + + return "localhost" + + +def is_ignored_interface(interface: str) -> bool: + ignored_prefixes = ("docker", "br-", "veth", "virbr", "podman", "lo") + return interface.startswith(ignored_prefixes) + + +def is_public_manager_ip(candidate: str) -> bool: + if not candidate or "." not in candidate: + return False + if candidate.startswith("127.") or candidate.startswith("169.254."): + return False + if candidate.startswith("172.17."): + return False + return True + + def get_user_container_lock(user_id: str) -> threading.Lock: with user_container_locks_guard: if user_id not in user_container_locks: @@ -448,12 +520,13 @@ async def main(): log(f"Gateway port: {GATEWAY_PORT if GATEWAY_PORT else 'disabled'}") log(f"Inactivity timeout: {INACTIVITY_SECONDS}s") + public_host = detect_lan_ip() servers = [] if GATEWAY_PORT > 0: gateway_server = await asyncio.start_server(handle_gateway_connection, "0.0.0.0", GATEWAY_PORT) log( "Shared gateway ready: " - f"{format_access_url('localhost', GATEWAY_PORT)} " + f"{format_access_url(public_host, GATEWAY_PORT)} " f"(host-based routing: http://.localhost:{GATEWAY_PORT})" ) servers.append(gateway_server) @@ -463,7 +536,7 @@ async def main(): await handle_connection(reader, writer, configured_user) server = await asyncio.start_server(handler, "0.0.0.0", user["port"]) - direct_url = format_access_url("localhost", user["port"]) + direct_url = format_access_url(public_host, user["port"]) if GATEWAY_PORT > 0: gateway_url = format_access_url(f"{user['id']}.localhost", GATEWAY_PORT) log(