
    epjZb             	          U d Z ddlZddlZddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlmZ ddlmZ ddlmZmZ ddlmZmZ  ej        e          Zg dZdaee         ed<    ej        d	          Zd
Zdee         dz  dee         fdZ de!dz  de!eef         fdZ"de!eef         fdZ# ej        d          Z$dedefdZ%defdZ&ddddde'dedz  dedz  de'fdZ(dedefdZ)dee         fdZ*g dZ+dZ,d Z-d!ede.fd"Z/d#Z0d$Z1g d%Z2de3ee         e!eef         ee         f         fd&Z4d'ee         d(e!eef         d)ee         defd*Z5d>d,e.de.fd-Z6d(e!eef         de7e         fd.Z8d!ee         d/e7e         dee         fd0Z9d?d2e.d3e.dee         fd4Z:ded5ede.fd6Z;dee         fd7Z<da=ee.         ed8<   da>ee.         ed9<   d5ede.fd:Z?d@d;Z@ G d< d=e          ZAdS )AzDocker execution environment for sandboxed command execution.

Security hardened (cap-drop ALL, no-new-privileges, PID limits),
configurable resource limits (CPU, memory, disk), and optional filesystem
persistence via bind mounts.
    N)Path)Optional)BaseEnvironment_popen_bash)_HERMES_PROVIDER_ENV_BLOCKLIST_is_hermes_internal_secret)z/usr/local/bin/dockerz/opt/homebrew/bin/dockerz6/Applications/Docker.app/Contents/Resources/bin/docker_docker_executablez^[A-Za-z_][A-Za-z0-9_]*$zhermes-egressforward_envreturnc                    g }t                      }| pg D ]}t          |t                    st                              d|           3|                                }|sJt                              |          st                              d|           ||v r|                    |           |	                    |           |S )z?Return a deduplicated list of valid environment variable names.z0Ignoring non-string docker_forward_env entry: %rz-Ignoring invalid docker_forward_env entry: %r)
set
isinstancestrloggerwarningstrip_ENV_VAR_NAME_REmatchaddappend)r
   
normalizedseenitemkeys        ?/home/thesage/.hermes/hermes-agent/tools/environments/docker.py_normalize_forward_env_namesr   ,   s    JUUD!r  $$$ 	NNMtTTTjjll 	%%c** 	NNJDQQQ$;;#    envc                 ^   | si S t          | t                    st                              d|            i S i }|                                 D ]\  }}t          |t
                    r,t                              |                                          st                              d|           b|                                }t          |t
                    sOt          |t          t          t          f          rt          |          }nt                              d||           |||<   |S )zValidate and normalize a docker_env dict to {str: str}.

    Filters out entries with invalid variable names or non-string values.
    zdocker_env is not a dict: %rz#Ignoring invalid docker_env key: %rz/Ignoring non-string docker_env value for %r: %r)r   dictr   r   itemsr   r   r   r   intfloatbool)r   r   r   values       r   _normalize_env_dictr&   E   s   
  	c4   5s;;;	!#Jiikk    
U#s## 	+;+A+A#))+++N+N 	NN@#FFFiikk%%% 	 %#ud!344 E

PRUW\]]]
3r   c                  L    	 ddl m}   |             pi S # t          $ r i cY S w xY w)zDLoad ~/.hermes/.env values without failing Docker command execution.r   load_env)hermes_cli.configr)   	Exceptionr(   s    r   _load_hermes_env_varsr,   c   sN    ......xzzR   			s    ##z[^A-Za-z0-9_.-]r%   c                     t          | t                    r| sdS t                              d|           }|dd         pd}|S )u  Coerce *value* into a Docker label-safe form (alnum + ``_.-``, ≤63 chars).

    Empty or all-invalid inputs collapse to ``"unknown"`` so the resulting
    label is always queryable. Used at container-create time; never round-trip
    a sanitized value back into application logic.
    unknown_N?   )r   r   _LABEL_VALUE_OK_REsub)r%   cleaneds     r   _sanitize_label_valuer4   s   sN     eS!!  y $$S%00Gcrcl'iGNr   c                  J    	 ddl m}   |             pdS # t          $ r Y dS w xY w)a   Return the active Hermes profile name, or ``"default"`` on any error.

    Resolved at container-create time so a single container is permanently
    tagged with the profile that created it. Profile switches inside the
    same process don't retroactively relabel running containers.
    r   get_active_profile_namedefault)hermes_cli.profilesr7   r+   r6   s    r   _get_active_profile_namer:      sO    ??????&&((5I5   yys    
""iX  )max_age_secondsprofile_filter
docker_exer;   r<   r=   c           
         |pt                      pd}g d}|r'|                    ddt          |           g           	 t          j        |ddg|ddd	d	d
dddt          j                  }n?# t          j        t          f$ r&}t          	                    d|           Y d}~dS d}~ww xY w|j
        dk    r:t          	                    d|j
        |j                                                   dS d |j                                        D             }|sdS ddl}|j                            |j        j                  }	d}
|D ]"}t'          ||          }||	|z
                                  }|| k     r4	 t          j        |dd|gd	d	d
ddt          j                  }|j
        dk    r7|
