Ship Meshtastic Chat UX, embedded Infonet/SHELL panels, and Docker dev polish.

Rename Mesh Chat to Meshtastic Chat, embed the Infonet terminal with Arti/Tor warmup, improve the agent shell PTY (git in the backend image, operator PATH), and add docker-compose.override for local image builds. Gitignore Hermes Agent runtime installs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
BigBodyCobain
2026-06-11 00:55:38 -06:00
parent d1e1be4016
commit e78e4d186d
24 changed files with 1258 additions and 970 deletions
+2 -1
View File
@@ -27,6 +27,7 @@ WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
curl \
git \
tor \
&& curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
&& apt-get install -y --no-install-recommends nodejs \
@@ -72,7 +73,7 @@ ENV PRIVACY_CORE_LIB=/app/libprivacy_core.so
# Create a non-root user for security
# Grant write access to /app so the auto-updater can extract files
# Pre-create /app/data so mounted volumes inherit correct ownership
RUN adduser --system --uid 1001 backenduser \
RUN adduser --system --uid 1001 --home /app backenduser \
&& mkdir -p /app/data \
&& chown -R backenduser /app \
&& chmod -R u+w /app
+36 -1
View File
@@ -43,9 +43,32 @@ def _set_winsize(fd: int, rows: int, cols: int) -> None:
fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
def _published_local_dashboard_ws(ws: WebSocket) -> bool:
"""Browser → published Docker port appears as a bridge IP, not loopback.
For the operator shell only, also accept when the upgrade request clearly
targets the local dashboard (Host/Origin on localhost).
"""
host_header = str(ws.headers.get("host") or "").strip().lower()
host_name = host_header.split(":", 1)[0]
if host_name in {"127.0.0.1", "localhost", "::1"}:
return True
origin = str(ws.headers.get("origin") or "").strip().lower()
if origin.startswith("http://127.0.0.1:") or origin.startswith("http://localhost:"):
return True
if origin.startswith("https://127.0.0.1:") or origin.startswith("https://localhost:"):
return True
return False
async def _authorize_agent_shell_ws(ws: WebSocket, admin_key_query: str = "") -> None:
host = (ws.client.host or "").lower() if ws.client else ""
if _is_trusted_local_runtime_host(host) or (_debug_mode_enabled() and host == "test"):
if (
_is_trusted_local_runtime_host(host)
or _published_local_dashboard_ws(ws)
or (_debug_mode_enabled() and host == "test")
):
return
admin_key = _current_admin_key()
presented = str(admin_key_query or ws.headers.get("x-admin-key", "") or "").strip()
@@ -164,6 +187,18 @@ async def agent_shell_websocket(
env = os.environ.copy()
env.setdefault("TERM", "xterm-256color")
env.setdefault("COLORTERM", "truecolor")
home = shell_cwd if os.path.isdir(shell_cwd) else "/app"
env["HOME"] = home
env["USER"] = env.get("USER") or "operator"
path_prefixes = [
os.path.join(home, ".local", "bin"),
os.path.join(home, ".hermes", "bin"),
]
path = env.get("PATH", "")
for prefix in path_prefixes:
if os.path.isdir(prefix):
path = f"{prefix}:{path}" if path else prefix
env["PATH"] = path
proc = await asyncio.create_subprocess_exec(
shell,
+7 -1
View File
@@ -16,7 +16,13 @@ _LOCK = threading.Lock()
def _default_working_directory() -> str:
return os.environ.get("AGENT_SHELL_DEFAULT_CWD") or os.environ.get("HOME") or "/app"
explicit = str(os.environ.get("AGENT_SHELL_DEFAULT_CWD") or "").strip()
if explicit and os.path.isdir(explicit):
return explicit
home = str(os.environ.get("HOME") or "").strip()
if home and home != "/nonexistent" and os.path.isdir(home):
return home
return "/app"
def get_agent_shell_settings() -> dict[str, Any]: