
    L0&j9                         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                      t          j        d          st          d          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-clientscpzWSCP 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      s\    < 
e
 
 	
 < 
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>I   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   r5   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,   rK   cmds      r   _build_ssh_commandz!SSHEnvironment._build_ssh_commandS   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t          j                  }|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   stdinr   zSSH connection failed: zSSH connection to r%   z
 timed out)rT   rR   
subprocessrunDEVNULL
returncodestderrstripstdoutr   TimeoutExpiredr   r   )r,   rS   result	error_msgs       r   r?   z$SSHEnvironment._establish_connectiond   s    %%''

6777	W^# (  F  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   A2B 0Cc                 r   	 |                                  }|                    d           t          j        |dddt          j                  }|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
   rW   r   zSSH: remote home = %srootz/rootz/home/)rT   rR   r[   r\   r]   ra   r`   r^   loggerdebug	Exceptionr   )r,   rS   rc   homes       r   r@   z"SSHEnvironment._detect_remote_homeu   s    	))++CJJ|$$$^# (  F =&&((D )Q..4d;;; 	 	 	D	97#	###s   BB 
BBc                     | j          d}|| d| d| dg}|                                 }|                    t          |                     t	          j        |dddt          j                   dS )	z?Create base ~/.hermes directory tree on remote in one SSH call.r)   z/skillsz/credentialsz/cacheTrf   rW   N)r*   rT   rR   r   r[   r\   r]   )r,   basedirsrS   s       r   rA   z"SSHEnvironment._ensure_remote_dirs   s    #---&&&4(=(=(=$O%%''

'--...$	
 	
 	
 	
 	
 	
r   	host_pathremote_pathc                    t          t          |          j                  }|                                 }|                    dt          j        |                      t          j        |dddt          j	                   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t          j	                  }|j        dk    r)t#          d|j                                                   dS )z0Upload a single file via scp over ControlMaster.z	mkdir -p Trf   rW   r   rM   rN   r   z-PrO   r%   r&      r   zscp failed: N)rQ   r   parentrT   rR   shlexquoter[   r\   r]   r>   r    rP   r!   r   r   r^   r   r_   r`   )r,   ro   rp   rs   	mkdir_cmdscp_cmdrc   s          r   rB   zSSHEnvironment._scp_upload   sz   T+&&-..++--	:U[%8%8::;;;$	
 	
 	
 	
 $ 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$
 
 
 !!Efm.A.A.C.CEEFFF "!r   filesc           	      	   |sdS | j          d}t          |          }|r|                                 }|                    t	          |                     t          j        |dddt
          j                  }|j        dk    r)t          d|j
                                                   t          j        d	          5 }|D ]J\  }}	 t          j                            ||          }	n(# t"          $ r}
t          d
|d|          |
d}
~
ww xY w|	dk    s|	                    d          rt          d
|d|          t          j                            ||	          }t          j        t          j                            |          d           	 t          j        t          j                            |          |           # t0          $ r7}t3          |dd          dk    rt5          j        ||           n Y d}~Dd}~ww xY wdddd|dg}|                                 }|                    dt9          j        |                      t          j        |t
          j        t
          j        t
          j                  }	 t          j        ||j         t
          j        t
          j                  }n7# tB          $ 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   tT          +                    d"tY          |                     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.
        Nr)   Trr   rW   r   zremote mkdir failed: zhermes-ssh-bulk-)prefixzremote path z is not under sync base .z../z escapes sync base )r$   winerrori"  tarz-chf-z-Cztar xf - --no-overwrite-dir -C )rZ   ra   r_   x   )r   r   rf   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*   r
   rT   rR   r   r[   r\   r]   r^   r   r_   r`   r6   TemporaryDirectoryospathrelpath
ValueError
startswithjoinmakedirsdirnamesymlinkabspathOSErrorgetattrr   copy2rt   ru   PopenPIPEra   rj   killwaitclosecommunicatepollreadrb   decoderh   ri   len)r,   rx   rm   r#   rS   rc   stagingro   rp   
rel_remoteexcstagedetar_cmdssh_cmdtar_procssh_proc_
ssh_stderrtar_stderr_raws                       r   rD   zSSHEnvironment._ssh_bulk_upload   sw     	F#---$U++ 	T))++CJJ+G44555^# (  F  A%%"#R6=;N;N;P;P#R#RSSS (0BCCC M	w*/  &	;!#d!C!CJJ!   &V{VVdVV 
 $$
(=(=e(D(D$&Q{QQQQ   gz::BGOOF33dCCCCJrwy996BBBB   q*d33t;;Y7777 87777 fc4#>G--//G
 NNPU[=N=NPPQQQ!' (!!	  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   (SM	 M	 M	 M	 M	 M	 M	 M	 M	 M	 M	 M	 M	 M	 M	^ 	A3u::NNNNNs   	R C-,R-
D7DDBR2GR
H,H	R	HA?R1K ?R 4K44RA+M=<R=A/O,,BRRRdestc                    | j          d                    d          }|                                 }|                    dt	          j        |                      t          |d          5 }t          j        |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)   /ztar cf - -C / wbr   )rZ   ra   r_   r   Nr   zSSH bulk download failed: r   r   )r*   lstriprT   rR   rt   ru   openr[   r\   r]   r   r^   r   r_   r   r`   )r,   r   rel_baser   frc   s         r   rE   z!SSHEnvironment._ssh_bulk_download/  s4    '11188==))++?H(=(=??@@@$ 	^ (!  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t          j                  }|j        dk    r)t          d|j        	                                           dS )z*Batch-delete remote files in one SSH call.Trf   rW   r   zremote rm failed: N)
rT   rR   r	   r[   r\   r]   r^   r   r_   r`   )r,   r   rS   rc   s       r   rC   zSSHEnvironment._ssh_deleteA  s    %%''

$\22333$
 
 
 !!KFM4G4G4I4IKKLLL "!r   c                 8    | j                                          dS )zCSync files to remote via FileSyncManager (rate-limited internally).N)rF   rG   r+   s    r   _before_executezSSHEnvironment._before_executeO  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)rT   rP   rt   ru   r   )r,   r   r   r   r   rS   s         r   	_run_bashzSSHEnvironment._run_bashW  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	t          j
        
           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   rM   rN   z-Oexitr%   T   )rX   r   rZ   )rF   rh   info	sync_backr>   existsr   r   r[   r\   r]   r   SubprocessErrorunlink)r,   rS   s     r   cleanupzSSHEnvironment.cleanupc  s(    	+KK<===((***%%'' 	
d$H43F$H$HV	%?%?DI%?%?A#'$,	     Z78   #**,,,,,   	 	s%   AB B0/B04C 
CC)r   r   r   r   )Nr   N)__name__
__module____qualname____doc__rQ   intr5   listrT   r?   r@   rA   rB   tuplerD   r   rE   rC   r   boolr[   r   r   r   __classcell__)rJ   s   @r   r   r   $   s(         9<DF$ $S $ $# $$*-$>A$ $ $ $ $ $L TD[ D    "W W W"$S $ $ $ $4
 
 
 
 GS Gs Gt G G G G:qOd5c?&; qO qO qO qO qOfnt n n n n n$MS	 Md M M M M" " " " ;@!$+/
, 
, 
,C 
,4 
,
,!Dj
,4>4D
, 
, 
, 
,      r   r   r   )r   r:   loggingr   rt   r   r[   r6   pathlibr   tools.environments.baser   r   tools.environments.file_syncr   r   r   r	   r
   	getLoggerr   rh   r   r   r   r   r   <module>r      s#   Q Q   				              @ @ @ @ @ @ @ @              
	8	$	$	
 	
 	
 	
S S S S S_ S S S S Sr   