dz  }
t                              d|dd         t-          |                     n;t          	                    d|dd         |j                                                   # t          j        t          f$ r/}t          	                    d|dd         |           Y d}~d}~ww xY w|
S )u{  Remove stale hermes-tagged containers left behind by prior processes.

    Targets containers that match all of:

    * ``label=hermes-agent=1`` (created by this codebase)
    * ``status=exited`` (running containers are NEVER reaped — they may
      belong to a sibling Hermes process whose reuse path will pick them
      up; killing them would crash the sibling mid-command)
    * (optional) ``label=hermes-profile=<profile_filter>`` (sweep only the
      caller's profile by default; a hermes process in profile A must not
      tear down profile B's containers)
    * ``State.FinishedAt`` older than *max_age_seconds* ago (so a sibling
      process that just exited and is about to be replaced doesn't get
      its container yanked out from under it)

    Returns the number of containers removed. Best-effort: any failure
    (docker daemon unreachable, slow inspect, parse error) is logged at
    debug level and the function returns whatever it managed before the
    failure. Safe to call repeatedly; idempotent.

    Issue #20561 — this is the safety net for SIGKILL / OOM / crashed
    terminal exits that bypass the ``atexit`` cleanup hook. Without it,
    even with the cleanup-fix in the prior commit, a hard-killed Hermes
    process leaves its container behind permanently because there's no
    subsequent Hermes process scheduled to reuse that exact (task, profile)
    pair.
    docker)--filterlabel=hermes-agent=1r@   zstatus=exitedr@   label=hermes-profile=ps-a--formatz{{.ID}}Tutf-8replace   Fcapture_outputtextencodingerrorstimeoutcheckstdinz"orphan reaper docker ps failed: %sNr   z'orphan reaper docker ps returned %d: %sc                 ^    g | ]*}|                                 |                                 +S  r   .0lns     r   
<listcomp>z*reap_orphan_containers.<locals>.<listcomp>   s-    TTTBTRXXZZTTTr   rm-f   rJ   rK   rL   rM   rN   rP      z2Reaped orphan container %s (exited %d seconds ago)   docker rm -f %s failed: %sz%orphan reaper docker rm %s failed: %s)find_dockerextendr4   
subprocessrunDEVNULLTimeoutExpiredOSErrorr   debug
returncodestderrr   stdout
splitlinesdatetimenowtimezoneutc_container_finished_attotal_secondsinfor"   )r;   r<   r=   r?   filterslistingecandidate_idsrk   rl   removedcidfinished_atageresults                  r   reap_orphan_containersr{      s   B 4;==4HFOOOG f
$c<QR`<a<a$c$cdeee.T4A'A:AyAdWYXZbg$
 
 

 %w/   91===qqqqq Q5 4 4 6 6	
 	
 	
 qTT'.*C*C*E*ETTTM q
 OOO



 1 5
6
6CG O O,VS99[ //11  	O^tS)#$\^ (  F
  A%%1HHc#hh   
 0Hfm1133   )73 	O 	O 	OLL@#crc(ANNNNNNNN	ONs1   /A1 1B-B((B-=B&H$$I):$I$$I)container_idc           
         	 t          j        | ddd|gddddddt           j        	          }nH# t           j        t          f$ r/}t
                              d
|dd         |           Y d}~dS d}~ww xY w|j        dk    rdS |j        	                                }|r|
                    d          rdS ddl}|                    dd|          }|                    dd          }	 ddl}|j                            |          S # t           $ r0}t
                              d||dd         |           Y d}~dS d}~ww xY w)u@  Parse ``docker inspect`` FinishedAt for *container_id*.

    Returns a timezone-aware datetime, or ``None`` if the field is missing,
    unparseable, or the zero-value ``0001-01-01T00:00:00Z`` Docker emits
    for never-finished containers. ``None`` means "don't reap" — the caller
    leaves the container alone.
    inspectrE   z{{.State.FinishedAt}}TrF   rG   
   FrI   z*orphan reaper docker inspect %s failed: %sNr]   r   z
0001-01-01z(\.\d{6})\d+z\1Zz+00:00z(could not parse FinishedAt %r for %s: %s)ra   rb   rc   rd   re   r   rf   rg   ri   r   
startswithrer2   rG   rk   fromisoformat
ValueError)r=   r|   rz   rt   raw_rerk   s          r   ro   ro      s   J0GVdWYXZbg$
 
 

 %w/   A<PSQSPSCTVWXXXttttt At
-



C #.... t 
''/5#
.
.C
++c8
$
$C ..s333   ?lSVTVSVFWYZ[[[ttttts-   +. A3$A..A3'D 
D?%D::D?c                  x   t           t           S t          j        d          } | r]t          j                            |           r>t          j        | t          j                  r| a t                              d|            | S t          j
        d          }|r|a |S t          j
        d          }|r|a t                              d|           |S t          D ]a}t          j                            |          r@t          j        |t          j                  r!|a t                              d|           |c S bdS )u  Locate the docker (or podman) CLI binary.

    Resolution order:
    1. ``HERMES_DOCKER_BINARY`` env var — explicit override (e.g. ``/usr/bin/podman``)
    2. ``docker`` on PATH via ``shutil.which``
    3. ``podman`` on PATH via ``shutil.which``
    4. Well-known macOS Docker Desktop install locations

    Returns the absolute path, or ``None`` if neither runtime can be found.
    NHERMES_DOCKER_BINARYz'Using HERMES_DOCKER_BINARY override: %sr?   podmanz%Using podman as container runtime: %sz%Found docker at non-PATH location: %s)r	   osgetenvpathisfileaccessX_OKr   rq   shutilwhich_DOCKER_SEARCH_PATHS)overridefoundr   s      r   r_   r_     s7    %!! y/00H BGNN8,, 8RW1M1M %=xHHH L""E " L""E ";UCCC %  7>>$ 	BIdBG$<$< 	!%KK?FFFKKK4r   )z
--cap-dropALL	--cap-addDAC_OVERRIDEr   CHOWNr   FOWNERz--security-optzno-new-privileges--tmpfsz/tmp:rw,nosuid,size=512mr   z#/var/tmp:rw,noexec,nosuid,size=256m2561g
extra_argsc                 8    t          d | pg D                       S )zTrue when user-supplied docker_extra_args already set ``--shm-size``.

    In that case we skip our default so the user's value is unambiguous
    (rather than relying on flag-ordering / last-wins behavior).
    c              3   v   K   | ]4}t          |t                    o|d k    p|                    d          V  5dS )
--shm-sizez--shm-size=N)r   r   r   )rU   as     r   	<genexpr>z+_extra_args_set_shm_size.<locals>.<genexpr>s  s[         	1cQ\ 1 PQ\\-5P5P     r   )any)r   s    r   _extra_args_set_shm_sizer   m  s7       "     r   )r   z/run:rw,noexec,nosuid,size=64m)r   z/run:rw,exec,nosuid,size=64m)r   SETUIDr   SETGIDc                     	 ddl m}  ddlm} n7# t          $ r*}t
                              d|           g i g fcY d}~S d}~ww xY w |             }|                    d          pi }|                    d          sg i g fS |                                }t          |                    dd	                    }|j
        s3d
}|rt          |          t
                              d|           g i g fS |j        r|j        s<d|j         d}|rt          |          t
                              d|           g i g fS |j        |j                                        s<d|j         d}|rt          |          t
                              d|           g i g fS |                                }|s3d}|rt          |          t
                              d|           g i g fS d}	d|j         d|	 dg}
d|j         }d|j        dz    }||||dd|	|	|	|	ddd}|D ]C}|j        ||j        <   |j        |d|j         <   t+          |dd          pdD ]}|j        ||<   Dddg}|
||fS ) u  Build the docker mount/env/host args needed to route a sandbox through
    the iron-proxy egress firewall.

    Returns ``(volume_args, env_overrides, host_args)``:

    * ``volume_args`` — read-only bind mount of the CA cert into the container
      (extends docker's ``-v`` argv list)
    * ``env_overrides`` — env vars to set on container creation: ``HTTPS_PROXY``,
      ``HTTP_PROXY``, ``NO_PROXY`` (loopback only), Python/Node/curl CA-bundle
      paths, and one ``HERMES_PROXY_TOKEN_<NAME>`` per minted mapping
    * ``host_args`` — extra ``--add-host`` flags so the container can reach the
      host-side proxy (Linux needs ``host.docker.internal:host-gateway``;
      Docker Desktop populates this automatically on macOS/Windows)

    Returns three empty containers when the proxy is disabled, not yet set up,
    or not currently running.  If ``proxy.enforce_on_docker`` is true and the
    proxy is enabled-but-not-running, raises ``RuntimeError`` so the docker
    backend refuses to start the sandbox.
    r   load_config
iron_proxyz%Egress proxy plumbing unavailable: %sNproxyenabledenforce_on_dockerTzvproxy.enabled is true but iron-proxy is not configured. Run `hermes egress setup` to mint tokens and write proxy.yaml.u:   %s — continuing without proxy (enforce_on_docker=false).z.iron-proxy is enabled but not running on port z&. Start it with `hermes egress start`.z!iron-proxy CA cert vanished from z0. Re-run `hermes egress setup` to regenerate it.ziron-proxy is configured but mappings.json is empty or corrupt.  Re-run `hermes egress setup` to mint provider tokens before starting a sandbox.z#/etc/ssl/certs/hermes-egress-ca.crt-v::rozhttp://host.docker.internal:r\   z127.0.0.1,localhost,::11--use-openssl-ca)HTTPS_PROXYhttps_proxy
HTTP_PROXY
http_proxyNO_PROXYno_proxyREQUESTS_CA_BUNDLESSL_CERT_FILECURL_CA_BUNDLENODE_EXTRA_CA_CERTSHERMES_EGRESS_PROXY"_HERMES_EGRESS_NODE_OPTIONS_APPENDHERMES_PROXY_TOKEN_alias_env_namesrR   z
--add-hostz!host.docker.internal:host-gateway)r*   r   agent.proxy_sourcesr   ImportErrorr   rf   get
get_statusr$   
configuredRuntimeErrorr   pid	listeningtunnel_portca_cert_pathexistsload_mappingsproxy_tokenreal_env_namegetattr)r   ipexccfg	proxy_cfgstatusenforcemsgmappingscontainer_cavolume_args	proxy_urlplain_http_urlenv_overridesmalias	host_argss                    r   _egress_proxy_args_for_dockerr     s   41111118888888   <cBBBB| +--C  &BI==## B|]]__F9==!4d;;<<G M 	  	$s###SUXYYYB|J 6+ 3V=O 3 3 3 	  	$s###SUXYYYB|"&*=*D*D*F*F"=0C = = = 	  	$s###SUXYYYB| !!H 	0 	
  	$s###SUXYYYB|8LF/CC,CCCDK Dv/ACCILF4F4JLLN
 ! $$ .- +%&+  # /AK&% &%M\  1 1)*ao&AB=AO==>Q 1266<" 	1 	1E#$=M%  	1 )*MNI	22s    
A>AAr   r   r   c                     | s|s|sdS t          j        | ||ddd          }t          j        |                    d                                                    dd         S )	z@Stable Docker-label value for the egress posture of a container.off)r   r   r   T),r   )	sort_keys
separatorsrF   N   )jsondumpshashlibsha256encode	hexdigest)r   r   r   payloads       r   _egress_reuse_fingerprintr   *  s      = I uj&*"	
 	

   G >'..1122<<>>ssCCr   Tr8   c                     	 ddl m} t           |                                d          pi                     d|                     S # t          t
          f$ r | cY S t          $ r | cY S w xY w)z7Read proxy.enforce_on_docker with fail-safe defaulting.r   r   r   r   )r*   r   r$   r   r   re   r+   )r8   	_load_cfgs     r   _egress_enforce_on_dockerr   >  s    >>>>>>YY[[__W--3889LgVVWWW!      s   AA A+A+*A+c                 L    h d}|                     d | D                        |S )zDEnv names that would weaken or bypass enforced egress if overridden.>   r   r   r   r   r   r   NODE_OPTIONSr   r   r   r   c              3   n   K   | ]0}|                     d           s|                     d          ,|V  1dS )_API_KEY_TOKENN)endswith)rU   r   s     r   r   z-_critical_egress_env_names.<locals>.<genexpr>R  s\        <<
##'*||H'='=     r   )update)r   criticals     r   _critical_egress_env_namesr   J  sN      H OO  $      Or   critical_namesc                 N   g }h d}ddh}d}|t          |           k     ri| |         |dz   t          |           k     r| |dz            nd}|v rWdk    r|                               n5|                    dd          d         }||v r|                    |           |d	z  }t          fd
|D                       r{                    d          r|                    d           n                    dd          d                             dd          d         }||v r|                    |           n4|v st          fd|D                       r|                               |dz  }|t          |           k     it          t          |                    S )zCReturn docker_extra_args entries that can override egress controls.>   --env-e
--env-filez	--networkz--netr   r\    r   =   c              3   H   K   | ]}                     | d           V  dS r  Nr   rU   flagargs     r   r   z0_extra_args_egress_collisions.<locals>.<genexpr>m  s5      @@ds~~jjj))@@@@@@r   z--env-file=c              3   H   K   | ]}                     | d           V  dS r  r  r  s     r   r   z0_extra_args_egress_collisions.<locals>.<genexpr>t  s5      (^(^4


)C)C(^(^(^(^(^(^r   )lenr   splitr   r   sortedr   )	r   r   
collisions	env_flagsnetwork_flagsinxtnamer	  s	           @r   _extra_args_egress_collisionsr  Y  s    J---I '*M	A
c*oo

m#$q53z??#:#:jQ)l""!!#&&&&yya((+>))%%d+++FA@@@@i@@@@@ 	#~~m,, ,!!,////yya((+11#q99!<>))%%d+++M!!S(^(^(^(^P](^(^(^%^%^!c"""	Q+ c*oo

, #j//"""r   Frun_as_host_userrun_execc                     t          |rt          nt                    }t          t                    |z   }| r|S |t          t                    z   S )aI  Return the security/cap/tmpfs args tailored to the privilege mode.

    ``run_exec`` mounts ``/run`` with ``exec`` instead of the hardened
    ``noexec`` default. This is required for s6-overlay images whose ``/init``
    entrypoint execs ``/run/s6/basedir/bin/init`` during startup; see
    ``_image_uses_init_entrypoint``.
    )list_RUN_TMPFS_EXEC_RUN_TMPFS_NOEXEC_BASE_SECURITY_ARGS_PRIVDROP_CAP_ARGS)r  r  	run_tmpfsargss       r   _build_security_argsr  z  sO     G__6GHHI#$$y0D $)****r   imagec           	         	 t          j        | dd|ddgdddddt           j        	          }n@# t           j        t          f$ r'}t
                              d
||           Y d}~dS d}~ww xY w|j        dk    r;t
                              d||j        |j        	                                           dS |j
        pd	                                }|r|dk    rdS 	 t          j        |          }n# t          t          f$ r Y dS w xY wt          |t                     r|g}t          |t"                    r|sdS t!          |d                   	                                }|dv S )a  Return True if ``image``'s entrypoint is the s6-overlay ``/init``.

    Such images (e.g. anything built on ``s6-overlay``, including
    ``hermes-agent:latest``) already provide their own PID-1 init and execute
    ``/run/s6/basedir/bin/init`` during stage0 startup. They are incompatible
    with Docker's ``--init`` (two competing PID-1 inits) and with a ``noexec``
    ``/run`` mount. Detection is best-effort: on any inspection failure we
    return False and keep the hardened defaults.
    r   r~   rE   z{{json .Config.Entrypoint}}TrF   rG   rH   r[   z/Docker: could not inspect entrypoint for %s: %sNFr   z4Docker: image inspect for %s returned %d (stderr=%s)r  null)z/initz&/package/admin/s6-overlay/command/init)ra   rb   rc   SubprocessErrorre   r   rf   rg   rh   r   ri   r   loadsr   	TypeErrorr   r   r  )r=   r   rz   rt   r   
entrypointfirsts          r   _image_uses_init_entrypointr(    s   )U68	$
 
 
 &0   FqQQQuuuuu A 	B6$fm&9&9&;&;	
 	
 	
 u=B
%
%
'
'C #--uZ__

	"   uu*c"" " \
j$'' z u
1$$&&EGGGs'   +. A+A&&A+C/ /DDc                      t          t          dd          } t          t          dd          }| |dS 	  |              d |             S # t          $ r Y dS w xY w)au  Return ``<uid>:<gid>`` for the current host user, or ``None`` on platforms
    where this is not meaningful (e.g. Windows without posix ids).

    We intentionally read ``os.getuid()``/``os.getgid()`` directly rather than
    going through ``getpass``/``pwd`` so this stays cheap and never raises on
    nameless UIDs (nss lookups can fail inside sandboxed launchers).
    getuidNgetgidr   )r   r   r+   )get_uidget_gids     r   _resolve_host_user_specr.    s{     b(D))Gb(D))G'/t'))))ggii)))   tts   A 
AA_storage_opt_ok_cgroup_limits_okc                    t           t           S t                      }|r| sda dS 	 t          j        |ddddddd	d
| ddgdddddt          j                  }|j        dk    a t           s<t                              d|j        pd	                                dd                    n4# t          $ r'}da t                              d|           Y d}~nd}~ww xY wt           S )u/  Probe whether cgroup resource limits work in this environment.

    Tests ``--cpus``, ``--memory`` and ``--pids-limit`` together by spawning
    a throwaway container from *image* (the same sandbox image we are about
    to use for real, so no extra pull and no dependency on a public
    registry). The container runs ``sleep 0`` — sleep is guaranteed to be
    present because the sandbox itself uses ``sleep 2h`` as its long-lived
    entrypoint.

    On hosts where the corresponding cgroup controllers are not delegated
    to this process (typical inside unprivileged LXCs and some rootless
    setups) these flags cause every container start to fail with ``OCI
    runtime error`` / exit 126. The probe runs once per process and the
    result — which is host-wide, not image-specific — is cached.
    NFrb   z--rm--cpusz0.5--memory64m--pids-limit32sleep0TrF   rG   <   r[   r   zCgroup resource limits (--cpus/--memory/--pids-limit) not available in this environment. Containers will run without CPU, memory or PID limits. To enable, delegate the cpu, memory and pids cgroup controllers to this container. Probe stderr: %sr  i  z8Cgroup limit probe failed; disabling resource limits: %s)r0  r_   ra   rb   rc   rg   r   r   rh   r   r+   )r   r=   rz   rt   s       r   _cgroup_limits_availabler:    s1   " $  J U !uVuj%GS"  	$
 
 
 #-2  	NN#
 $"++--dsd3    V V V!QSTUUUUUUUUV s   A?B& &
C0CCc            	         t                      } | s)t                              d           t          d          	 t	          j        | dgdddddt          j                  }|j        d	k    rHt                              d
| |j        |j        	                                           t          d          dS # t          $ r- t                              d| d           t          d          t          j        $ r- t                              d| d           t          d          t          $ r t                              dd            w xY w)zBest-effort check that the docker CLI is available before use.

    Reuses ``find_docker()`` so this preflight stays consistent with the rest of
    the Docker backend, including known non-PATH Docker Desktop locations.
    zDocker backend selected but no docker executable was found in PATH or known install locations. Install Docker Desktop and ensure the CLI is available.z|Docker executable not found in PATH or known install locations. Install Docker and ensure the 'docker' command is available.versionTrF   rG      r[   r   zIDocker backend selected but '%s version' failed (exit code %d, stderr=%s)zXDocker command is available but 'docker version' failed. Check your Docker installation.zVDocker backend selected but the resolved docker executable '%s' could not be executed.)exc_infozHDocker executable could not be executed. Check your Docker installation.zYDocker backend selected but '%s version' timed out. The Docker daemon may not be running.zHDocker daemon is not responding. Ensure Docker is running and try again.z4Unexpected error while checking Docker availability.N)r_   r   errorr   ra   rb   rc   rg   rh   r   FileNotFoundErrorrd   r+   )r=   rz   s     r   _ensure_docker_availablerA     s    J 	
 	
 	
 	

 K
 
 	

.#	$
 
 
D !!LL,!##%%   2   "!7  	
 	
 	
	 	 	
 	
 	
 V
 
 	
 $ 	
 	
 	
4	 	 	
 	
 	
 V
 
 	
    B 	 	
 	
 	
 	s   'B7 7BEc            %           e Zd ZdZdZdeedf         fdZdddddd	d
dddddd	d	ddefdedede	de
de	de	dedededee         dz  dedz  dededededededef$ fdZdee         fdZdeeef         fd Zdeeeef         ee         f         fd!Zdeee         eedf         f         fd"Zdee         fd#Zd	d$dd%d&ed'ede	d(edz  dej        f
d)Zd*Zd+edefd,Zdefd-Zd=d/ededef fd0Zedefd1            Zd2edee         fd3Zd4ed5ed6edeeeef                  fd7Z d	d8d9efd:Z!d>de
defd<Z" xZ#S )?DockerEnvironmentu  Hardened Docker container execution with resource limits and persistence.

    Security: all capabilities dropped, no privilege escalation, PID limits,
    size-limited tmpfs for scratch dirs. The container itself is the security
    boundary — the filesystem inside is writable so agents can install packages
    (pip, npm, apt) as needed. Writable workspace via tmpfs or bind mounts.

    Persistence: when enabled, bind mounts preserve /workspace and /root
    across container restarts.
    Tr   .c                 *    t          | j                  S )z@Keep explicit docker_forward_env values out of shared snapshots.)tuple_forward_envselfs    r   ,_additional_profile_scoped_passthrough_namesz>DockerEnvironment._additional_profile_scoped_passthrough_namesQ  s    T&'''r   /rootr9  r   Fr8   Nr   cwdrN   cpumemorydiskpersistent_filesystemtask_idvolumesr
   r   networkhost_cwdauto_mount_cwdr  r   persist_across_processesshm_sizec           
          QRSTU |dk    rd}t                                          ||           | _        | _        | _        t          |
           _        t          |           _        d _	        d  _
        i  _        d _        d _        d _        g  _        t                               d|	           |	2t%          |	t&                    st                               d|	           g }	t+                       g }|d	k    r3t-          |          r$|                    d
t1          |          g           |d	k    r)t-          |          r|                    d| dg           t-          |          r|                    dt2          g           t1          |pd                                          }|r,|dk    r&t7          |          s|                    d|g           |d	k    rZt8          j        dk    rJ                                 r|                    dd| dg           nt                               d           |s|                    d           d	dl m!} g }d}|	pg D ]}t%          |t0                    st                               d|           3|                                }|sJd|v r|                    d|g           d|v rd}lt                               d|           |r<tD          j#        $                    tD          j#        %                    |                    nd}|o0tM          |          o!tD          j#        '                    |          o| }|r<|r:tD          j#        '                    |          st           (                    d|           d  _)        d  _*        g } j        r |            dz  |z  }t1          |dz             _*        tE          j+         j*        d           |                    d j*         d g           |sS|sQt1          |d!z             _)        tE          j+         j)        d           |                    d j)         dg           n2|s|s|                    d"d#g           |                    g d$           |r%t                               d%|           d| dg|}n|rt           (                    d&           	 d	d'l,m-}m.}m/}  |            D ]} ta          | d(                   }!|!1                                rt                               d)|!           G|!2                                st                               d*|!           w|                    d| d(          d| d+          d,g           t                               d-| d(         | d+                     |            D ]}"ta          |"d(                   }!|!1                                st                               d.|!           G|                    d|"d(          d|"d+          d,g           t                               d/|"d(         |"d+                     |            D ]}#ta          |#d(                   }!|!1                                st                               d0|!           G|                    d|#d(          d|#d+          d,g           t                               d1|#d(         |#d+                    n2# tf          $ r%}$t           (                    d2|$           Y d }$~$nd }$~$ww xY wti                      \  }%U}&tk          |%U|&          }'tm                      }(to          U          RUr^tq          Rfd3 j        D                       })|)r<d4|) d5|(rd6nd7 d8}*|(rts          |* d9          t                               d:|*           |                    |%           UrO	 d	d;l:m;}+  |+            <                    d<          pi },n]# tz          t|          f$ r i },Y nHtf          $ r<}-	 d	d l?}.n# tz          $ r  w xY wt                               d=|-           i },Y d }-~-nd }-~-ww xY wtM          |,<                    d>d                    }(h d?}/t                      S	 d	d@lAmB}0 dA |0C                                D             Sn# tf          $ r Y nw xY w|/Sz  }1tq          SU fdB|1D                       }2|2r<dC|2 d5|(rd6nd7 d8}*|(rts          |* dD          t                               dE|*           	 d	d;l:m;}3 tM           |3            <                    d<          pi <                    d>d                    }4n&# tz          t|          f$ r d}4Y ntf          $ r d}4Y nw xY w|4r,Ur*t           j                  }5|5E                    U           n)t          U          }5|5E                     j                   |5F                    dFd           }6|6r|5<                    dGd          }7|7G                                }8dHdIhQ|6                                TTQv r<QTfdJ|8D             }9|9rt                               dK|9T           QTfdL|8D             }8T|8vr|8                    T           dMH                    |8                                          |5dG<   |5dG         s|5F                    dGd            g }:tq          |5          D ]$};|:                    dN|; dO|5|;          g           %g }<|rJt                      }=|= dP|=g}<t                               dQ|=           nt                               dR           t                      pd _K        t           jK        |          }>|>rt                               dS|           t          |otM          |<          |>T          }?t                               dU|           g }@|pg D ]H}At%          |At0                    st                               dV|A           3|@                    |A           IUrNt          |@R          }B|Br<dW|B d5|(rd6nd7 d8}*|(rts          |* dX          t                               dY|*           |?|<z   |z   |z   |&z   |z   |:z   |@z   }Ct                               dZ|C           d[t          jP                    jQ        d d\          }Dt          t                                }Et          |          }Fd]d^d]d_|F d]d`|E d]t           dO|' g}G| _        |D _        |> _        |C _        dadbdc|Fdd|Et          |'i _        d}H|r U                    |F|E|'          }I|I|I\  }J}Kd}Ld }M|s V                    |J          }M|Mdek    }L|Lrt                               df|Jd dg         |Mpdh|F|E           	 t          jX         jK        didj|Jgdddkdldmdt          jY        n           nG# t          jZ        t|          f$ r.}$t                               do|Jd dg         |$           Y d }$~$nd }$~$ww xY wd }I|I|I\  }J}K|J _
        |Kdpk    r	 t          jX         jK        dq|Jgdddkdldmdt          jY        n           nT# t          j[        t          jZ        f$ r6}$t                               dr|Jd dg         |K|$           d  _
        Y d }$~$nd }$~$ww xY w j
        r(t                               ds|Jd dg         |F|E|K           d}H|Hs-|>rg ndtg}N jK        dudvg|Ndw|D|Gdx||C|dydz}Ot           (                    d{dMH                    |O                     	 t          jX        |Odddkdld|dt          jY        n          }Pnk# t          j[        t          jZ        f$ rM}$t                               d}|D|$           t          jX         jK        didj|Dgdd~t          jY                    d }$~$ww xY w|Pj\                                         _
        t                               d|D j
        d dg                     ]                                 _^         _                                 d S )N~rJ  )rK  rN   rR   r  FzDockerEnvironment volumes: %sz'docker_volumes config is not a list: %rr   r2  r3  r   r5  r8  r   darwin--storage-optzsize=zDocker storage driver does not support per-container disk limits (requires overlay2 on XFS with pquota). Container will run without disk quota.z--network=none)get_sandbox_dirz'Docker volume entry is not a string: %rr   r   z:/workspaceTz*Docker volume '%s' missing colon, skippingz@Skipping docker cwd mount: host_cwd is not a valid directory: %sr?   home)exist_okz:/root	workspacer   z/workspace:rw,exec,size=10g)r   z/home:rw,exec,size=1gr   z/root:rw,exec,size=1gz.Mounting configured host cwd to /workspace: %szDSkipping docker cwd mount: /workspace already mounted by user config)get_credential_file_mountsget_skills_directory_mountget_cache_directory_mounts	host_pathug   Docker: skipping credential mount — source is a directory (likely Docker-in-Docker auto-creation): %su:   Docker: skipping credential mount — source not found: %scontainer_pathr   z$Docker: mounting credential %s -> %su?   Docker: skipping skills mount — source is not a directory: %sz$Docker: mounting skills dir %s -> %su>   Docker: skipping cache mount — source is not a directory: %sz#Docker: mounting cache dir %s -> %sz1Docker: could not load credential file mounts: %sc              3   $   K   | ]
}|v |V  d S NrR   )rU   r   _critical_egress_namess     r   r   z-DockerEnvironment.__init__.<locals>.<genexpr>;  s7       ) )C;Q4Q4Q4Q4Q4Q4Q) )r   z@docker_forward_env would inject real egress-protected variables z; enforce_on_docker is r   disabled.zi  Remove these names from docker_forward_env or disable enforce_on_docker to opt out of egress isolation.zC%s  Explicit docker_forward_env values will override egress tokens.r   r   z:Could not read proxy config for egress collision check: %sr   >
   r   r   r   r   r   r   r   r   r   r   r   c                     h | ]	}|j         
S rR   )r   )rU   r   s     r   	<setcomp>z-DockerEnvironment.__init__.<locals>.<setcomp>  s'     + + +()AO+ + +r   c              3      K   | ]I}|j         v r>|vsj         |         |         k    r#|v s|v rj         |         |         k    E|V  Jd S re  )_env)rU   k_critical_provider_keysegress_env_overridesrH  s     r   r   z-DockerEnvironment.__init__.<locals>.<genexpr>  s       ! !	>>111y|';A'>>> 000111 IaL,@,CCC  DCCC! !r   z;docker_env in config.yaml overrides egress-proxy variables z`  Remove these keys from docker_env or disable enforce_on_docker to opt out of egress isolation.zX%s  Falling back to docker_env values; sandbox traffic will NOT route through the proxy.r   r   r   z--use-bundled-cac                 (    g | ]}|v |k    |S rR   rR   rU   t_CA_MODE_FLAGSappend_tokens     r   rW   z.DockerEnvironment.__init__.<locals>.<listcomp>  s/    cccn9L9LQRVbQbQb1QbQbQbr   zOverriding conflicting NODE_OPTIONS CA-mode flag(s) %s with egress-required %s to keep Node routed through the egress CA store.c                 (    g | ]}|vs|k    |S rR   rR   rq  s     r   rW   z.DockerEnvironment.__init__.<locals>.<listcomp>  s/    "n"n"n.AXAX\]am\m\m1\m\m\mr    r   r  z--userz)Docker: running container as host user %szdocker_run_as_host_user is enabled but this platform does not expose POSIX uid/gid; container will start as its image default user.ug   Docker: image %s uses /init (s6-overlay) as entrypoint — skipping --init and mounting /run with exec.)r  zDocker volume_args: %sz/Ignoring non-string docker_extra_args entry: %rz7docker_extra_args would override egress-proxy controls zP  Remove these args or disable enforce_on_docker to opt out of egress isolation.z2%s  Extra Docker args may bypass egress isolation.zDocker run_args: %shermes-   --labelzhermes-agent=1zhermes-task-id=zhermes-profile=zhermes-agentr   hermes-task-idhermes-profilenoneu   Existing container %s has NetworkMode=%s but docker_network=false requests an air-gapped container — removing it and starting fresh (task=%s, profile=%s).r]   r.   rX   rY   rF   rG   rZ   rI   z,Failed to remove mismatched container %s: %srunningstartu[   Failed to start existing container %s (state=%s): %s — falling back to a fresh container.z:Reusing container %s (task=%s, profile=%s, prior state=%s)--initrb   -d--name-wr7  infinityzStarting container: %sx   z<docker run failed for %s, cleaning up orphaned container: %sr   rJ   rN   rP   zStarted container %s (%s))`super__init___persistent_persist_across_processes_task_idr   rF  r&   rl  _init_unset_passthrough_names_container_id_labels_image_container_name_image_uses_s6_init_all_run_argsr   rq   r   r  r   rA  r:  r`   r   _DEFAULT_PIDS_LIMITr   r   sysplatform_storage_opt_supportedr   tools.environments.baser[  r   r   abspath
expanduserr$   isdirrf   _workspace_dir	_home_dirmakedirstools.credential_filesr_  r`  ra  r   is_diris_filer+   r   r   r   r   r  r   r*   r   r   r   re   yamlr   r   r   r   r    r   popr  joinr.  r_   _docker_exer(  r  r  uuiduuid4hexr4   r:   _EGRESS_LABEL_KEY_find_reusable_container_container_network_modera   rb   rc   rd   CalledProcessErrorri   _build_init_env_args_init_env_argsinit_session)WrH  r   rK  rN   rL  rM  rN  rO  rP  rQ  r
   r   rR  rS  rT  r  r   rU  rV  resource_argsshmr[  r   workspace_explicitly_mountedvolhost_cwd_absbind_host_cwdwritable_argssandboxr_  r`  ra  mount_entrysrcskills_mountcache_mountrt   egress_volume_argsegress_host_argsegress_label_enforce_egress_forward_collisions_msg_load_cfg_for_collision
