
    `gj9                         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y)
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_dirsc                      t        j                  d      st        d      t        j                  d      st        d      y)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     I/root/.hermes/venv/lib/python3.12/site-packages/tools/environments/ssh.py_ensure_ssh_availabler      sA    <<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                  j                  dd       t        j                  | d| d| j                               j                         d d } j                  | dz   _        t!                 j#                           j%                          _         j)                          t+         fd	 j,                   j.                   j0                   j2                  
       _         j4                  j7                  d        j9                          y )N)r   r   z
hermes-sshT)parentsexist_ok@:   z.sockc                  4    t         j                   d      S )N/.hermes)r   _remote_homeselfs   r   <lambda>z)SSHEnvironment.__init__.<locals>.<lambda>I   s    D4E4E3Fh1O!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   r0   zSSHEnvironment.__init__-   sF   S'2			  3 3 56Etd; ^^fAdV1TF#**,

)+cr
 #..J<u1EE""$ 446  ",P&&&&00!44
 	d+r   N
extra_argsreturnc                 @   dg}|j                  dd| j                   g       |j                  ddg       |j                  ddg       |j                  ddg       |j                  ddg       |j                  ddg       | j                  d	k7  r&|j                  d
t        | j                        g       | j                  r|j                  d| j                  g       |r|j                  |       |j                  | j                   d| j                          |S )Nr   -oControlPath=zControlMaster=autozControlPersist=300zBatchMode=yesz StrictHostKeyChecking=accept-newzConnectTimeout=10   z-p-ir    )extendr9   r   strr   appendr   r   )r'   rF   cmds      r   _build_ssh_commandz!SSHEnvironment._build_ssh_commandS   s    g

DL)<)<(=>?@

D./0

D./0

D/*+

D<=>

D-./99?JJc$))n-.==JJdmm,-JJz"

dii[$))-.
r   c                    | j                         }|j                  d       	 t        j                  |dddt        j                        }|j
                  dk7  rD|j                  j                         xs |j                  j                         }t        d|       y # 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)rQ   rO   
subprocessrunDEVNULL
returncodestderrstripstdoutr   TimeoutExpiredr   r   )r'   rP   result	error_msgs       r   r:   z$SSHEnvironment._establish_connectiond   s    %%'

67	W^^# ((F   A%"MM//1JV]]5H5H5J	"%<YK#HII & (( 	W!3DII;a		{*UVV	Ws   A;B :Cc                 x   	 | j                         }|j                  d       t        j                  |dddt        j                        }|j
                  j                         }|r'|j                  dk(  rt        j                  d|       |S | j                  dk(  ryd	| j                   S # t        $ r Y *w xY w)
z(Detect the remote user's home directory.z
echo $HOMET
   rT   r   zSSH: remote home = %srootz/rootz/home/)rQ   rO   rX   rY   rZ   r^   r]   r[   loggerdebug	Exceptionr   )r'   rP   r`   homes       r   r;   z"SSHEnvironment._detect_remote_homeu   s    	))+CJJ|$^^# ((F ==&&(D))Q.4d; 99		{##	  		s   BB- -	B98B9c                     | j                    d}|| d| d| dg}| j                         }|j                  t        |             t	        j
                  |dddt        j                         y)	z?Create base ~/.hermes directory tree on remote in one SSH call.r$   z/skillsz/credentialsz/cacheTrc   rT   N)r%   rQ   rO   r   rX   rY   rZ   )r'   basedirsrP   s       r   r<   z"SSHEnvironment._ensure_remote_dirs   sx    ##$H-g&4&(=$vO%%'

'-.$$	
r   	host_pathremote_pathc                    t        t        |      j                        }| j                         }|j	                  dt        j                  |              t        j                  |dddt        j                         ddd| j                   g}| j                  dk7  r&|j                  d	t        | j                        g       | j                  r|j                  d
| j                  g       |j                  || j                   d| j                   d| g       t        j                  |dddt        j                        }|j                   dk7  r&t#        d|j$                  j'                                y)z0Upload a single file via scp over ControlMaster.z	mkdir -p Trc   rT   r   rI   rJ   rK   z-PrL   r    r!      r   zscp failed: N)rN   r   parentrQ   rO   shlexquoterX   rY   rZ   r9   r   rM   r   r   r   r[   r   r\   r]   )r'   rl   rm   rp   	mkdir_cmdscp_cmdr`   s          r   r=   zSSHEnvironment._scp_upload   sC   T+&--.++-	9U[[%8$9:;$$	
 $,t/B/B.C DE99?NND#dii.12==NND$--01	dii[$))Ak]#KLM$$
 !fmm.A.A.C-DEFF "r   filesc           	      $	   |sy| j                    d}t        |      }|r| j                         }|j                  t	        |             t        j                  |dddt
        j                        }|j                  dk7  r&t        d|j                  j                                t        j                  d	      5 }|D ]  \  }}	 t        j                  j!                  ||      }	|	dk(  s|	j%                  d      rt        d
|d|      t        j                  j'                  ||	      }t        j(                  t        j                  j+                  |      d       	 t        j,                  t        j                  j/                  |      |        dddd|dg}| j                         }|j                  dt9        j:                  |              t        j<                  |t
        j                  t
        j>                  t
        j>                        }	 t        j<                  ||j@                  t
        j>                  t
        j>                        }|j@                  jI                          	 |jK                  d      \  }}d}|jM                         |jK                  d      \  }}n(|j                  r|j                  jO                         nd}|j                  dk7  r9t        d|j                   d|jS                  d       j                                |j                  dk7  r9t        d!|j                   d|jS                  d       j                                	 ddd       tT        jW                  d"tY        |             y# t"        $ r}
