summaryrefslogtreecommitdiffstats
path: root/packaging/win/service.ps1
blob: 29a02fe3488f56f99eddd279a3c6b0fac6762d83 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
<#
.SYNOPSIS
    Install/remove/query/run/end the "MeshBay Node" boot-time Scheduled Task.

.DESCRIPTION
    A real Windows Service runs under LocalSystem/NetworkService before anyone
    signs in -- but those accounts have no normal user profile, and this app's
    entire design keeps node.toml, the keystore and all data under the signed-in
    user's own %LOCALAPPDATA%\meshbay\. Running as LocalSystem would not find
    any of it.

    The middle ground, and what this script sets up: a Scheduled Task that runs
    AS THIS USER at system boot, without needing them to sign in first -- an
    S4U (Service For User) logon: no password stored anywhere, and unlike
    LocalSystem it loads this account's own profile, so %LOCALAPPDATA%\meshbay\
    keeps working with zero changes. The cost: S4U carries no network
    credential (no reaching a domain share as this user), which the node
    never needed -- everything it touches is local disk plus outbound
    internet.

    Install uses Register-ScheduledTask with -LogonType S4U, not
    `schtasks /create`: schtasks only infers the logon type from whether /rp
    is present, and both readings were tried and broke on this exact machine's
    blank-password account (common on a personal PC) -- `/rp ""` fails
    credential validation ("the user name or password is incorrect", even
    though nothing is wrong), and omitting /rp registers "Interactive only"
    instead of S4U, which never runs at boot and doesn't launch anything even
    run on demand while signed in (both confirmed live, 2026-09-05).
    -LogonType S4U is explicit, no inference. remove/status/run/end have no
    such ambiguity and stay on schtasks.exe.

    Mirrors meshbay_node.platform.service_install/_remove/_status/_run/_end --
    same TASK_NAME, same flags -- so the CLI and the installer agree on what
    "installed" means. install/remove need admin (a boot trigger touches
    system-wide scheduler state); status/run/end do not, once the task exists,
    because Task Scheduler grants the owning user that much by default -- which
    is what lets the Node page's Start/Stop/Restart drive it with no further
    UAC prompts.

    Shipped as an extraResource at <install>\resources\service.ps1, so it
    locates meshbay-node.exe from its own path.

.PARAMETER Action
    install | remove | status | run | end
#>
[CmdletBinding()]
param(
    [ValidateSet("install", "remove", "status", "run", "end")]
    [string]$Action = "status"
)

# Not "Stop": schtasks writes its normal "task not found" outcome to stderr,
# and with ErrorActionPreference=Stop that promotes to a terminating error
# even through a 2>$null redirect (a native command's stderr is converted to
# an ErrorRecord before the redirect discards it). Every exit path below
# checks $LASTEXITCODE explicitly instead.
$TASK_NAME = "MeshBay Node"
$resources = $PSScriptRoot
$node      = Join-Path $resources "node-runtime\meshbay-node.exe"

function Get-CurrentUser {
    $domain = $env:USERDOMAIN
    if (-not $domain) { $domain = $env:COMPUTERNAME }
    return "$domain\$env:USERNAME"
}

switch ($Action) {
    "install" {
        if (-not (Test-Path $node)) { throw "meshbay-node.exe not found at $node" }
        $user = Get-CurrentUser
        $action = New-ScheduledTaskAction -Execute $node
        $trigger = New-ScheduledTaskTrigger -AtStartup
        $principal = New-ScheduledTaskPrincipal -UserId $user -LogonType S4U -RunLevel Limited
        Register-ScheduledTask -TaskName $TASK_NAME -Action $action -Trigger $trigger `
            -Principal $principal -Force -ErrorAction Stop | Out-Null
        Write-Host "service: installed ($user, runs at boot)"
    }
    "remove" {
        & schtasks /delete /tn $TASK_NAME /f 2>$null | Out-Null
        Write-Host "service: removed"
    }
    "status" {
        $out = & schtasks /query /tn $TASK_NAME /fo list 2>$null
        if ($LASTEXITCODE -ne 0) {
            Write-Output "NOT_INSTALLED"
            exit 1
        }
        $line = $out | Select-String "^Status:"
        $state = if ($line) { ($line -replace "^Status:\s*", "").Trim() } else { "unknown" }
        Write-Output "INSTALLED:$state"
        exit 0
    }
    "run" {
        & schtasks /run /tn $TASK_NAME
        if ($LASTEXITCODE -ne 0) { throw "schtasks /run failed (exit $LASTEXITCODE)" }
    }
    "end" {
        & schtasks /end /tn $TASK_NAME
        if ($LASTEXITCODE -ne 0) { throw "schtasks /end failed (exit $LASTEXITCODE)" }
    }
}