_proxy_cfg_er  _critical_proxy_control_ip_for_mappings	_critical_collisions_load_cfg_for_precedence_enforce_egress_merge
merged_env_egress_node_appendexisting_nodeexisting_tokensdroppedenv_argsr   	user_args	user_specimage_uses_s6_initsecurity_argsvalidated_extrar	  _extra_collisionsall_run_argscontainer_nameprofile_name
task_label
label_argsreusedexistingr|   statemode_mismatchactual_mode	init_argsrun_cmdrz   rs  rf  rn  rt  ro  	__class__sW   `                                                                                @@@@@r   r  zDockerEnvironment.__init__U  s(   * #::CS'2220)A&8EE',,	>@*,0')$&). (*3W===z'4'@'@NNDgNNNG 	!""" 77/667  (CHH!5666A::2599:  *lll!;<<<#E** 	H  .2E!FGGG (.b!!'')) 	63#::&>z&J&J:  ,!4555!8800**,, $$ot%GHHHHe    	3  !1222
 	<;;;;; ',$Mr 	R 	RCc3'' H#NNN))++C czz""D#;/// C''370KSQQQQHPXrwrw'9'9('C'CDDDVX 1\""1l++1 10	 	  	gh 	grw}}\/J/J 	gLL[]efff-1(, 	%o''(2W<G 6!122DNK6666  ///"    ! )E &)'K*?&@&@#D/$????$$T0===&    ! )E $$<&       " " "   
  	aKKH,WWWL!=!=!=LLKK) 	aLL_```L	Q           :9;;  ;{344::<< 	 NNF  
 {{}} NNTVY   """;/TT+>N2OTTT$    :, 01    !; : < <  <455zz|| NNY   ""#K0VV<@P3QVVV$    : - !12     :9;;  ;{344zz|| NNX   """;/TT+>N2OTTT$    9, 01   "  	Q 	Q 	QLLLaPPPPPPPP	Q *++ 	C02B 1 46F
 
 455!;<P!Q!Q 	"( ) ) ) )#0) ) ) # # # G!4G G$3CyyG G G 
 # & W W W   Y   	-...$   J	 TTTTTT5577;;GDDJ

)      


      KKKK"   P    





  #:>>2Et#L#LMMO' ' '# 14#NNNNNN+ +-=-K-K-M-M+ + +''    /2III  ! ! ! ! ! !$! ! !  K"  G!,G G$3CyyG G G 
 # & % % %  
 89=  
	)QQQQQQ$())++//88>B($//% %!! W% 	) 	) 	)$(!!! 	) 	) 	)$(!!!	) ! 	)%9 	)diJ23333233Jdi((( )nn0$
 
  	5&NN>2>>M+1133O 12DEN.4466L~--cccccoccc NN+,3\  
 #o"n"n"n"no"n"n"n ?22&&|444),/)B)B)H)H)J)JJ~&n- 5~t444*%% 	@ 	@COOTc#=#=JsO#=#=>????  "	 
	/11I$%y1	GSSSS*   '==4H 99I5QQ 	KK?  
 -0i'
 
 

 	,k::: $" 	( 	(Cc3'' PRUVVV""3'''' 	 =!7! ! ! G(G G$3CyyG G G 
 # & : : :   H$  
   	
    	 	)<888 :4:<<#3BQB#799 --E-G-GHH*733
