Ë
    ãß²jk$  ã                   ób  — 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mZ ddl	m
Z
 ddlmZ ddlmZmZmZ ddlmZ ddlmZ dd	lmZmZ  ej0                  e«      Zd
efd„Zd
efd„Zd
efd„Zd
efd„Z ded
dfd„Z!d
efd„Z"d
efd„Z# ejH                  «       Z%ddeded
efd„Z& G d„ de«      Z'y)záSingularity/Apptainer persistent container environment.

Security-hardened with --containall, --no-home, capability dropping. Supports
resource limits and optional persistence via writable overlay dirs that survive sessions.
é    N)ÚPath)ÚOptional©Úget_hermes_home)ÚBaseEnvironmentÚ_load_json_storeÚ_save_json_store)Ú_popen_bash)Úsanitize_task_id_for_path)Ú	bash_argvÚrun_captureÚreturnc                  ó   — t        «       dz  S )Nzsingularity_snapshots.jsonr   © ó    úC/home/ubuntu/.hermes/hermes-agent/tools/environments/singularity.pyÚ_snapshot_storer      s   € ô ÓÐ;Ñ;Ð;r   c                  óX   — dD ]  } t        j                  | «      sŒ| c S  t        d«      ‚)z/Locate the apptainer or singularity CLI binary.)Ú	apptainerÚsingularityz´Neither 'apptainer' nor 'singularity' was found in PATH. Install Apptainer (https://apptainer.org/docs/admin/main/installation.html) or Singularity and ensure the CLI is available.)ÚshutilÚwhichÚRuntimeError)Úexes    r   Ú_find_singularity_executabler      s6   € à+ò ˆÜ�<‰<˜ÕØŠJðô ð	:ó;ð ;r   c                  óH  — t        «       } 	 t        | dgd¬«      }|j                  dk7  r;|j                  j                  «       d	d
 }t        d| › d|j                  › d|› �«      ‚| S # t        $ r t        d| › d�«      ‚t        j
                  $ r t        d| › d�«      ‚w xY w)z?Preflight check: resolve the executable and verify it responds.Úversioné
   ©Útimeoutz"Singularity backend selected but 'z' could not be executed.ú'z version' timed out.r   NéÈ   z version' failed (exit code z): )	r   r   ÚFileNotFoundErrorr   Ú
subprocessÚTimeoutExpiredÚ
returncodeÚstderrÚstrip)r   Úresultr'   s      r   Ú_ensure_singularity_availabler*   )   sÂ   € ä
&Ó
(€Cð:Ü˜c 9Ð-°rÔ:ˆð
 ×Ñ˜AÒØ—‘×$Ñ$Ó& t¨Ð,ˆÜ˜Q˜s˜eÐ#?À×@QÑ@QÐ?RÐRUÐV\ÐU]Ð^Ó_Ð_Ø€Jøô ò _ÜÐ?À¸uÐD\Ð]Ó^Ð^Ü×$Ñ$ò :Ü˜Q˜s˜eÐ#7Ð8Ó9Ð9ð:ús   ŒA' Á':B!c                  ó(   — t        t        «       «      S ©N)r   r   r   r   r   Ú_load_snapshotsr-   8   s   € ÜœOÓ-Ó.Ð.r   Údatac                 ó,   — t        t        «       | «       y r,   )r	   r   )r.   s    r   Ú_save_snapshotsr0   <   s   € Ü”_Ó&¨Õ-r   c                  óš  — t        j                  d«      } | rt        | «      }n”ddlm}  |«       dz  }t        d«      }|j                  «       rit        j                  |t         j                  «      rE|t        j                  dd«      z  dz  }|j                  d	d	¬
«       t        j                  d|«       |j                  d	d	¬
«       |S )z\``TERMINAL_SCRATCH_DIR`` override, else a writable ``/scratch`` (HPC), else the sandbox dir.ÚTERMINAL_SCRATCH_DIRr   )Úget_sandbox_dirr   z/scratchÚUSERÚhermeszhermes-agentT©ÚparentsÚexist_okz Using /scratch for sandboxes: %s)ÚosÚgetenvr   Útools.environments.baser3   ÚexistsÚaccessÚW_OKÚmkdirÚloggerÚinfo)Úcustom_scratchÚscratch_pathr3   Úscratchs       r   Ú_get_scratch_dirrE   @   s¨   € ä—Y‘YÐ5Ó6€NÙÜ˜NÓ+‰å;Ù&Ó(¨=Ñ8ˆÜ�zÓ"ˆØ�>‰>Ô¤§	¡	¨'´2·7±7Ô ;Ø"¤R§Y¡Y¨v°xÓ%@Ñ@À>ÑQˆLØ×Ñ t°dÐÔ;Ü�K‰KÐ:¸LÔIØ×Ñ˜t¨dÐÔ3ØÐr   c                  óŠ   — t        j                  d«      } | rt        | «      nt        «       dz  }|j	                  dd¬«       |S )NÚAPPTAINER_CACHEDIRz
.apptainerTr6   )r9   r:   r   rE   r?   )Ú	cache_dirÚ
cache_paths     r   Ú_get_apptainer_cache_dirrJ   Q   s@   € Ü—	‘	Ð.Ó/€IÙ$-”�i”Ô3CÓ3EÈÑ3T€JØ×Ñ˜T¨DÐÔ1ØÐr   ÚimageÚ
executablec                 óö  — | j                  d«      rt        | «      j                  «       s| j                  d«      s| S | j	                  dd«      j	                  dd«      j	                  dd«      }t        «       }||› d�z  }|j                  «       rt        |«      S t        5  |j                  «       rt        |«      cddd«       S t        j                  d«       t        j                  d	| «       t        j                  d
|«       |dz  }|j                  dd¬«       ddlm}  |dd¬«      }t        |«      |d<   t        |«      |d<   	 t        |dt        |«      | gd|¬«      }|j                  dk7  rCt        j                  d«       t        j                  d|j                   dd «       | cddd«       S t        j                  d«       t        |«      cddd«       S # t"        j$                  $ rC t        j                  d«       |j                  «       r|j'                  «        | cY cddd«       S t(        $ r+}	t        j                  d|	«       | cY d}	~	cddd«       S d}	~	ww xY w# 1 sw Y   yxY w)zVBuild (once, cached) a SIF from a ``docker://`` URL; falls back to the URL on failure.z.sifz	docker://Ú ú/Ú-ú:Nz&Building SIF image (one-time setup)...z  Source: %sz  Target: %sÚtmpTr6   r   )Úbuild_subprocess_envF)Úscrub_secretsÚinherit_profile_homeÚAPPTAINER_TMPDIRrG   ÚbuildiX  )r    Úenvz/SIF build failed, falling back to docker:// URLz  Error: %siô  zSIF image built successfullyz2SIF build timed out, falling back to docker:// URLz2SIF build error: %s, falling back to docker:// URL)Úendswithr   r<   Ú
startswithÚreplacerJ   ÚstrÚ_sif_build_lockr@   rA   r?   Útools.environments.localrS   r   r&   Úwarningr'   r$   r%   ÚunlinkÚ	Exception)
rK   rL   Ú
image_namerH   Úsif_pathÚtmp_dirrS   rX   r)   Úes
             r   Ú_get_or_build_sifrf   [   s  € à�‰�vÔ¤4¨£;×#5Ñ#5Ô#7À×@PÑ@PÐQ\Ô@]Øˆà—‘˜{¨BÓ/×7Ñ7¸¸SÓA×IÑIÈ#ÈsÓS€JÜ(Ó*€IØ˜j˜\¨Ð.Ñ.€HØ‡�ÔÜ�8‹}Ðä	ñ  Ø�?‰?ÔÜ�x“=÷ ñ  ô 	�‰Ð<Ô=Ü�‰�N EÔ*Ü�‰�N HÔ-à˜eÑ#ˆØ�‰˜d¨TˆÔ2õ 	BÙ"°ÈUÔSˆÜ"% g£,ˆÐÑÜ$'¨	£NˆÐ Ñ!ð	Ü  *¨g´s¸8³}ÀeÐ!LÐVYÐ_bÔcˆFØ× Ñ  AÒ%Ü—‘ÐPÔQÜ—‘˜}¨f¯m©m¸D¸SÐ.AÔBØ÷- ñ  ô. �K‰KÐ6Ô7Ü�x“=÷1 ñ  øô2 ×(Ñ(ò 	Ü�N‰NÐOÔPØ�‰Ô Ø—‘Ô!Ø‰L÷; ñ  ô< ò 	Ü�N‰NÐOÐQRÔSØŒL÷A ñ  ûð<	ú÷= ð  úsQ   Â#I/ÃBI/ÅA#G$Æ;G$Ç$A
I,È.I/È9I,ÉI'ÉI,ÉI/É'I,É,I/É/I8c                   óœ   ‡ — e Zd ZdZdZ	 	 	 ddededededed	ed
edefˆ fd„Z	d„ Z
ddddœdededededz  dej                  f
d„Zd„ Zˆ xZS )ÚSingularityEnvironmenta  Hardened Singularity/Apptainer container with resource limits and persistence.

    Spawn-per-call: every execute() spawns a fresh ``apptainer exec ... bash -c`` process.
    Session snapshot preserves env vars across calls; CWD persists via in-band stdout markers.
    TFrK   Úcwdr    ÚcpuÚmemoryÚdiskÚpersistent_filesystemÚtask_idc	                 óö  •— t         ‰	| �  ||¬«       t        «       | _        t	        || j                  «      | _        dt        j                  «       j                  d d › �| _	        d| _
        || _        || _        d | _        || _        || _        | j                  r>t!        «       dz  dt#        |«      › �z  | _        | j                  j%                  dd¬«       | j'                  «        | j)                  «        y )	N)ri   r    Úhermes_é   Fzhermes-overlayszoverlay-Tr6   )ÚsuperÚ__init__r*   rL   rf   rK   ÚuuidÚuuid4ÚhexÚinstance_idÚ_instance_startedÚ_persistentÚ_task_idÚ_overlay_dirÚ_cpuÚ_memoryrE   r   r?   Ú_start_instanceÚinit_session)