t        d
|d|      |
d}
~
ww xY w# t0        $ r3}t3        |dd      dk(  rt5        j6                  ||       n Y d}~-d}~ww xY w# tB        $ r" |jE                          |jG                           w xY w# t
        jP                  $ rL |jE                          |jE                          |jG                          |jG                          t        d      w xY w# 1 sw Y   xY w)#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$   Tro   rT   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 )rW   r^   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%   r
   rQ   rO   r   rX   rY   rZ   r[   r   r\   r]   r1   TemporaryDirectoryospathrelpath
ValueError
startswithjoinmakedirsdirnamesymlinkabspathOSErrorgetattrr   copy2rq   rr   PopenPIPEr^   rg   killwaitclosecommunicatepollreadr_   decodere   rf   len)r'   ru   rj   r   rP   r`   stagingrl   rm   
rel_remoteexcstagedetar_cmdssh_cmdtar_procssh_proc_
ssh_stderrtar_stderr_raws                       r   r?   zSSHEnvironment._ssh_bulk_upload   s    ##$H-$U+))+CJJ+G45^^# ((F   A%"%:6==;N;N;P:Q#RSS ((0BC M	w*/ &	;!#d!CJ $
(=(=e(D&&{o5HQ  gz:BGGOOF3dCJJrwwy96B!0 fc4#>G--/G
 NN<U[[=N<OPQ!'' ((!!	H%++8??:??%?? OO!!#@ ( 4 4S 4 A: "%==?*(0(<(<R(<(H%A~?GX__%9%9%;TWN ""a'",X-@-@,A%,,I,>DDFGI  ""a'"6x7J7J6K3!((	(:@@BCE  (SM	^ 	A3u:NW " &&{o5MdXV  q*d3t;Y7 84  " ,, @"#>??@{M	 M	s   =	R N'A;R#3N6BR?O5R5A%P#BR	N3N..N33R6	O2?(O-'R-O22R5+P  R#ARRRdestc                    | j                    dj                  d      }| j                         }|j                  dt	        j
                  |              t        |d      5 }t        j                  |t        j                  |t        j                  d      }ddd       j                  dk7  r6t        d	|j                  j                  d
      j                                y# 1 sw Y   OxY w)z*Download remote .hermes/ as a tar archive.r$   /ztar cf - -C / wbr|   )rW   r^   r\   r   Nr   zSSH bulk download failed: r}   r~   )r%   lstriprQ   rO   rq   rr   openrX   rY   rZ   r   r[   r   r\   r   r]   )r'   r   rel_baser   fr`   s         r   r@   z!SSHEnvironment._ssh_bulk_download/  s     ''(188=))+H(='>?@$ 	^^ ((!F	 !!;FMM<P<PXa<P<b<h<h<j;klmm "	 	s   "7C''C0remote_pathsc                    | j                         }|j                  t        |             t        j                  |dddt        j
                        }|j                  dk7  r&t        d|j                  j                                y)z*Batch-delete remote files in one SSH call.Trc   rT   r   zremote rm failed: N)
rQ   rO   r	   rX   rY   rZ   r[   r   r\   r]   )r'   r   rP   r`   s       r   r>   zSSHEnvironment._ssh_deleteA  s{    %%'

$\23$$
 !!3FMM4G4G4I3JKLL "r   c                 8    | j                   j                          y)zCSync files to remote via FileSyncManager (rate-limited internally).N)rA   rB   r&   s    r   _before_executezSSHEnvironment._before_executeO  s    !r   Fr|   )loginr   
stdin_data
cmd_stringr   r   c                    | j                         }|r)|j                  dddt        j                  |      g       n'|j                  ddt        j                  |      g       t	        ||      S )z7Spawn an SSH process that runs bash on the remote host.bashz-lz-c)rQ   rM   rq   rr   r   )r'   r   r   r   r   rP   s         r   	_run_bashzSSHEnvironment._run_bashW  s\     %%'JJdEKK
,CDEJJekk*&=>?3
++r   c                    | j                   r/t        j                  d       | j                   j                          | j                  j                         rr	 ddd| j                   dd| j                   d| j                   g}t        j                  |dd	t        j                  
       	 | j                  j                          y y # t        t        j                  f$ r Y 8w xY w# t        $ r Y y w xY w)Nz"SSH: syncing files from sandbox...r   rI   rJ   z-Oexitr    T   )rU   r   rW   )rA   re   info	sync_backr9   existsr   r   rX   rY   rZ   r   SubprocessErrorunlink)r'   rP   s     r   cleanupzSSHEnvironment.cleanupc  s    KK<=((*%%'
dl43F3F2G$HV		{!DII;%?A#'$,,	##**, ( Z778   s%   AC	 -C( 	C%$C%(	C43C4)~<   rK    )NrG   N)__name__
__module____qualname____doc__rN   intr0   listrQ   r:   r;   r<   r=   tupler?   r   r@   r>   r   boolrX   r   r   r   __classcell__)rE   s   @r   r   r   $   s1    9<DF$S $ $# $$*-$>A$LTD[ D "W"$S $4
 GS Gs Gt G:qOd5c?&; qO qOfnt n n$MS	 Md M" ;@!$+/
,C 
,4 
,
,!Dj
,4>4D4D
,r   r   r   )r   r5   loggingr   rq   r   rX   r1   pathlibr   tools.environments.baser   r   tools.environments.file_syncr   r   r   r	   r
   	getLoggerr   re   r   r   r   r   r   <module>r      sT    Q   	      @  
		8	$	
S_ Sr   