
    iQ0                         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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/ubuntu/.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           t          j        | d| d|                                                                           d d         } j	        | 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>E   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hashlibsha256encode	hexdigest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    
_socket_id	__class__s	   `       r   r4   zSSHEnvironment.__init__)   s   S'222			  3 5 566Etd;;; ^##d##T##**,,
 

)++crc
 #.J1E1E1EE""$$$ 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$   )extendr=   r   strr    appendr   r   )r+   rJ   cmds      r   _build_ssh_commandz!SSHEnvironment._build_ssh_commandO   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)rS   rQ   
subprocessrun
returncodestderrstripstdoutr   TimeoutExpiredr   r   )r+   rR   result	error_msgs       r   r>   z$SSHEnvironment._establish_connection`   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
   rV   r   zSSH: remote home = %srootz/rootz/home/)rS   rQ   rY   rZ   r^   r]   r[   loggerdebug	Exceptionr   )r+   rR   r`   homes       r   r?   z"SSHEnvironment._detect_remote_homek   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/cacheTrc   rV   N)r)   rS   rQ   r   rY   rZ   )r+   basedirsrR   s       r   r@   z"SSHEnvironment._ensure_remote_dirs   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 Trc   rV   scprL   rM   r   z-PrN   r$   r%      r   zscp failed: N)rP   r   parentrS   rQ   shlexquoterY   rZ   r=   r   rO   r    r   r   r[   r   r\   r]   )r+   rl   rm   rq   	mkdir_cmdscp_cmdr`   s          r   rA   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.
        NTrp   rV   r   zremote mkdir failed: zhermes-ssh-bulk-)prefix/)r#   tarz-chf-z-C.z tar xf - --no-overwrite-dir -C /)r^   r\   )stdinr^   r\   x   )r   r   rc   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
   rS   rQ   r   rY   rZ   r[   r   r\   r]   r5   TemporaryDirectoryospathjoinlstripmakedirsdirnamesymlinkabspathPopenPIPEr^   rg   killwaitclosecommunicatepollreadr_   decodere   rf   len)r+   rv   r"   rR   r`   stagingrl   rm   stagedtar_cmdssh_cmdtar_procssh_proc_
ssh_stderrtar_stderr_raws                   r   rC   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 7	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   (g7	 7	 7	 7	 7	 7	 7	 7	 7	 7	 7	 7	 7	 7	 7	r 	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(   ry   ztar cf - -C / wbr~   )r^   r\   r   Nr   zSSH bulk download failed: r   r   )r)   r   rS   rQ   rr   rs   openrY   rZ   r   r[   r   r\   r   r]   )r+   r   rel_baser   fr`   s         r   rD   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.Trc   rV   r   zremote rm failed: N)	rS   rQ   r	   rY   rZ   r[   r   r\   r]   )r+   r   rR   r`   s       r   rB   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)rE   rF   r*   s    r   _before_executezSSHEnvironment._before_execute   s    !!!!!r   Fr~   )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)rS   rO   rr   rs   r   )r+   r   r   r   r   rR   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   rL   rM   z-Oexitr$   T   )rW   r   )rE   re   info	sync_backr=   existsr   r   rY   rZ   OSErrorSubprocessErrorunlink)r+   rR   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__rP   intr4   listrS   r>   r?   r@   rA   tuplerC   r   rD   rB   r   boolrY   r   r   r   __classcell__)rI   s   @r   r   r       s,         9<DF$ $S $ $# $$*-$>A$ $ $ $ $ $L TD[ D    "	W 	W 	W$S $ $ $ $(H H H HGS Gs Gt G G G G"POd5c?&; PO PO PO PO POd
nt 
n 
n 
n 
n 
nMS	 Md M M M M" " " " ;@!$+/
, 
, 
,C 
,4 
,
,!Dj
,4>4D
, 
, 
, 
,      r   r   r   )r   r9   loggingr   rr   r   rY   r5   pathlibr   tools.environments.baser   r   tools.environments.file_syncr   r   r   r	   r
   	getLoggerr   re   r   r   r   r   r   <module>r      s#   Q Q   				              @ @ @ @ @ @ @ @              
	8	$	$
 
 
 
C C C C C_ C C C C Cr   