fix the manager

This commit is contained in:
2026-07-06 12:34:01 +02:00
parent 948f209436
commit 0572efba8b
4 changed files with 115 additions and 15 deletions

View File

@@ -6,16 +6,16 @@ set -euo pipefail
# The manager normally proxies user ports; the first command block matches the manager # The manager normally proxies user ports; the first command block matches the manager
# container creation and does not publish ports. # container creation and does not publish ports.
# User: mario - manager-equivalent container # User: tomas - manager-equivalent container
docker volume create v-conf-mario >/dev/null docker volume create v-conf-tomas >/dev/null
docker run -d \ docker run -d \
--name fis-mario \ --name fis-tomas \
--restart always \ --restart always \
--volume v-conf-mario:/config:rw \ --volume v-conf-tomas:/config:rw \
--env PUID=1000 \ --env PUID=1000 \
--env PGID=1000 \ --env PGID=1000 \
--env PASSWORD=mario \ --env PASSWORD=tomasmali92 \
--env CUSTOM_USER=mario \ --env CUSTOM_USER=tomas \
--memory 3g \ --memory 3g \
--memory-swap 5g \ --memory-swap 5g \
--privileged \ --privileged \
@@ -23,17 +23,17 @@ docker run -d \
--interactive \ --interactive \
fis fis
# Emergency direct access for mario on host port 3001: # Emergency direct access for tomas on host port 3009:
# docker rm -f fis-mario || true # docker rm -f fis-tomas || true
# docker run -d \ # docker run -d \
# --name fis-mario \ # --name fis-tomas \
# --restart always \ # --restart always \
# -p 3001:3000 \ # -p 3009:3000 \
# --volume v-conf-mario:/config:rw \ # --volume v-conf-tomas:/config:rw \
# --env PUID=1000 \ # --env PUID=1000 \
# --env PGID=1000 \ # --env PGID=1000 \
# --env PASSWORD=mario \ # --env PASSWORD=tomasmali92 \
# --env CUSTOM_USER=mario \ # --env CUSTOM_USER=tomas \
# --memory 3g \ # --memory 3g \
# --memory-swap 5g \ # --memory-swap 5g \
# --privileged \ # --privileged \

View File

@@ -0,0 +1,27 @@
{
"defaults": {
"container_prefix": "fis-",
"environment": {
"PUID": "1000",
"PGID": "1000"
},
"volumes": {
"v-conf-{id}": "/config"
},
"mem_limit": "3g",
"memswap_limit": "5g",
"privileged": true,
"tmpfs": {},
"dns": [],
"extra_hosts": {}
},
"users": [
{
"id": "tomas",
"port": 3009,
"environment": {
"PASSWORD": "tomasmali92"
}
}
]
}

View File

@@ -10,6 +10,7 @@ import asyncio
import json import json
import os import os
import socket import socket
import subprocess
import threading import threading
import time import time
from typing import Any, Dict, Optional from typing import Any, Dict, Optional
@@ -52,6 +53,77 @@ def format_access_url(host: str, port: int) -> str:
return f"http://{host}:{port}" 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: def get_user_container_lock(user_id: str) -> threading.Lock:
with user_container_locks_guard: with user_container_locks_guard:
if user_id not in user_container_locks: 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"Gateway port: {GATEWAY_PORT if GATEWAY_PORT else 'disabled'}")
log(f"Inactivity timeout: {INACTIVITY_SECONDS}s") log(f"Inactivity timeout: {INACTIVITY_SECONDS}s")
public_host = detect_lan_ip()
servers = [] servers = []
if GATEWAY_PORT > 0: if GATEWAY_PORT > 0:
gateway_server = await asyncio.start_server(handle_gateway_connection, "0.0.0.0", GATEWAY_PORT) gateway_server = await asyncio.start_server(handle_gateway_connection, "0.0.0.0", GATEWAY_PORT)
log( log(
"Shared gateway ready: " "Shared gateway ready: "
f"{format_access_url('localhost', GATEWAY_PORT)} " f"{format_access_url(public_host, GATEWAY_PORT)} "
f"(host-based routing: http://<user>.localhost:{GATEWAY_PORT})" f"(host-based routing: http://<user>.localhost:{GATEWAY_PORT})"
) )
servers.append(gateway_server) servers.append(gateway_server)
@@ -463,7 +536,7 @@ async def main():
await handle_connection(reader, writer, configured_user) await handle_connection(reader, writer, configured_user)
server = await asyncio.start_server(handler, "0.0.0.0", user["port"]) 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: if GATEWAY_PORT > 0:
gateway_url = format_access_url(f"{user['id']}.localhost", GATEWAY_PORT) gateway_url = format_access_url(f"{user['id']}.localhost", GATEWAY_PORT)
log( log(