#!/usr/bin/env python3.14# -*- coding: utf-8 -*-"""The shell tier's client for the message spool: one call, whichever plane answers.Extracted from :mod:`euler_webshell.msg.commands`, which was its only caller until`user-authorize <thread-id>` needed to read and answer a thread from:mod:`solver.crypto.keys` (web-server-guide § Messaging). A command that wants the spoolnow asks here rather than re-deriving the two-plane dance — or, worse, reaching into the`msg` command's privates.**Two channels, one call.** A web shell's uid is in `euler-web`, so it dials`msg.sock` directly and the kernel proves who it is. The operator's terminal uid isdeliberately *not* in that group, so it falls back to`sudo python -m euler_webshell.msg.admin`, which injects the invoking identity and proxiesthe same call. The channel is chosen by *what this process can reach*, never by a flag:a collaborator never sees a sudo prompt, and the operator never has to remember whichplane they are on.**Silent.** Failures are logged, not printed: this is called from inside other commands,which own what the user reads. :func:`call` returns `None` for "neither planeanswered", and the caller says so in its own words.Stdlib-only (via :mod:`euler_site.unixhttp`), like the rest of the shell-facing half ofthis package."""from__future__importannotations__all__=['OPS','call','sudo_plane_present']importjsonimportloggingimportosimportsubprocessimportsysfrompathlibimportPathfromtypingimportAnyfromsolver.config.envimportload_specfromeuler_site.unixhttpimportrequestlog=logging.getLogger('solver.msg')#: The scoped env file the admin CLI reads its token from — its presence is what tells#: us a spool is deployed here at all, so a plain dev checkout never prompts for sudo._MSG_ENV='/etc/euler/msg.env'#: Logical operation → (method, public path, admin path). The two planes expose the same#: verbs under different prefixes; this table is the only place that knows both. It maps the#: *service*, not the shell command, which is why `queue` is still here with no caller: the#: `msg queue` verb went when `list` absorbed it (the mailbox already shows staff every#: inbound thread), and the service kept the endpoint.OPS:dict[str,tuple[str,str,str]]={'mailbox':('GET','/messages','/admin/messages'),'thread':('GET','/messages/{id}','/admin/threads/{id}'),'send':('POST','/messages','/admin/messages'),'read':('POST','/messages/{id}/read','/admin/threads/{id}/read'),'queue':('GET','/staff/queue','/admin/queue'),'notice':('POST','/staff/notice','/admin/notice'),'dismiss':('DELETE','/staff/queue/{id}','/admin/threads/{id}'),}def_socket_path()->str:"""The public spool socket (env-overridable, for tests and dev runs)."""returnload_spec('msg').raw('socket_path')defsudo_plane_present()->bool:"""Whether a spool admin plane exists to sudo into. Checked before spending a sudo prompt: on a plain dev checkout with no web stack there is nothing behind the fallback, and asking for a password only to fail is worse than saying the service is not reachable. """return(Path(os.environ.get('EULER_MSG_ENV',_MSG_ENV)).exists()orPath(load_spec('msg').raw('admin_socket_path')).exists())def_direct(method:str,path:str,body:dict[str,Any]|None)->tuple[int,Any]|None:"""Call the spool over `msg.sock`; None when this uid cannot reach it. An `OSError` here is the *expected* outcome for the operator's own uid (not in `euler-web`) and for a host with no spool deployed — it is the signal to try the sudo plane, not an error to report. """try:returnrequest(_socket_path(),method,path,body=body,timeout=15.0)exceptOSError:returnNonedef_via_sudo(method:str,path:str,body:dict[str,Any]|None)->tuple[int,Any]|None:"""Call the spool through the sudo admin CLI; None when that fails outright. The body goes in on **stdin**, never in `argv` — a message in the process table would be readable by every uid on the host. """argv=['sudo',sys.executable,'-m','euler_webshell.msg.admin','api',method,path]try:proc=subprocess.run(argv,input=json.dumps(bodyor{}),stdout=subprocess.PIPE,text=True,check=False)except(OSError,KeyboardInterrupt)asexc:log.debug('message admin CLI could not be run (%s)',exc)returnNoneifproc.returncode!=0:returnNone# the CLI already reported on stderrtry:envelope=json.loads(proc.stdoutor'{}')returnint(envelope['status']),envelope['body']except(json.JSONDecodeError,KeyError,TypeError,ValueError):log.debug('malformed reply from the message admin plane: %r',proc.stdout)returnNonedefcall(op:str,*,thread_id:str='',body:dict[str,Any]|None=None)->tuple[int,Any]|None:"""Run *op* on whichever plane this process can reach; None when neither answered."""method,public,admin=OPS[op]direct=_direct(method,public.format(id=thread_id),body)ifdirectisnotNone:returndirectifnotsudo_plane_present():returnNonereturn_via_sudo(method,admin.format(id=thread_id),body)