ÚselfrK   ri   r    rj   rk   rl   rm   rn   Ú	__class__s
            €r   rs   zSingularityEnvironment.__init__’   sç   ø€ ô 	‰Ñ˜S¨'ÐÔ2Ü7Ó9ˆŒÜ& u¨d¯o©oÓ>ˆŒ
Ø$¤T§Z¡Z£\×%5Ñ%5°c°rÐ%:Ð$;Ð<ˆÔØ!&ˆÔØ0ˆÔØˆŒØ,0ˆÔØˆŒ	ØˆŒà×Òô !Ó"Ð%6Ñ6¸8ÔD]Ð^eÓDfÐCgÐ9hÑhð Ôà×Ñ×#Ñ#¨D¸4Ð#Ô@à×ÑÔØ×ÑÕr   c                 óØ  — | j                   ddddg}| j                  r3| j                  r'|j                  dt	        | j                  «      g«       n|j                  d«       	 ddlm}m} g  |«       ¢ |«       ¢­D ]!  }|j                  d	|d
   › d|d   › d�g«       Œ# 	 | j                  dkD  r |j                  d| j                  › d�g«       | j                  dkD  r&|j                  dt	        | j                  «      g«       |j                  t	        | j                  «      | j                  g«       	 t!        |d¬«      }|j(                  dk7  rt'        d|j*                  › �«      ‚d| _        t        j/                  d| j                  | j                  «       y # t        $ r!}t        j                  d|«       Y d }~�Œ!d }~ww xY w# t"        j$                  $ r t'        d«      ‚w xY w)NÚinstanceÚstartz--containallz	--no-homez	--overlayz--writable-tmpfsr   )Úget_credential_file_mountsÚget_skills_directory_mountz--bindÚ	host_pathrQ   Úcontainer_pathz:roz8Singularity: could not load credential/skills mounts: %sz--memoryÚMz--cpuséx   r   zInstance start timed outzFailed to start instance: Tz/Singularity instance %s started (persistent=%s))rL   ry   r{   Úextendr\   ÚappendÚtools.credential_filesr…   r†   ra   r@   Údebugr}   r|   rK   rw   r   r$   r%   r   r&   r'   rx   rA   )r€   Úcmdr…   r†   Úentryre   r)   s          r   r~   z&SingularityEnvironment._start_instanceª   s¼  € Ø�‰ 
