
    i,                         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
mZ ddlmZmZmZmZmZ  ej        e          Zd
dZ G d d	e
          ZdS )zKSSH remote execution environment with ControlMaster connection persistence.    N)Path)BaseEnvironment_popen_bash)FileSyncManageriter_sync_filesquoted_mkdir_commandquoted_rm_commandunique_parent_dirsreturnc                  L    t          j        d          st          d          dS )z@Fail fast with a clear error when the SSH client is unavailable.sshzWSSH is not installed or not in PATH. Install OpenSSH client: apt install openssh-clientN)shutilwhichRuntimeError     >/home/agentuser/.hermes/hermes-agent/tools/environments/ssh.py_ensure_ssh_availabler      s3    < 
e
 
 	

 
r   c                   $    e Zd ZdZ	 	 d&dededed	ed
edef fdZd'dedz  defdZd Z	defdZ
d(dZdededdfdZdeeeef                  ddfdZdeddfdZdee         ddfdZd(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 ))SSHEnvironmenta  Run commands on a remote machine over SSH.

    Spawn-per-call: every execute() spawns a fresh ``ssh ... bash -c`` process.
    Session snapshot preserves env vars across calls.
    CWD persists via in-band stdout markers.
    Uses SSH ControlMaster for connection reuse.
    ~<       hostusercwdtimeoutportkey_pathc                     t                                          ||           | _        | _        | _        | _        t          t          j                              dz   _	         j	        
                    dd            j	        | d| d| dz   _        t                                                                                           _                                          t#           fd j         j         j         j        	           _         j                            d
                                             d S )N)r   r   z
