' Watchdog: keeps the local dashboard server alive.
'
' History: this script used to also babysit a Cloudflare quick tunnel, including a
' health probe that killed a tunnel which was running but revoked server-side (that
' failure went unnoticed for 24 days on 2026-10-08). The tunnel was retired on
' 2026-10-13: its address drifted on every restart and Cloudflare revoked it every
' few days (observed 5 to 24 days).
'
' The public entry point is now Tailscale Funnel, owned by the tailscaled service
' (set to Automatic), so there is nothing here to supervise:
'     https://desktop-ue6cc4h.tail87341f.ts.net  ->  http://127.0.0.1:8765
'
' What this script still does, every 60 seconds:
'   dashboard server on 8765 missing  ->  launch start_dashboard_server.ps1, which
'   itself keeps python restarted if it crashes.
'
' Actions are appended to deploy\logs\watchdog.log so failures are diagnosable;
' the earlier versions failed silently, which made debugging guesswork.
'
' Runs hidden from the Startup folder. This file is intentionally ASCII-only.
Option Explicit

Dim shell, fso, deployDir, projectRoot, pythonExe
Set shell = CreateObject("WScript.Shell")
Set fso = CreateObject("Scripting.FileSystemObject")

deployDir   = Left(WScript.ScriptFullName, InStrRev(WScript.ScriptFullName, "\"))
projectRoot = fso.GetParentFolderName(deployDir)
pythonExe   = "C:\Users\Luka\AppData\Local\Programs\Python\Python312\python.exe"

Dim LOCAL_PROBE
LOCAL_PROBE = "http://127.0.0.1:8765/reports/dashboard/version.json"

Dim consecFail
consecFail = 0

' Action log. The watchdog used to fail silently, which made it impossible to tell
' whether it was working at all.
Dim wdLog
wdLog = deployDir & "logs\watchdog.log"

Sub Note(msg)
    On Error Resume Next
    Dim f
    Set f = fso.OpenTextFile(wdLog, 8, True)   ' 8 = ForAppending
    If Err.Number = 0 Then
        f.WriteLine Now & "  " & msg
        f.Close
    End If
    On Error GoTo 0
End Sub

Do
    On Error Resume Next

    Dim svc, procs
    Set svc = GetObject("winmgmts:\\.\root\cimv2")

    ' ------------------------------------------- dashboard server on 127.0.0.1:8765
    Set procs = svc.ExecQuery("SELECT ProcessId FROM Win32_Process WHERE Name = 'python.exe' AND CommandLine LIKE '%serve_dashboard.py%'")
    If procs.Count = 0 Then
        shell.Run "powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File """ & deployDir & "start_dashboard_server.ps1""", 0, False
    End If

    ' ------------------------------------------- public entry point
    '
    ' The Cloudflare quick tunnel was retired on 2026-10-13. Its address changed on every
    ' restart and Cloudflare revoked it every few days (observed 5 to 24 days), which
    ' caused repeated outages and a lot of false alarms here.
    '
    ' The public entry point is now Tailscale Funnel:
    '     https://desktop-ue6cc4h.tail87341f.ts.net  ->  http://127.0.0.1:8765
    ' It has a permanent address, automatic HTTPS, and needs no port forwarding or
    ' firewall changes because it rides the outbound WireGuard tunnel. tailscaled owns
    ' it (service is set to Automatic), so this watchdog deliberately does not manage it.
    '
    ' Nothing to do here: only the local dashboard server above needs babysitting.

    On Error GoTo 0
    WScript.Sleep 60000
Loop

' Extract the public URL from public_url.txt, robustly.
'
' Why this is not just "read the text and trim":
'   If the file carries a UTF-8 BOM and is read through the FileSystemObject, the
'   resulting string can end up without a plain ASCII "http" substring at all, so a
'   naive InStr probe finds nothing and the URL parses as empty. That made the health
'   check treat a perfectly good tunnel as dead and kill it in a loop.
' Strategy: ADODB.Stream with charset utf-8 (it strips the BOM correctly); if that is
'   unavailable or the file is locked mid-write, fall back to a byte-safe ASCII scan.
Function ReadUrlText(path)
    Dim stream, text
    ReadUrlText = ""
    On Error Resume Next
    Set stream = CreateObject("ADODB.Stream")
    If Err.Number = 0 Then
        stream.Type = 2
        stream.Charset = "utf-8"
        stream.Open
        stream.LoadFromFile path
        text = stream.ReadText(-1)
        stream.Close
    End If
    On Error GoTo 0
    If InStr(text, "http") = 0 Then
        text = AsciiOnly(ReadAll(path))
    End If
    ReadUrlText = CleanUrl(text)
End Function

' Keep only the first run of printable ASCII characters (skips BOM junk and newlines).
Function AsciiOnly(s)
    Dim i, ch, out, started
    AsciiOnly = ""
    out = ""
    started = False
    For i = 1 To Len(s)
        ch = AscW(Mid(s, i, 1))
        If ch >= 33 And ch <= 126 Then
            out = out & Mid(s, i, 1)
            started = True
        ElseIf started Then
            Exit For
        End If
    Next
    AsciiOnly = out
End Function

' Read a small text file whole.
Function ReadAll(path)
    Dim ts
    ReadAll = ""
    On Error Resume Next
    Set ts = fso.OpenTextFile(path, 1, False)
    If Err.Number <> 0 Then Exit Function
    If Not ts.AtEndOfStream Then ReadAll = ts.ReadAll
    ts.Close
    On Error GoTo 0
End Function

' Normalise a URL read from disk.
' Defensive: PowerShell 5.1's Set-Content -Encoding UTF8 writes a UTF-8 BOM, and
' FSO reads it back as visible junk. A BOM-prefixed URL makes every probe fail,
' which would make this watchdog kill a perfectly healthy tunnel forever.
Function CleanUrl(s)
    Dim p, i, c, e
    CleanUrl = ""
    p = InStr(s, "http")
    If p = 0 Then Exit Function
    s = Mid(s, p)
    e = Len(s)
    For i = 1 To Len(s)
        c = AscW(Mid(s, i, 1))
        If c <= 32 Then
            e = i - 1
            Exit For
        End If
    Next
    CleanUrl = Left(s, e)
End Function

' True when the URL answers with HTTP 200 (short timeouts, no proxy).
Function UrlOk(url)
    Dim http, code
    UrlOk = False
    On Error Resume Next
    Set http = CreateObject("MSXML2.ServerXMLHTTP.6.0")
    If Err.Number <> 0 Then
        Set http = CreateObject("MSXML2.ServerXMLHTTP")
    End If
    If Err.Number <> 0 Then Exit Function
    http.setTimeouts 8000, 8000, 8000, 12000
    http.Open "GET", url, False
    http.setRequestHeader "Cache-Control", "no-store"
    http.Send
    code = http.Status
    If code = 200 Then UrlOk = True
    On Error GoTo 0
End Function
