
    i0                         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/piyush/.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          }|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)rT   rR   
subprocessrun
returncodestderrstripstdoutr   TimeoutExpiredr   r   )r,   rS   result	error_msgs       r   r?   z$SSHEnvironment._establish_connectiond   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
   rW   r   zSSH: remote home = %srootz/rootz/home/)rT   rR   rZ   r[   r_   r^   r\   loggerdebug	Exceptionr   )r,   rS   ra   homes       r   r@   z"SSHEnvironment._detect_remote_homeo   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/cacheTrd   rW   N)r*   rT   rR   r   rZ   r[   )r,   basedirsrS   s       r   rA   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 Trd   rW   r   rM   rN   r   z-PrO   r%   r&      r   zscp failed: N)rQ   r   parentrT   rR   shlexquoterZ   r[   r>   r    rP   r!   r   r   r\   r   r]   r^   )r,   rm   rn   rq   	mkdir_cmdscp_cmdra   s          r   rB   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   rW   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   rd   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
   rT   rR   r   rZ   r[   r\   r   r]   r^   r6   TemporaryDirectoryospathjoinlstripmakedirsdirnamesymlinkabspathPopenPIPEr_   rh   killwaitclosecommunicatepollreadr`   decoderf   rg   len)r,   rv   r#   rS   ra   stagingrm   rn   stagedtar_cmdssh_cmdtar_procssh_proc_
ssh_stderrtar_stderr_raws                   r   rD   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   rT   rR   rr   rs   openrZ   r[   r   r\   r   r]   r   r^   )r,   r   rel_baser   fra   s         r   rE   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.Trd   rW   r   zremote rm failed: N)	rT   rR   r	   rZ   r[   r\   r   r]   r^   )r,   r   rS   ra   s       r   rC   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)rF   rG   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)rT   rP   rr   rs   r   )r,   r   r   r   r   rS   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   rM   rN   z-Oexitr%   T   )rX   r   )rF   rf   info	sync_backr>   existsr   r   rZ   r[   OSErrorSubprocessErrorunlink)r,   rS   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__rQ   intr5   listrT   r?   r@   rA   rB   tuplerD   r   rE   rC   r   boolrZ   r   r   r   __classcell__)rJ   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   r:   loggingr   rr   r   rZ   r6   pathlibr   tools.environments.baser   r   tools.environments.file_syncr   r   r   r	   r
   	getLoggerr   rf   r   r   r   r   r   <module>r      s#   Q Q   				              @ @ @ @ @ @ @ @              
	8	$	$	
 	
 	
 	
C C C C C_ C C C C Cr   