¨G°^À[ÐQˆØ×Ò × 1Ò 1Ø�J‰J˜¤S¨×):Ñ):Ó%;Ð<Õ=à�J‰JÐ)Ô*ð	XßeØWÑ5Ó7ÐWÑ:TÓ:VÑWò ]�Ø—
‘
˜H¨¨{Ñ);Ð(<¸A¸eÐDTÑ>UÐ=VÐVYÐ&ZÐ[Õ\ñ]ð
 �<‰<˜!ÒØ�J‰J˜
 t§|¡| n°AÐ$6Ð7Ô8Ø�9‰9�qŠ=Ø�J‰J˜¤# d§i¡i£.Ð1Ô2Ø�
‰
”C˜Ÿ
™
“O T×%5Ñ%5Ð6Ô7ð	;Ü  ¨cÔ2ˆFð ×Ñ Ò!ÜÐ!;¸F¿M¹M¸?ÐKÓLÐLØ!%ˆÔÜ�‰ÐEÀt×GWÑGWÐY]×YiÑYiÕjøô! ò 	XÜ�L‰LÐSÐUV×WÒWûð	Xûô ×(Ñ(ò 	;ÜÐ9Ó:Ð:ð	;ús$   Á#=F Ä6G
 Æ	GÆ&GÇGÇ
G)rŠ   N)Úloginr    Ú
stdin_dataÚ
cmd_stringr‘   r’   r   c                ó˜   — | j                   st        d«      ‚| j                  dd| j                  › �gt	        ||«      ¢}t        ||«      S )z5Spawn a bash process inside the Singularity instance.z Singularity instance not startedÚexeczinstance://)rx   r   rL   rw   r   r
   )r€   r“   r‘   r    r’   r�   s         r   Ú	_run_bashz SingularityEnvironment._run_bashÇ   sR   € ð ×%Ò%ÜÐAÓBÐBØ�‰ ¨+°d×6FÑ6FÐ5GÐ(HÐhÌ9ÐU_ÐafÓKgÐhˆÜ˜3 
Ó+Ð+r   c                 óÆ  — | j                   rM	 t        | j                  dd| j                  gd¬«       t        j                  d| j                  «       d| _         | j                  rE| j                  r8t        «       }t        | j                  «      || j                  <   t        |«       yyy# t        $ r+}t        j                  d| j                  |«       Y d}~Œˆd}~ww xY w)	z;Stop the instance. If persistent, the overlay dir survives.rƒ   Ústopé   r   zSingularity instance %s stoppedz*Failed to stop Singularity instance %s: %sNF)rx   r   rL   rw   r@   rA   ra   r_   ry   r{   r-   r\   rz   r0   )r€   re   Ú	snapshotss      r   ÚcleanupzSingularityEnvironment.cleanupÏ   s¿   € à×!Ò!ðbÜ˜TŸ_™_¨j¸&À$×BRÑBRÐSÐ]_Õ`Ü—‘Ð=¸t×?OÑ?OÔPð &+ˆDÔ"à×Ò × 1Ò 1Ü'Ó)ˆIÜ'*¨4×+<Ñ+<Ó'=ˆI�d—m‘mÑ$Ü˜IÕ&ð !2Ðøô	 ò bÜ—‘ÐKÈT×M]ÑM]Ð_`×aÑaûðbús   ŽAB, Â,	C Â5!CÃC )Ú~é<   r   r   r   FÚdefault)Ú__name__Ú
__module__Ú__qualname__Ú__doc__Ú_sudo_nopasswd_probe_supportedr\   ÚintÚfloatÚboolrs   r~   r$   ÚPopenr–   r›   Ú__classcell__)r�   s   @r   rh   rh   ‰   s¥   ø„ ñð &*Ð"àSTØOTØ )ñ˜cð ¨ð ¸Cð È5ð ØðØ(+ðØHLðàõò0kð: ;@ÐPSØ+/ò, Cð ,°4ð ,È#ð ,Ø! D™jð,Ø4>×4DÑ4Dó,ö'r   rh   )r   )(r¢   Úloggingr9   r   r$   Ú	threadingrt   Úpathlibr   Útypingr   Úhermes_constantsr   r;   r   r   r	   Útools.environments.base_outputr
   Útools.environments.path_utilsr   Ú tools.environments.remote_commonr   r   Ú	getLoggerrŸ   r@   r   r\   r   r*   Údictr-   r0   rE   rJ   ÚLockr]   rf   rh   r   r   r   ú<module>r´      sÜ   ðñó Û 	Û Û Û Û Ý Ý å ,ß WÑ WÝ 6Ý Cß Cà	ˆ×	Ñ	˜8Ó	$€ð<˜ó <ð; có ;ð só ð/˜ó /ð.˜$ð . 4ó .ð˜$ó ð" $ó ð !�)—.‘.Ó"€ñ+˜Sð +¨cð +ÀCó +ô\S'˜_õ S'r   