From 28ef7a68932cb74f202f5d71eeb9a0ff2e0d301e Mon Sep 17 00:00:00 2001 From: kassam Date: Thu, 23 Jul 2026 11:13:09 +0400 Subject: [PATCH] Update 2026-07-23 11:13:07 --- .gitignore | 2 + maps/.current.json | 2 +- maps/pudu_modified/annotations.yaml | 11 ++ maps/pudu_modified/map.pgm | Bin 0 -> 54182 bytes maps/pudu_modified/map.pgm.orig | Bin 0 -> 54127 bytes maps/pudu_modified/map.yaml | 7 + maps/pudu_modified/map_keepout_baked.pgm | Bin 0 -> 54127 bytes maps/pudu_modified/map_keepout_baked.yaml | 7 + pudu_gui.py | 165 +++++++++++++++++++++- start.sh | 16 ++- 10 files changed, 202 insertions(+), 8 deletions(-) create mode 100644 maps/pudu_modified/annotations.yaml create mode 100644 maps/pudu_modified/map.pgm create mode 100644 maps/pudu_modified/map.pgm.orig create mode 100644 maps/pudu_modified/map.yaml create mode 100644 maps/pudu_modified/map_keepout_baked.pgm create mode 100644 maps/pudu_modified/map_keepout_baked.yaml diff --git a/.gitignore b/.gitignore index c02264b..ef96b82 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ __pycache__/ *.pyc Logs/ *.log +.pudu_token +access.log diff --git a/maps/.current.json b/maps/.current.json index 685cacd..c250317 100644 --- a/maps/.current.json +++ b/maps/.current.json @@ -1 +1 @@ -{"yaml": "/home/zedx/Robotics_workspace/yslootahtech/Project/G1/Nav2_Projects/sanad_nav3/maps/pudu_modified/map.yaml"} \ No newline at end of file +{"yaml": "/home/zedx/Robotics_workspace/yslootahtech/Project/Other/pudu_map_gui/maps/pudu_modified/map.yaml"} \ No newline at end of file diff --git a/maps/pudu_modified/annotations.yaml b/maps/pudu_modified/annotations.yaml new file mode 100644 index 0000000..fd2b31a --- /dev/null +++ b/maps/pudu_modified/annotations.yaml @@ -0,0 +1,11 @@ +home_points: +- name: W + x: 0 + y: 0.01 + yaw: 0.00999633 +- name: N + x: 1.27 + y: -4.19 + yaw: -0.38 +charge_stations: [] +areas: [] diff --git a/maps/pudu_modified/map.pgm b/maps/pudu_modified/map.pgm new file mode 100644 index 0000000000000000000000000000000000000000..5088cfc97c45e4e590cb49f8ea2fa1fe68ab4aea GIT binary patch literal 54182 zcmdU&ORg-(afa`ht2lr+V9O@#x#6L0ZGk2!gA+?2WCNOc5!x0v;e`;6KVBIT8CjLp zU48npNOfh#P7h{q9d6 z|MJ66fBxgoAOH8ifBpEk@BZg^AAkJ*`~UvacmMVMUq1fx=bwJ~=I{RTpFaNnZ~x(& z-+lY-H@|%T^{ZUD@Ru)N$UvWmSI&9;j9|EkBLA8Ie`Qp2^>;~sJ*Zds@~f8YdCslx zBmSj_`_h|-_4A4KR8Z3gg^o^X5%>ATUh>O~JguQIypQ|5l|vc3<) z{NACyD;@@Jh*=Bo#d}FjO^6={?uhm1@XK62tlAxSC7#dcu4V4z{6O5=a3cOf-*dZs zbKDLwW!-m3h?T@otpYDzKw*)TT+WtRU;>Sz()}zfk;C;Gzbp=8Q@3PGZ!W3gr6p zGS4iN;34suTJ=+trvl4!T$2~`N$^%;DmoRoK-_M}Bv^^tti|#>^Hkt=!QVb{*S9OS zxySsry>K>4wMnq{SmPhw%T)p8J|JI|TnQSurX2{E7S!o!0if3;nfK;X!Cq|NdR73f z>5+Tj`Q>BH)q5|t4}4mH>i}*6IF&kW!LHDxtcKG9K=wl|gOwKBPX(VOKK-esdl_#y z*sHSSi=o|km3r6*ck(~Nbh1Y_DhcTJI_0GeYZEUR&zn-|nRZe4Gl=(|y+jS#w#gf3 z;)WhSNZg*}jSjzwn48?3yqzA)<}40XquH4D8k1nC>f+f;Vd*I8nLEUyj_tI_X@ zkEOU1IsHmZlKnRjyIvj`9m@FFLdbx>vBh!YPl;J8Ma4Dpc&cSJ^F7tgu9moj*py`G zvQs2WG9TX?VonI$d&KCKD#wj|;}jn5{ES5wYTJtbYMaTt`fc3mtAwU?@aGsf4arExTJBc}`Va$dZS|}rf_iEh!D&}PuPkso5=_N1ghpF><%?+Xk)W@NysjYc(%&v|E_15Yhhruj*hemkm=hdVL8 zQ2;BSVot<-v_SiEVyZCL@J0?|tQEuoc@V?sD#Lu*$b$fH+NPYx4`gKx$Vn?8A8_ou zNUzz+b$&x^?FF)GC1f0jY*@>0c(lscx&sFqqXK{v>|RhA@;b{ zK=yOx-Lykplo+I8pdmTiyoe@XB5U*YpVQH~I7$g5YDkQYSNYn%*HxtVtro&WD z^EnF6anft*ZJ3TFvNvGzLzbA&iRrC()04NT1={K&X6Y?EQ=55l5?`~dap>^MXE9s% zg$U>xmlC6NCLXn#EI48A6i4FjXrTggQyc5h2S|o`Hk!~5U^A>st+tk1^s5WRY8ww= z4XXM&zmd4>r&UQb++#4amm7{|rHv^(4sA@CDhz{J!>h$O{JAV}Sb6McY9m#x>X4^8!$p zWa5k~okoP6YLCu?_YtS|o}Ast{5Iln8f>`7vKJNCc*i)NUgHg~kHZpQ7x50wPR!ww zA95~weDF;-%6v`4{9Ybt?;T0^yghx7aiTS3tJ^PKlCyG^`Nd|BpMCGWka$BHv_kCM zeUI^NCo7^azB?KSJ&4Msibc8$DYcQ&5tA%~;=8)27KO!hUL^l9oo}BJ<33_hwT6@e zJu!IYlKYsK^ySnAh)2ggu@%yCJH#48y0$ti5pV)PupSKuiOIFZMS|)Wg+2Bh5$cl+ zlfpzJ3bz7DD`X`5dJ>5x>N4?=hzHYFq+UQz6l7_vQ&=`MGWY>|en>qeG&z=6e>COF zGCT9nIgb#dUw$_I+kmvmsukRChDGI(F$XLxv!We9Zu9IWozdPBL(znQ91m#)2Fp#v zb|mQD7IYj5EHRgevBbf@HJalS&+Lv3RATL`)T=#i6$08r#HdJ#n!7<94zcdXs7pFn zl&idiIII}O6oPM9M^iJ!XrQX_;)6<)9qYC7F<=_cf{BI%?Y|a?sVSxqqrphiYc3Fl z5S-^WTbL=qBK+>PtmgFiq4DnQz^)Nfv=<+P)(H0;Ji%0ARG5O24oxgzS=iXHIvdA( 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 literal 0 HcmV?d00001 diff --git a/maps/pudu_modified/map_keepout_baked.yaml b/maps/pudu_modified/map_keepout_baked.yaml new file mode 100644 index 0000000..4cfe823 --- /dev/null +++ b/maps/pudu_modified/map_keepout_baked.yaml @@ -0,0 +1,7 @@ +image: map_keepout_baked.pgm +mode: trinary +resolution: 0.05 +origin: [-5.85, -11.8, 0.0] +negate: 0 +occupied_thresh: 0.65 +free_thresh: 0.196 diff --git a/pudu_gui.py b/pudu_gui.py index b3e74f5..ebfe8cc 100644 --- a/pudu_gui.py +++ b/pudu_gui.py @@ -17,12 +17,14 @@ Pipeline (all local, per user click): exactly like start_r1_nav.sh; G1/Go2 = plain RViz on the local ROS). Requires: pillow numpy pyyaml (same as the original converter). Server binds -127.0.0.1 only. Conversion logic is vendored from pudu_to_ros2_map.py (verified +127.0.0.1 by default; set PUDU_BIND (an address, or 0.0.0.0) to expose it on the +LAN — it has NO auth and can drive the robots, so only on a trusted network. Conversion logic is vendored from pudu_to_ros2_map.py (verified against real exports: origin = bottom-left meters, PNG row 0 = top, 0/128/255). """ import base64 import io import json +import hmac import os import shutil import subprocess @@ -2255,9 +2257,119 @@ def open_rviz(robot, ip): # ───────────────────────── http plumbing ───────────────────────── +# ── AUTH ───────────────────────────────────────────────────────────────────── +# This dashboard ssh's into the robots, starts/stops their nav stacks, deletes +# maps, opens RViz on THIS machine's X display and (G1) arms the gait. Exposed on +# a LAN with no auth, anyone on the wifi can do all of that — which is exactly +# what happened: a stranger drove this dashboard and it looked like the laptop +# being remote-controlled. So: no token, no LAN. +# PUDU_TOKEN= required whenever PUDU_BIND is not loopback. +# Loopback stays open with no token: a local browser is already the trust boundary. +AUTH_TOKEN = os.environ.get("PUDU_TOKEN", "").strip() +BIND_ADDR = os.environ.get("PUDU_BIND", "127.0.0.1").strip() or "127.0.0.1" +# PUDU_OPEN=1 = deliberately serve the LAN with NO key (operator's explicit choice). +# Without it, exposing the port with no token FAILS CLOSED rather than silently +# handing the robots to the network — an accident should never open the door. +AUTH_OPEN = os.environ.get("PUDU_OPEN", "").strip() in ("1", "true", "yes") +AUTH_REQUIRED = (BIND_ADDR not in ("127.0.0.1", "localhost", "::1")) and not AUTH_OPEN + + +def _client_ok(headers, path, cookie_hdr): + """Token from the Authorization header, ?t=, or the pudu_t cookie.""" + if not AUTH_REQUIRED: + return True + if not AUTH_TOKEN: + return False # exposed without a token: refuse everything + got = "" + auth = headers.get("Authorization", "") + if auth.startswith("Bearer "): + got = auth[7:].strip() + if not got and "t=" in (path or ""): + from urllib.parse import urlparse, parse_qs + got = (parse_qs(urlparse(path).query).get("t") or [""])[0] + if not got and cookie_hdr: + for part in cookie_hdr.split(";"): + k, _, v = part.strip().partition("=") + if k == "pudu_t": + got = v + break + return bool(got) and hmac.compare_digest(got, AUTH_TOKEN) + + +LOGIN_PAGE = """ +Pudu Map GUI - sign in + + +
+

Pudu Map GUI

+

This dashboard can drive the robots. Enter the access key.

+ + +
Wrong key.
+
+""" + + class Handler(BaseHTTPRequestHandler): - def log_message(self, *a): - pass + def log_message(self, fmt, *a): + # ACCESS LOG. The container runs --rm, so an in-memory-only log dies with + # it — that is why "who was pressing my buttons?" was unanswerable. Write + # to the project dir so it survives. + try: + line = "%s %s %s\n" % (time.strftime("%Y-%m-%d %H:%M:%S"), + self.client_address[0], fmt % a) + with open(os.path.join(HERE, "access.log"), "a") as f: + f.write(line) + except Exception: + pass + + def _deny(self): + # A BROWSER asking for a PAGE gets the login form, so the plain + # http://:8777/ URL works — type the key once, the cookie carries it + # from then on. Anything else (fetch/XHR/curl) gets a clean 401. + wants_page = ("text/html" in (self.headers.get("Accept") or "") + and self.command == "GET") + if wants_page: + body = LOGIN_PAGE.encode() + self.send_response(401) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Cache-Control", "no-store") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + else: + body = b'{"error":"unauthorized - open the dashboard and sign in"}' + self.send_response(401) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + self.log_message("DENIED %s", self.path) + + def _authed(self): + return _client_ok(self.headers, self.path, self.headers.get("Cookie", "")) def _json(self, obj, code=200): body = json.dumps(obj).encode() @@ -2270,6 +2382,11 @@ class Handler(BaseHTTPRequestHandler): def _html(self, body): self.send_response(200) self.send_header("Content-Type", "text/html; charset=utf-8") + # Opening the page as ...:8777/?t=TOKEN stores it, so the app's own + # fetch() calls (which cannot know the token) stay authorised. + if AUTH_REQUIRED and AUTH_TOKEN and "t=" in self.path: + self.send_header("Set-Cookie", + f"pudu_t={AUTH_TOKEN}; Path=/; SameSite=Strict; Max-Age=86400") # no-store: a stale cached dashboard once ran OLD JS against a NEW # backend — "button does nothing" with zero errors. Never again. self.send_header("Cache-Control", "no-store") @@ -2278,6 +2395,8 @@ class Handler(BaseHTTPRequestHandler): self.wfile.write(body) def do_GET(self): + if not self._authed(): + return self._deny() path = self.path.split("?")[0] # ?v=... cache-busters must still match if path in ("/", "/index.html"): with open(os.path.join(HERE, "index.html"), "rb") as f: @@ -2291,6 +2410,28 @@ class Handler(BaseHTTPRequestHandler): self._json({"error": "not found"}, 404) def do_POST(self): + if self.path.split("?")[0] == "/login": + # the ONE unauthenticated endpoint: it grants the cookie, nothing else + try: + n = int(self.headers.get("Content-Length", 0)) + key = (json.loads(self.rfile.read(n) or b"{}").get("key") or "").strip() + except Exception: + key = "" + if AUTH_TOKEN and hmac.compare_digest(key, AUTH_TOKEN): + self.send_response(204) + self.send_header("Set-Cookie", + f"pudu_t={AUTH_TOKEN}; Path=/; SameSite=Strict; Max-Age=604800") + self.send_header("Content-Length", "0") + self.end_headers() + self.log_message("LOGIN ok") + else: + self.log_message("LOGIN FAILED") + self.send_response(403) + self.send_header("Content-Length", "0") + self.end_headers() + return + if not self._authed(): + return self._deny() n = int(self.headers.get("Content-Length", 0)) try: req = json.loads(self.rfile.read(n) or b"{}") @@ -2435,9 +2576,21 @@ def main(): except Exception as e: print(f"[pudu_gui] current-map restore skipped: {e}") threading.Thread(target=_reap_children, daemon=True).start() - srv = ThreadingHTTPServer(("127.0.0.1", PORT), Handler) - url = f"http://127.0.0.1:{PORT}" - print(f"[pudu_gui] serving {url} (Ctrl+C to stop)") + # BIND: localhost by default. Set PUDU_BIND to expose it on the LAN, e.g. + # PUDU_BIND=10.255.254.83 (this workstation's wifi address) + # PUDU_BIND=0.0.0.0 (every interface) + # ⚠ THIS DASHBOARD HAS NO AUTHENTICATION and it can ssh into the robots, start + # and stop their nav stacks, delete maps and — on the G1 — arm the gait. Anyone + # who can reach this port can do all of that. Only expose it on a network you + # trust, and prefer naming ONE interface over 0.0.0.0. + bind = os.environ.get("PUDU_BIND", "127.0.0.1").strip() or "127.0.0.1" + srv = ThreadingHTTPServer((bind, PORT), Handler) + shown = "127.0.0.1" if bind in ("0.0.0.0", "::") else bind + url = f"http://{shown}:{PORT}" + print(f"[pudu_gui] serving {url} (bound {bind}:{PORT}; Ctrl+C to stop)") + if bind not in ("127.0.0.1", "localhost"): + print("[pudu_gui] WARNING: reachable from the network and NOT authenticated — " + "it can drive the robots. Keep it on a trusted LAN.") threading.Timer(0.8, lambda: webbrowser.open(url)).start() try: srv.serve_forever() diff --git a/start.sh b/start.sh index 116f5e3..1b24adf 100755 --- a/start.sh +++ b/start.sh @@ -32,7 +32,18 @@ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" IMG=pudu-map-gui NAME=pudu-map-gui PORT=8777 -URL="http://127.0.0.1:$PORT" +# PUDU_BIND: which address the dashboard listens on. Default localhost-only. +# PUDU_BIND=10.255.254.83 ./start.sh restart -> reachable from the LAN +# PUDU_BIND=0.0.0.0 ./start.sh restart -> every interface +# The container already runs --net=host, so this is the ONLY thing that decides +# reachability. NO AUTH: anyone who can reach the port can drive the robots. +PUDU_BIND="${PUDU_BIND:-127.0.0.1}" +# health-check a REACHABLE address: 0.0.0.0 is a bind wildcard, not a destination +case "$PUDU_BIND" in + 0.0.0.0|::) HOSTADDR=127.0.0.1 ;; + *) HOSTADDR="$PUDU_BIND" ;; +esac +URL="http://$HOSTADDR:$PORT" _stop() { # the GUI's own viewer processes live INSIDE the container, so removing it @@ -62,6 +73,9 @@ RUN=(docker run --rm --net=host --name "$NAME" -e DISPLAY="${DISPLAY:-:0}" -e HOME="$HOME" -e PUDU_GUI_DIR="$HERE" + -e PUDU_BIND="$PUDU_BIND" + -e PUDU_TOKEN="${PUDU_TOKEN:-}" + -e PUDU_OPEN="${PUDU_OPEN:-}" -v /tmp/.X11-unix:/tmp/.X11-unix -v "$HOME/Robotics_workspace:$HOME/Robotics_workspace" -v "$HOME/Downloads:$HOME/Downloads"