'555777+<<l<<	

 -#5 ) Cjl|	
$ # D	"44L, H #&.#e !&" :"&">">|"L"LK$/6$9M  $NN1 %SbS);+C)"L  
m"!-tT<H+/!%	$&"'","4     '5w? m m m'UWcdgegdgWhjkllllllllm#H#&.#e%1"I%%2"!-wE+/!%	$&"&","4     '9:;TU 2 2 2H("-ua  
 .2******2 % "KKT$SbS):|U   "F ,	^ 1@xjI %		 	 )	 		
 	
 	 	 	 	 $	G LL1388G3D3DEEE##'	$,   1:3LM    R"A   %tT>B#'$,   
 #$ "(!4!4!6!6DKK3^TEWX[Y[X[E\]]] #7799 	s   &H#\
 

\9\44\95%` a50	a5:`?>a0?a a00a5.$c 
c c Af f. f.-f. /x y&$yy..z {.8,{)){.7&~ A@9AA@@A@c                 j   |                                  \  }}t          | j                  }|                    |           |D ]}|                    |d           t          t          |                    | _        g }t          |          D ]$}|                    d| d||          g           %|S )zBuild -e KEY=VALUE args for injecting host env vars into init_session.

        These are used during init_session() so that export -p captures the
        configured environment and the current profile's forwarded values.
        Nr   r  )	_resolve_passthrough_envr    rl  r   r  rE  r  r  r`   )rH  passthrough_envunset_namesexec_envr  r  r   s          r   r  z&DockerEnvironment._build_init_env_args  s     (,'D'D'F'F$#'	??((( 	% 	%DLLt$$$$-26+3F3F-G-G*(## 	: 	:CKK#777789999r   c                 6    |                                  d         S )zBResolve forwarded host variables through the active profile scope.r   )r  rG  s    r   _build_passthrough_envz(DockerEnvironment._build_passthrough_env  s    ,,..q11r   c                 v   i }t          | j                  }t                      }d}d}d }	 ddlm}m} ddlm}m}	 |} |	            }t           |                      }n# t          $ r Y nw xY wd |D             }
||
t          z
  z  }|rt                      ni }t                      }t          |          D ]}}t          j        |          p|                    |          }| |||          }||||<   A|r: ||          s/t                              |          r|                    |           ~||fS )z<Return forwarded values and scoped names that must be unset.NFc                     dS )NFrR   )_names    r   <lambda>z<DockerEnvironment._resolve_passthrough_env.<locals>.<lambda>  s    e r   r   )get_all_passthroughresolve_passthrough_value)_is_global_envis_multiplex_activec                 0    h | ]}t          |          |S rR   )r   )rU   rm  s     r   rj  z=DockerEnvironment._resolve_passthrough_env.<locals>.<setcomp>  s6     
 
 
/I!/L/L

 
 
r   )r   rF  tools.env_passthroughr  r  agent.secret_scoper  r  r+   r   r,   r  r   r   r   r   	fullmatchr   )rH  r  explicit_forward_keyspassthrough_keysr  multiplex_activeis_global_envr  r  _is_multiplex_active_implicit_forwardforward_keys
hermes_envr  r   r%   s                   r   r  z*DockerEnvironment._resolve_passthrough_env  s   #% #D$5 6 6%(UU$(! ++
	        gfffffff*M3355"#6#6#8#899 	 	 	D	
 
'
 
 
 -0ADb0bc0<D*,,,"
 #,'' 	% 	%CIcNN9jnnS&9&9E(411#u==  %! %--*<*< %AQA[A[\_A`A` %$$$$$s   3A! !
A.-A.c                     |                                  \  }}g }t          |          D ]$}|                    d| d||          g           %|t          t          |                    fS )zFBuild runtime forwarding args plus names absent from the active scope.r   r  )r  r  r`   rE  )rH  r  r  r  r   s        r   #_build_runtime_env_args_with_unsetsz5DockerEnvironment._build_runtime_env_args_with_unsets  s~    '+'D'D'F'F$/** 	A 	ACKK#>>(<>>?@@@@U6+..////r   c                 6    |                                  d         S )z<Build only dynamic forwarded values for a non-login command.r   )r  rG  s    r   _build_runtime_env_argsz)DockerEnvironment._build_runtime_env_args'  s    7799!<<r   r  )loginrN   
stdin_data
cmd_stringr  r  c                >   | j         s
J d            | j        dg}||                    d           d}|r|                    | j                   n3| j        r,|                                 \  }}|                    |           |rt          | dd          }|r'd                    d |D                       }d	| d
| }|                    | j         g           |r|                    ddd|g           n|                    dd|g           t          ||          S )z1Spawn a bash process inside the Docker container.zContainer not startedexecNz-irR   r  rv  c              3   >   K   | ]}t          j        |          V  d S re  )shlexquote)rU   r  s     r   r   z.DockerEnvironment._run_bash.<locals>.<genexpr>A  s,      #N#N$EK$5$5#N#N#N#N#N#Nr   zunset z 2>/dev/null || true
bashz-lz-c)
r  r  r   r`   r  _profile_scoped_passthroughr  r   r  r   )	rH  r  r  rN   r  cmdr  runtime_argsquoted_namess	            r   	_run_bashzDockerEnvironment._run_bash+  s[    !::#:::!(!JJt
 (* 	%JJt*++++- 	%(,(P(P(R(R%L+JJ|$$$ 	M!$(GLLK 	S88#N#N+#N#N#NNNLR,RRjRRJ

D&'((( 	3JJdJ78888JJj12223
+++r   )zNo such containerzis not runningzno such containeroutputc                 D    t          fd| j        D                       S )zCReturn True if the output indicates the container no longer exists.c              3       K   | ]}|v V  	d S re  rR   )rU   pr  s     r   r   z7DockerEnvironment._is_container_gone.<locals>.<genexpr>Y  s'      DD11;DDDDDDr   )r   _NO_CONTAINER_PATTERNS)rH  r  s    `r   _is_container_gonez$DockerEnvironment._is_container_goneW  s)    DDDD(CDDDDDDr   c           
         | j         pddd         }t                              d|           d| _         | j                            dd          }| j                            dd          }|                     ||| j                            t          d                    }||\  }}|dk    r+|| _         t                              d	|dd                    n	 t          j	        | j
        d