hermes-sshT)parentsexist_ok@:z.sockc                  2    t           j         d          S )N/.hermes)r   _remote_homeselfs   r   <lambda>z)SSHEnvironment.__init__.<locals>.<lambda>9   s    D4E1O1O1O!P!P r   )get_files_fn	upload_fn	delete_fnbulk_upload_fnbulk_download_fn)force)super__init__r   r   r   r    r   tempfile
gettempdircontrol_dirmkdircontrol_socketr   _establish_connection_detect_remote_homer(   _ensure_remote_dirsr   _scp_upload_ssh_delete_ssh_bulk_upload_ssh_bulk_download_sync_managersyncinit_session)r*   r   r   r   r   r   r    	__class__s   `      r   r3   zSSHEnvironment.__init__(   sS   S'222			  3 5 566Etd;;;".D1M1M41M1M$1M1M1MM""$$$ 4466  """,PPPP&&0!4
 
 
 	d+++r   N
extra_argsr   c                 T   dg}|                     dd| j         g           |                     ddg           |                     ddg           |                     ddg           |                     ddg           |                     ddg           | j        d	k    r)|                     d
t          | j                  g           | j        r|                     d| j        g           |r|                     |           |                    | j         d| j                    |S )Nr   -oControlPath=zControlMaster=autozControlPersist=300zBatchMode=yesz StrictHostKeyChecking=accept-newzConnectTimeout=10r   z-p-ir$   )extendr8   r   strr    appendr   r   )r*   rD   cmds      r   _build_ssh_commandz!SSHEnvironment._build_ssh_commandC   s3   g

D>)<>>?@@@

D./000

D./000

D/*+++

D<=>>>

D-.///9??JJc$)nn-...= 	.JJdm,--- 	#JJz"""

di--$)--...
r   c                    |                                  }|                    d           	 t          j        |ddd          }|j        dk    rD|j                                        p|j                                        }t          d|           d S # t          j	        $ r! t          d| j
         d| j         d	          w xY w)
Nz!echo 'SSH connection established'T   capture_outputtextr   r   zSSH connection failed: zSSH connection to r$   z
 timed out)rM   rK   
subprocessrun
returncodestderrstripstdoutr   TimeoutExpiredr   r   )r*   rL   result	error_msgs       r   r9   z$SSHEnvironment._establish_connectionT   s    %%''

6777	W^C4QSTTTF A%%"M//11JV]5H5H5J5J	"#HY#H#HIII &% ( 	W 	W 	WUDIUU	UUUVVV	Ws   A'B 0Cc                 \   	 |                                  }|                    d           t          j        |ddd          }|j                                        }|r(|j        dk    rt                              d|           |S n# t          $ r Y nw xY w| j
        dk    rdS d	| j
         S )
z(Detect the remote user's home directory.z
echo $HOMET
   rP   r   zSSH: remote home = %srootz/rootz/home/)rM   rK   rS   rT   rX   rW   rU   loggerdebug	Exceptionr   )r*   rL   rZ   homes       r   r:   z"SSHEnvironment._detect_remote_home_   s    		))++CJJ|$$$^C4QSTTTF=&&((D )Q..4d;;; 	 	 	D	97#	###s   BB 
BBc                     | j          d}|| d| d| dg}|                                 }|                    t          |                     t	          j        |ddd           dS )	z?Create base ~/.hermes directory tree on remote in one SSH call.r'   z/skillsz/credentialsz/cacheTr]   rP   N)r(   rM   rK   r   rS   rT   )r*   basedirsrL   s       r   r;   z"SSHEnvironment._ensure_remote_dirss   s    #---&&&4(=(=(=$O%%''

'--...s4dBGGGGGGr   	host_pathremote_pathc                    t          t          |          j                  }|                                 }|                    dt          j        |                      t          j        |ddd           ddd| j	         g}| j
        dk    r)|                    d	t          | j
                  g           | j        r|                    d
| j        g           |                    || j         d| j         d| g           t          j        |ddd          }|j        dk    r)t!          d|j                                                   dS )z0Upload a single file via scp over ControlMaster.z	mkdir -p Tr]   rP   scprF   rG   r   z-PrH   r$   r%      r   zscp failed: N)rJ   r   parentrM   rK   shlexquoterS   rT   r8   r   rI   r    r   r   rU   r   rV   rW   )r*   rf   rg   rk   	mkdir_cmdscp_cmdrZ   s          r   r<   zSSHEnvironment._scp_upload}   sU   T+&&-..++--	:U[%8%8::;;;yD"MMMM$ Dt/B D DE9??NND#di..1222= 	2NND$-0111	di#K#K$)#K#Kk#K#KLMMM4QSTTT!!Efm.A.A.C.CEEFFF "!r   filesc           	         |sdS t          |          }|r|                                 }|                    t          |                     t	          j        |ddd          }|j        dk    r)t          d|j        	                                           t          j        d          5 }|D ]\  }}t          j                            ||                    d	                    }t          j        t          j                            |          d
           t          j        t          j                            |          |           dddd|dg}	|                                 }
|
                    d           t	          j        |	t          j        t          j                  }	 t	          j        |
|j        t          j        t          j                  }n7# t.          $ r* |                                 |                                  w xY w|j                                         	 |                    d          \  }}d}|                                |                    d          \  }}n"|j        r|j                                        nd}nr# t          j        $ r` |                                 |                                 |                                 |                                 t          d          w xY w|j        dk    r@t          d|j         d|                    d          	                                           |j        dk    r@t          d|j         d|                    d          	                                           	 ddd           n# 1 swxY w Y   t@          !                    dtE          |                     dS )a  Upload many files in a single tar-over-SSH stream.

        Pipes ``tar c`` on the local side through an SSH connection to
        ``tar x`` on the remote, transferring all files in one TCP stream
        instead of spawning a subprocess per file.  Directory creation is
        batched into a single ``mkdir -p`` call beforehand.

        Typical improvement: ~580 files goes from O(N) scp round-trips
        to a single streaming transfer.
        NTrj   rP   r   zremote mkdir failed: zhermes-ssh-bulk-)prefix/)r#   tarz-chf-z-C.ztar xf - -C /)rX   rV   )stdinrX   rV   x   )r   r   r]   zSSH bulk upload timed outztar create failed (rc=z): replaceerrorsz tar extract over SSH failed (rc=z*SSH: bulk-uploaded %d file(s) via tar pipe)#r
   rM   rK   r   rS   rT   rU   r   rV   rW   r4   TemporaryDirectoryospathjoinlstripmakedirsdirnamesymlinkabspathPopenPIPErX   ra   killwaitclosecommunicatepollreadrY   decoder_   r`   len)r*   rp   r"   rL   rZ   stagingrf   rg   stagedtar_cmdssh_cmdtar_procssh_proc_
ssh_stderrtar_stderr_raws                   r   r>   zSSHEnvironment._ssh_bulk_upload   s&     	F$U++ 	T))++CJJ+G44555^C4QSTTTF A%%"#R6=;N;N;P;P#R#RSSS (0BCCC 3	w*/ ? ?&	;g{/A/A#/F/FGGBGOOF33dCCCC
27??955v>>>>fc4#>G--//GNN?+++!'

  H%+8?:?%?       O!!###@ ( 4 4S 4 A A: "%==??*(0(<(<R(<(H(H%A~~?G%WX_%9%9%;%;%;TWN, @ @ @"#>???@ "a''"IX-@ I I%,,I,>>DDFFI I   "a''"Ex7J E E!((	(::@@BBE E   (_3	 3	 3	 3	 3	 3	 3	 3	 3	 3	 3	 3	 3	 3	 3	j 	A3u::NNNNNsF   -C=N/+1GN/4HN/.A+JN/A/L		BN//N36N3destc                    | j          d                    d          }|                                 }|                    dt	          j        |                      t          |d          5 }t          j        ||t          j	        d          }ddd           n# 1 swxY w Y   |j
        dk    r=t          d	|j                            d
                                                     dS )z*Download remote .hermes/ as a tar archive.r'   rs   ztar cf - -C / wbrx   )rX   rV   r   Nr   zSSH bulk download failed: ry   rz   )r(   r   rM   rK   rl   rm   openrS   rT   r   rU   r   rV   r   rW   )r*   r   rel_baser   frZ   s         r   r?   z!SSHEnvironment._ssh_bulk_download   s3    '11188==))++?H(=(=??@@@$ 	\^GAjoWZ[[[F	\ 	\ 	\ 	\ 	\ 	\ 	\ 	\ 	\ 	\ 	\ 	\ 	\ 	\ 	\!!lFM<P<PXa<P<b<b<h<h<j<jllmmm "!s   ,#BB"Bremote_pathsc                 
   |                                  }|                    t          |                     t          j        |ddd          }|j        dk    r)t          d|j                                                   dS )z*Batch-delete remote files in one SSH call.Tr]   rP   r   zremote rm failed: N)	rM   rK   r	   rS   rT   rU   r   rV   rW   )r*   r   rL   rZ   s       r   r=   zSSHEnvironment._ssh_delete   s    %%''

$\22333DtRPPP!!KFM4G4G4I4IKKLLL "!r   c                 8    | j                                          dS )zCSync files to remote via FileSyncManager (rate-limited internally).N)r@   rA   r)   s    r   _before_executezSSHEnvironment._before_execute   s    !!!!!r   Frx   )loginr   
stdin_data
cmd_stringr   r   c                    |                                  }|r,|                    dddt          j        |          g           n*|                    ddt          j        |          g           t	          ||          S )z7Spawn an SSH process that runs bash on the remote host.bashz-lz-c)rM   rI   rl   rm   r   )r*   r   r   r   r   rL   s         r   	_run_bashzSSHEnvironment._run_bash   sy     %%'' 	@JJdEK
,C,CDEEEEJJek*&=&=>???3
+++r   c                    | j         r3t                              d           | j                                          | j                                        r	 ddd| j         dd| j         d| j         g}t          j	        |dd	
           n# t          t          j        f$ r Y nw xY w	 | j                                         d S # t          $ r Y d S w xY wd S )Nz"SSH: syncing files from sandbox...r   rF   rG   z-Oexitr$   T   )rQ   r   )r@   r_   info	sync_backr8   existsr   r   rS   rT   OSErrorSubprocessErrorunlink)r*   rL   s     r   cleanupzSSHEnvironment.cleanup  s    	+KK<===((***%%'' 
	d$H43F$H$HV	%?%?DI%?%?As4CCCCCZ78   #**,,,,,   
	 
	s$   6B B%$B%)C 
CC)r   r   r   r   )Nr   N)__name__
__module____qualname____doc__rJ   intr3   listrM   r9   r:   r;   r<   tupler>   r   r?   r=   r   boolrS   r   r   r   __classcell__)rC   s   @r   r   r      s+         9<DF S  # *->A     6 TD[ D    "	W 	W 	W$S $ $ $ $(H H H HGS Gs Gt G G G G"LOd5c?&; LO LO LO LO LO\
nt 
n 
n 
n 
n 
nMS	 Md M M M M" " " " ;@!$+/
, 
, 
,C 
,4 
,
,!Dj
,4>4D
, 
, 
, 
,      r   r   r   )r   loggingr}   rl   r   rS   r4   pathlibr   tools.environments.baser   r   tools.environments.file_syncr   r   r   r	   r
   	getLoggerr   r_   r   r   r   r   r   <module>r      s   Q Q  				              @ @ @ @ @ @ @ @              
	8	$	$
 
 
 
t t t t t_ t t t t tr   