
    q%jT                        d Z ddlmZ ddlZddlZddlZddlmZmZ  ej                  d      Z	dZ
dZdZd	Zd
ZddZddZdddZd Zy)uM  Tailscale Funnel 自動修復(階段 10-F)。

uvicorn 啟動後在背景跑這個 task,每 5 分鐘 self-check 一次:
  1. 從外部 URL `https://cooper-nas.taila49e28.ts.net/health` curl
  2. 若 200 → 通道好,啥都不做
  3. 若 timeout / TLS handshake fail / 非 200 → 重設 funnel(off + on)
  4. 連續 fix 上限 3 次,免無限亂搞

依賴:
  - robertsu 已被設成 tailscale operator(`sudo tailscale set --operator=robertsu` 已執行)
  - 所以這個 daemon 不用 sudo

每次自動 fix 都會寫 AgentActivity 紀錄,你在 dashboard「庫柏動態」能看到。
    )annotationsN)datetimetimezonezcoba.funnel_watchdogz,/var/packages/Tailscale/target/bin/tailscalez+https://cooper-nas.taila49e28.ts.net/healthi,  
   i@  c            
     @  K   dddddddt        t              t        g	} 	 t        j                  | t        j
                  j                  t        j
                  j                  d d	{   }t        j                  |j                         t        d
z          d	{   \  }}|j                         j                         }|dk(  rydd|xs d fS 7 j7 8# t        j                  $ r Y yt        $ r&}dt        |      j                   d| fcY d	}~S d	}~ww xY ww)ut   從外部公開 URL curl 自己。回 (ok, msg)。
    ok=True 代表 200 OK;False 代表通道斷或非 200。
    curlz-sz-oz	/dev/nullz-wz%{http_code}z
--max-timestdoutstderrN   timeout200)TokFz
http_code=none)Fr   : )strHEALTH_TIMEOUT_SECONDS
FUNNEL_URLasynciocreate_subprocess_exec
subprocessPIPEwait_forcommunicatedecodestripTimeoutError	Exceptiontype__name__)cmdprocr
   r   	http_codees         @/volume1/homes/robertsu/coba/app/app/services/funnel_watchdog.py_curl_externalr'      s    
 	dKnc01	C133%%**%%**
 

  '//0@0@0BLbefLfggMMO))+	
9#6"7888

 h
    1a))*"QC0001sq   DA	C )C*3C C*C D	
C DC C D+D-D5DDDDDc                 v  K   	 t        j                  t        ddt         j                  j                  t         j                  j                         d{   } t        j
                  | j                         d       d{    t        j                  d       d{    t        j                  t        ddt        t              t         j                  j                  t         j                  j                         d{   }t        j
                  |j                         d       d{   \  }}||z   j                  d	
      }d|v sd|v s|j                  dk(  rydd|j                   d|dd  fS 7 '7 7 7 7 T# t        $ r&}dt        |      j                   d| fcY d}~S d}~ww xY ww)uj   跑 tailscale funnel off + on。回 (success, msg)。

    operator 已是 robertsu,不需要 sudo。
    funneloffr	   N   r      z--bgreplace)errorszFunnel startedzAvailable on the internetr   )Tzfunnel restartedFzfunnel start unexpected: rc=z out=   r   )r   r   TAILSCALE_BINr   r   r   r   sleepr   PORTr   
returncoder   r    r!   )p1p2r
   r   outr%   s         r&   _reset_funnelr7   :   s    
1118U%%**%%**
 

 r~~/<<<mmA118VSY%%**%%**
 

  '//0@"MM&&i&8s"&AS&HBMM]^L^+4R]]O5TcTTT#

 	=

 N
  1a))*"QC0001s   F9AF E<,F E?F F A#F F,F 0F13F $F9%F ;F9<F ?F F F F 	F6F1+F6,F91F66F9c                T  K   	 ddl m} ddlm}  |       4 d{   }|j	                   |dd| ||xs i              |j                          d{    ddd      d{    y7 M7 7 	# 1 d{  7  sw Y   yxY w# t        $ r t        j                  d       Y yw xY ww)u,   寫 AgentActivity 讓 dashboard 看得到。r   )AsyncSessionLocal)AgentActivityNfunnel_watchdog)activity_typeuser_idsummarysuccessextrau#   funnel_watchdog 寫 activity 失敗)	app.databaser9   app.models.agent_activityr:   addcommitr   logger	exception)r>   r?   r@   r9   r:   dbs         r&   _log_activityrH   W   s     @2;$&&"FF=/kr  ))+ '&&  '&&&  @>?@s   B(B A)B 4A/A+A/B #A-$B (B()B +A/-B /B5A86B=B  B(B B%"B($B%%B(c                 z  K   t         j                  dt               t        j                  d       d{    d} 	 	 t                d{   \  }}|r| dkD  rt         j                  d|        d} n| dz  } t         j                  d||        | d	k\  rt        d
|  d| d       t                d{   \  }}t        d|rdnd d|||| d       d{    |rTt        j                  d       d{    t                d{   \  }}|rt        dd       d} nt        d| d       | dkD  rd} t        j                  t               d{    !7 )7 7 7 7 o7 _# t        j                  $ r t         j                  d       Y yt        $ r t         j                  d       Y zw xY w7 aw)u"   每 5 分鐘檢查一次 funnel。u0   funnel_watchdog_loop 啟動 (check interval=%ds)   Nr   Tzfunnel back ok (after %d fails)   z9[funnel_watchdog] external curl fail: %s (consecutive=%d)r,   z)[FUNNEL_WATCHDOG] resetting funnel after z fails: )flushu"   Funnel 通道掉了,自動重啟 (u   成功u   失敗))curl_msgfix_msgconsecutive_fails)r?   r@   r   u+   [FUNNEL_WATCHDOG] ✓ recovered after resetu4   [FUNNEL_WATCHDOG] ⚠ reset done but still failing:    u)   funnel_watchdog_loop 收到 cancel,退出u0   funnel_watchdog 迭代例外(繼續下個週期))rE   infoCHECK_INTERVAL_SECONDSr   r1   r'   warningprintr7   rH   CancelledErrorr   rF   )rP   r   msgfixedrO   ok2msg2s          r&   funnel_watchdog_loopr[   i   s    
KKBDZ[
--

'	Q*,,GB$q(KK ACTU$%!!Q&!Z"$57 %)EFWEXX`ad`efnrs+8?%:NE7'<XT\<]]^_ %(+'.1B   %mmB///*8*:$:	T!$OX\]01-!$XY]X^"_gkl(1,,-) mm2333U  
 - &; 0$: %% 	KKCD 	QOP	Q 	4s   3F;EF;E, E!A(E, 4E$5$E, E&E, 8E(9E, 
E*0E, ;F;F9F;!E, $E, &E, (E, *E, ,(F6F;F63F;5F66F;)returnztuple[bool, str])N)r>   r   r?   boolr@   zdict | None)__doc__
__future__r   r   loggingr   r   r   	getLoggerrE   r0   r   rS   r   r2   r'   r7   rH   r[        r&   <module>rd      s^    #    '			1	2>:
  161:@$14rc   