|gddddddt          j                   || _         t                              d|dd                    nL# t          j        t          j        f$ r.}t                              d|dd         |           Y d}~nd}~ww xY w| j         s| j        st                              d           dS 	 ddl}d|                                j        dd          }	| j        rg ndg}
g }| j                                        D ]!\  }}|                    d| d| g           "| j
        ddg|
d|	|d| j        | j        | j        dd}t          j	        |ddddd dt          j                  }|j                                        | _         |	| _        t                              d!|	| j         dd                    nJ# t          j        t          j        t6          f$ r&}t                              d"|           Y d}~dS d}~ww xY w	 d| _        |                                  n3# t<          $ r&}t                              d#|           Y d}~dS d}~ww xY wt                              d$| j         pddd                    dS )%a9  Recreate the container after it was removed out-of-band.

        Tries label-based reuse first; if no existing container is found,
        starts a fresh one with the same image and run-args.  Returns True
        on success, False if recreation fails (caller should surface the
        original error).
        r  Nr]   u7   Container %s appears to be gone — attempting recoveryrz  r{  r   r}  z&Recovery: reusing running container %sr~  TrF   rG   rZ   rI   z Recovery: restarted container %sz*Recovery: failed to start container %s: %sz8Recovery: no saved image name, cannot recreate containerFr   rw  rx  r  ry  r  rb   r  r  r  r7  r  r  z)Recovery: created fresh container %s (%s)z,Recovery: failed to create new container: %sz2Recovery: init_session failed in new container: %su(   Recovery successful — new container %s)r  r   r   r  r   r  r  rq   ra   rb   r  rc   r  rd   r  r?  r  r  r  r  r!   r`   rK  r  ri   r   r  re   _snapshot_readyr  r+   )rH  old_idr  profile_labelr  rw   r  rt   _uuidnew_namer  r  rm  vr  rz   s                   r   _recreate_containerz%DockerEnvironment._recreate_container[  sO    $*CRC0Ev	
 	
 	
 " \%%&6;;
(()92>>00t|'7'78I5'Q'Q
 
 !JC	!!%("Dc#2#hOOOO	^N)7C8'+$QZdfnr(0   
 *-D&KK BCHMMMM"5z7PQ ^ ^ ^NN#OQTUXVXUXQY[\]]]]]]]]^ ! !	; WXXXu$$$$<U[[]]%6rr%:<<"&":JBB
	
 L..00 ? ?DAq%%yQ****&=>>>>$eT		 	 '	  		
 	
 (	 '	 K	 	 (	 $DtgV_iltx$,   &,]%8%8%:%:"'/$?d0"5    1:3LgV   KQOOOuuuuu
	#(D  	 	 	LLMqQQQ55555	 	>ASAYWY[^\^[^@_```tsJ   AD1 1E:$E55E:)DJ< <!LK>>LL# #
M-MMr  commandc                 0    t                      j        ||fi |}|                    dd          dk    r_|                     |                    dd                    r6| j        r/|                                 r t                      j        ||fi |}|S )a
  Execute a command, auto-recovering from dead containers.

        If the container was removed out-of-band (idle reaper, docker prune,
        OOM kill, daemon restart), detect the error and recreate the container
        transparently before retrying once.
        rg   r   r  r  )r  executer   r  r  r  )rH  r  rK  kwargsrz   r  s        r   r  zDockerEnvironment.execute  s     !#8888JJ|Q''1,,''

8R(@(@AA -. - '')) A(#@@@@r   c            	      d   t           t           S 	 t                      pd} t          j        | dddgddddd	t          j        
          }|j                                                                        }|dk    rda dS t          j        | ddddgdddddt          j        
          }|j        dk    rC|j                                        }|r%t          j        | d|gddt          j                   da nda n# t          $ r da Y nw xY wt                              dt                      t           S )zCheck if Docker's storage driver supports --storage-opt size=.
        
        Only overlay2 on XFS with pquota supports per-container disk quotas.
        Ubuntu (and most distros) default to ext4, where this flag errors out.
        Nr?   rq   rE   z{{.Driver}}TrF   rG   r   r[   overlay2FcreaterZ  zsize=1mzhello-worldrH   r   rX   r=  r  z Docker --storage-opt support: %s)r/  r_   ra   rb   rc   ri   r   lowerrg   r+   r   rf   )r?   rz   driverprober|   s        r   r  z(DockerEnvironment._storage_opt_supported  sk    &""	$ ]].hF^];#$\^ (  F
 ]((**0022F##"'u N?I}M#$\^ (  E
 1$$$|1133 =NFD,#?26)3);= = = = #'"' 	$ 	$ 	$#OOO	$7IIIs   A,C9 >A:C9 9DDr|   c           
         	 t          j        | j        ddd|gddddddt           j        	          }n?# t           j        t
          f$ r&}t                              d
|           Y d}~dS d}~ww xY w|j        dk    r:t                              d|j        |j	        
                                           dS |j        
                                }|pdS )a  Return the container's ``HostConfig.NetworkMode`` (e.g. ``bridge``,
        ``none``, ``host``), or ``None`` when inspection fails.

        Used by the reuse path to make sure a persisted container's network
        mode still matches the operator's ``docker_network`` setting; callers
        treat ``None`` (unknown) as a mismatch when lockdown was requested,
        so a failed inspect fails closed rather than open.
        r~   rE   z{{.HostConfig.NetworkMode}}TrF   rG   r   FrI   z%docker inspect NetworkMode failed: %sNr   z*docker inspect NetworkMode returned %d: %s)ra   rb   r  rc   rd   re   r   rf   rg   rh   r   ri   )rH  r|   rz   rt   modes        r   r  z)DockerEnvironment._container_network_mode  s    	^$i = 
  $GI (  FF )73 	 	 	LL@!DDD44444	 !!LL<!6=#6#6#8#8   4}""$$|ts   03 A/	A**A/r  r  r  c           
      *   	 dddd| dd| g}|dk    r%|                     ddt           d| g           d}nd	t          z   d
z   }t          j        | j        ddg|d|ddddddt          j                  }n?# t          j        t          f$ r&}t          	                    d|           Y d}~dS d}~ww xY w|j
        dk    r:t          	                    d|j
        |j                                                   dS d |j                                        D             }|sdS d}	d}
|D ]}|dk    r||                    dd          }t!          |          dk     r2|d         |d                                         |d         }}}|dvr#t          	                    d|t          |           nL|                    dd          }t!          |          dk    r|d         |d                                         }}|
||f}
|dk    r|	||f}	|	p|
S ) u  Look for an existing container labeled for this (task, profile).

        Returns ``(container_id, state)`` on hit, ``None`` on miss / on any
        failure (including ``docker ps`` itself failing). State is one of the
        values Docker reports via ``{{.State}}`` — e.g. ``running``, ``exited``,
        ``created``, ``paused``, ``restarting``, ``dead``. The caller decides
        whether the state warrants ``docker start`` before reuse.

        Restricted to the docker-stored label set this class creates; never
        matches containers that happened to be named ``hermes-*`` but were
        started by some other tool.
        r@   rA   zlabel=hermes-task-id=rB   r   zlabel=r  z{{.ID}}	{{.State}}z{{.ID}}	{{.State}}	{{.Label "z"}}rC   rD   rE   TrF   rG   r   FrI   u;   docker ps probe failed: %s — will start a fresh containerNr   u@   docker ps probe returned %d: %s — will start a fresh containerc                 :    g | ]}|                                 |S rR   rS   rT   s     r   rW   z>DockerEnvironment._find_reusable_container.<locals>.<listcomp>F  s%    GGGBHHJJGGGGr   	r     r\   )r  z
<no value>r   z7skipping container %s for egress=off reuse: label %s=%rr}  )r`   r  ra   rb   r  rc   rd   re   r   rf   rg   rh   r   ri   rj   r  r  r$  )rH  r  r  r  rr   fmtrz   rt   linesr}  r'  rV   partsrw   r  
egress_vals                   r   r  z*DockerEnvironment._find_reusable_container  s   $ 	2@J@@CMCCG
 u$$
,W5F,W,W,W,WXYYY+ 8:KKeS^$dD  !$
  $GI (  FF )73 	 	 	LLVXYZZZ44444	 !!LLR!6=#6#6#8#8   4GGfm6688GGG 	4  	' 	'Bu$$ q))u::>>).q58>>3C3CU1XJU%>>>LL&'*,=z    ? q))u::??"1XuQx~~'7'7U}e	!!go,%s   A:A= =B9B44B9)force_remover1  c                  	
 | j         s2| j        s)| j        | j        fD ]}|rt	          j        |d           dS |rd
d	n| j        r	d| _         dS d
d	| j        dd         d
	
fd}ddl}|	                    |dd 	          }|
                                 || _        d| _         	r0| j        s+| j        | j        fD ]}|rt	          j        |d           dS dS dS )u}	  Tear down the container according to persist mode and *force_remove*.

        Persist-mode (``persist_across_processes=True``, the default) leaves the
        container **running** untouched. The docs promise "ONE long-lived
        container shared across sessions" and stopping it on every Hermes exit
        breaks that promise:

        * Background processes inside the container (``npm run dev``, watchers,
          long-running pytest) get killed every time the user runs ``/quit``.
        * Every reuse requires ``docker start`` + waiting for the container to
          come back up, adding 1–2s to the first tool call of the new session.
        * The user-visible difference between "ONE long-lived container" and
          "a new container that happens to share state" is exactly this:
          processes survive in the former, die in the latter.

        Resource reclamation for the persist-mode case lives in the
        ``reap_orphan_containers()`` path (see issue #20561 commit 3): if no
        Hermes process touches a labeled container for ``2 × lifetime_seconds``
        it gets ``docker rm -f``'d at the next Hermes startup. That covers the
        SIGKILL / OOM / abandoned-laptop cases without us needing to stop the
        container on every graceful exit.

        Opt-out mode (``persist_across_processes=False``) still does
        ``docker stop`` + ``docker rm -f`` on every cleanup, matching the
        pre-PR behavior for users who explicitly want per-process isolation.

        ``force_remove=True`` overrides persist mode and always tears the
        container down (``docker stop`` + ``docker rm -f``). This is the
        explicit-teardown path for ``/reset``, ``cleanup_vm(task_id)``-driven
        resets, or any caller that wants a guaranteed fresh container on next
        ``DockerEnvironment(task_id=...)``. No current caller passes
        ``force_remove=True``; the parameter is here so the explicit-teardown
        semantics can be wired up later without changing this method's
        signature.

        Cleanup runs on a daemon thread with bounded ``subprocess.run`` calls
        (not the racy ``Popen(... &)`` pattern from before PR #33645). The
        atexit hook in ``tools/terminal_tool.py`` waits up to 15s for the
        thread to finish before the interpreter exits, so ``docker stop`` /
        ``docker rm`` actually completes when we do trigger it.
        T)ignore_errorsNr]   r   c                     rh	 t          j        dddgddt           j                   n?# t           j        t          f$ r&} t
                              d|            Y d } ~ nd } ~ ww xY wri	 t          j        dd	gddt           j                   d S # t           j        t          f$ r'} t
                              d
|            Y d } ~ d S d } ~ ww xY wd S )Nstopz-t10TrZ   r  z%docker stop %s timed out / failed: %srX   rY   r^   )ra   rb   rc   rd   re   r   r   )rt   r|   r=   log_idshould_removeshould_stops    r   _do_cleanupz.DockerEnvironment.cleanup.<locals>._do_cleanup  sH    WWN#VT4F'+R(0    
 #17; W W WNN#JFTUVVVVVVVVW LLN#T4>'+R(0     
 #17; L L LNN#?KKKKKKKKKLL Ls-   '- A)A$$A)/&B C-CCr   zhermes-cleanup-)targetdaemonr  r   N)r  r  r  r  r   rmtreer  r  	threadingThreadr~  _cleanup_thread)rH  r1  dr:  r?  rr  r|   r=   r7  r8  r9  s         @@@@@r   cleanupzDockerEnvironment.cleanupi  s   T ) 	 # =-t~> = =A =at<<<<F  	!K MM+ 	! "&DFK M %
crc"	L 	L 	L 	L 	L 	L 	L 	L 	L 	L4 	KC]U[C]C]^^				 !
  	9!1 	9)4>: 9 9 9M!48888	9 	9 	9 	99 9r         >@c                     t          | dd          }||                                sdS |                    |           |                                 S )u  Block up to *timeout* seconds for the cleanup worker thread.

        Returns ``True`` if the thread finished (or no thread was started),
        ``False`` on timeout. The atexit hook in terminal_tool.py calls this
        on every active environment so docker stop/rm actually completes
        before the Python process exits — without this, ``hermes /quit``
        races the interpreter shutdown and leaves stopped containers behind.
        rA  NT)rN   )r   is_aliver  )rH  rN   threads      r   wait_for_cleanupz"DockerEnvironment.wait_for_cleanup  sU     0$77>!2!2>4G$$$??$$$$r   )r  )rD  )$__name__
__module____qualname____doc__r
  rE  r   rI  _DEFAULT_SHM_SIZEr"   r#   r$   r  r    r  r  r  r   r  r  r   ra   Popenr  r  r  r  r  staticmethodr  r   r  r  rC  rH  __classcell__)r  s   @r   rC  rC  C  s#       	 	 #'(eCHo ( ( ( ( &+ (,$!&)-)'J
 J
J
 J
 	J

 J
 J
 J
  $J
 J
 J
 #Y%J
 D[J
 J
 J
 J
  !J
" #J
$ #'%J
& 'J
 J
 J
 J
 J
 J
Xd3i    $2S#X 2 2 2 2&%%S#XC0H*I &% &% &% &%P0U49eCQTHo;U5V 0 0 0 0=c = = = = ;@!$+/ ,  ,  ,C  ,4  , ,!Dj ,4>4D ,  ,  ,  ,LE E E E E ERT R R R Rh s        " (D ( ( ( \(T C  HSM        DZ Z  Z  	Z 
 
%S/	"Z  Z  Z  Z x /4 u9 u9 u9t u9 u9 u9 u9n% % % % % % % % % % %r   rC  )T)Fr=  )BrL  r   r   loggingr   r   r  r   ra   r  r  pathlibr   typingr   r  r   r   tools.environments.localr   r   	getLoggerrI  r   r   r	   r   __annotations__compiler   r  r  r   r    r&   r,   r1   r4   r:   r"   r{   ro   r_   r  r  rM  r$   r   r  r  r  rE  r   r   r   r   r   r  r  r(  r.  r/  r0  r:  rA  rC  rR   r   r   <module>rX     sK        				 				       



              @ @ @ @ @ @ @ @       
 
	8	$	$    %) HSM ( ( (2:9:: # d3i$.> 49    2TD[ T#s(^    <tCH~      RZ 233      #    " !%!	[ [ [[ $J[ d
	[
 	[ [ [ [| s  #        F*Xc] * * * *D       	 	$ 	 	 	 	 @ ;   ^3uT#YS#XS	-Q'R ^3 ^3 ^3 ^3BDcDS>D CyD 		D D D D(	 	t 	t 	 	 	 	d38n S    #S	#+.s8#	#Y# # # #B+ +4 +4 +DQTI + + + +*HC *H *H *H *H *H *HZ#    $ #'$ & & &$( 8D> ( ( (1C 1D 1 1 1 1h@ @ @ @Fj% j% j% j% j% j% j% j% j% j%r   