aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-client/build/installer.nsh
blob: 3157f22ecefd879566351ab784b1ecb934b85b06 (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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
; electron-builder NSIS customisation (auto-included: build/installer.nsh).
;
; Per-user install, no elevation at install time (package.json build.nsis) --
; that part never changes. What this adds, all conditional on interactive setup
; (never ${Silent}):
;   - the per-machine ("anyone who uses this computer") option removed from
;     electron-builder's install-mode page: MeshBay is per-user only -- the
;     keystore and the DPAPI-protected secrets are bound to the signed-in
;     account (MESHBAY_DESIGN.md §11.2 / §7.5) -- so that option was only ever
;     shown disabled here. customInstallMode forces current-user and the page
;     is skipped entirely;
;   - the bundled daemon dir on the user's PATH, so `meshbay-node` works in a
;     terminal;
;   - a custom page (radio buttons, like the rest of setup) choosing when the
;     node runs: only while the MeshBay app is open, at each sign-in (a per-user
;     Startup-folder launcher -- no admin -- see
;     meshbay_node.platform.autostart_install), or as a background service
;     (a boot-time S4U scheduled task -- one admin confirmation -- see
;     meshbay_node.platform.service_install and packaging/win/service.ps1).
;     Default: background service;
;   - the inbound firewall rules, set up in EVERY mode (a node that silently
;     accepts no connections is the failure mode called out in
;     MESHBAY_DESIGN.md §7.5). Folded into the SAME elevation as the boot
;     task when service mode is chosen; their own single elevation otherwise.
;     One UAC prompt for an install, never two, never zero;
;   - cleanup of whichever of those is outside $INSTDIR on the way out (the
;     Startup .vbs; the scheduled task and firewall rules, together, if the
;     user opts in at uninstall time).
;
; Deliberately NOT touched:
;   - %LOCALAPPDATA%\meshbay\  (node.toml, keystore.enc, unlock.key, data/) --
;     the keystore must survive an uninstall/reinstall; installers place files,
;     never remove secrets. This is also why service mode needs no code
;     changes to platform.py: it runs as this same user (S4U), so it is the
;     same profile either way.

!include "WinMessages.nsh"
!include "WordFunc.nsh"
!include "LogicLib.nsh"
!include "nsDialogs.nsh"
!insertmacro WordAdd
!insertmacro un.WordAdd

!define MB_PWSH "$SYSDIR\WindowsPowerShell\v1.0\powershell.exe"

; The dir electron-builder drops resources into. `meshbay-node.exe` and its
; frozen Python live directly in here. A fixed suffix of $INSTDIR, so both the
; add (install) and the remove (uninstall, where $INSTDIR is still known) match
; the exact same string.
!define MB_NODE_BIN "$INSTDIR\resources\node-runtime"

; ── force per-user, skip the all-users / current-user page ──────────────────
!macro customInstallMode
  StrCpy $isForceCurrentInstall "1"
!macroend

!macro customInit
  StrCpy $MB_AutoMode "2"
!macroend

; ── the autostart choice, as a radio page ─────────────────────────────────
; MB_AutoMode: "0" only-while-open, "1" at sign-in, "2" background service.
; Declared here (not at file scope) so the uninstaller pass -- which inserts
; none of the macros that touch it -- does not warn about an unused Var, which
; electron-builder's makensis promotes to a hard error. customInit defaults it
; for silent installs, where this page never runs.
!macro customPageAfterChangeDir
  Var MB_AutoMode
  Var MB_Dlg
  Var MB_RbOpen
  Var MB_RbSignin
  Var MB_RbService

  Page custom mbAutostartPageCreate mbAutostartPageLeave

  Function mbAutostartPageCreate
    !insertmacro MUI_HEADER_TEXT "MeshBay Node" "Choose when the node runs on this computer."

    nsDialogs::Create 1018
    Pop $MB_Dlg
    ${If} $MB_Dlg == error
      Abort
    ${EndIf}

    ${NSD_CreateLabel} 0 0 100% 26u "The node makes your groups' content available to other members. It can run only while the MeshBay window is open, start on its own when you sign in, or run as a background service that is up even before you sign in."
    Pop $0

    ${NSD_CreateRadioButton} 6u 34u 96% 12u "Only while MeshBay is open"
    Pop $MB_RbOpen
    ${NSD_CreateRadioButton} 6u 48u 96% 12u "Automatically when I sign in to Windows"
    Pop $MB_RbSignin
    ${NSD_CreateRadioButton} 6u 62u 96% 12u "As a background service (starts at boot, before sign-in)  -- recommended"
    Pop $MB_RbService

    ${NSD_CreateLabel} 0 82u 100% 34u "Setup adds Windows Firewall rules for local-network connections in every case. The background-service option, and those firewall rules, together need one administrator confirmation now -- without it the node cannot be reached and the app is not operational."
    Pop $0

    ${If} $MB_AutoMode == "0"
      ${NSD_Check} $MB_RbOpen
    ${ElseIf} $MB_AutoMode == "1"
      ${NSD_Check} $MB_RbSignin
    ${Else}
      ${NSD_Check} $MB_RbService
    ${EndIf}

    nsDialogs::Show
  FunctionEnd

  Function mbAutostartPageLeave
    ${NSD_GetState} $MB_RbOpen $0
    ${If} $0 == ${BST_CHECKED}
      StrCpy $MB_AutoMode "0"
    ${EndIf}
    ${NSD_GetState} $MB_RbSignin $0
    ${If} $0 == ${BST_CHECKED}
      StrCpy $MB_AutoMode "1"
    ${EndIf}
    ${NSD_GetState} $MB_RbService $0
    ${If} $0 == ${BST_CHECKED}
      StrCpy $MB_AutoMode "2"
    ${EndIf}
  FunctionEnd
!macroend

!macro customInstall
  ; resources\node-runtime\meshbay-node.exe is about to be overwritten; a
  ; daemon still running from a previous version holds the file open.
  nsExec::Exec 'taskkill /IM meshbay-node.exe /F'

  ; Add the daemon dir to the per-user PATH (HKCU\Environment). WordAdd is a
  ; stock NSIS macro over a ';'-delimited list -- it is a no-op if the entry is
  ; already there, so a reinstall does not double it. New shells only; the
  ; broadcast tells already-open Explorer/shells to reload the environment.
  ReadRegStr $0 HKCU "Environment" "Path"
  ${WordAdd} "$0" ";" "+${MB_NODE_BIN}" $1
  WriteRegExpandStr HKCU "Environment" "Path" "$1"
  SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000

  ${IfNot} ${Silent}
    ; The firewall rules go in for every autostart mode. Read their state
    ; first, unelevated -- Get-NetFirewallRule needs no admin, only New/Remove
    ; do -- so an upgrade or repair that changes nothing triggers no UAC.
    nsExec::Exec '"${MB_PWSH}" -NoProfile -ExecutionPolicy Bypass -File "$INSTDIR\resources\firewall.ps1" check'
    Pop $R0                       ; 0 = every rule already present

    ${If} $MB_AutoMode == "2"
      ; Background service: the boot-time Scheduled Task AND the firewall
      ; rules, in ONE elevation (service-mode.ps1 does both). Skip it only
      ; when the task already exists and the rules are already there.
      nsExec::Exec '"${MB_PWSH}" -NoProfile -ExecutionPolicy Bypass -File "$INSTDIR\resources\service.ps1" status'
      Pop $R1                     ; 0 = task installed
      ${If} $R1 == 0
      ${AndIf} $R0 == 0
        Goto mb_auto_done
      ${EndIf}
      ExecShellWait "runas" "${MB_PWSH}" \
        '-NoProfile -ExecutionPolicy Bypass -File "$INSTDIR\resources\service-mode.ps1" -Action install' \
        SW_HIDE
      Goto mb_auto_done
    ${EndIf}

    ; Modes 0 and 1: no scheduled task. One elevation for the firewall rules,
    ; and only if one is actually missing.
    ${If} $R0 != 0
      ExecShellWait "runas" "${MB_PWSH}" \
        '-NoProfile -ExecutionPolicy Bypass -File "$INSTDIR\resources\firewall.ps1" add' \
        SW_HIDE
    ${EndIf}

    ; Mode 1 also drops the per-user sign-in launcher (no admin -- it is just
    ; a .vbs in this account's Startup folder). Idempotent, so a repeat run is
    ; harmless. meshbay_node.platform.service_install() removes this itself if
    ; the user later switches to service mode from the Node page.
    ${If} $MB_AutoMode == "1"
      nsExec::Exec '"${MB_NODE_BIN}\meshbay-node.exe" autostart install'
    ${EndIf}

    mb_auto_done:
  ${EndIf}
!macroend

!macro customUnInstall
  nsExec::Exec 'taskkill /IM meshbay-node.exe /F'

  ; Take our entry back out of PATH, leaving the rest of it alone.
  ReadRegStr $0 HKCU "Environment" "Path"
  ${un.WordAdd} "$0" ";" "-${MB_NODE_BIN}" $1
  WriteRegExpandStr HKCU "Environment" "Path" "$1"
  SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000

  ; Offer to take the firewall rules, and the service task if one was set up,
  ; back out together (needs admin again -- one prompt for both, same as
  ; install). Both underlying removes are no-ops when there is nothing to
  ; remove, so this is safe to run unconditionally regardless of which mode
  ; was chosen. Stale rules/tasks are inert if left, so this is opt-in and
  ; default-No; a silent uninstall skips it entirely. customUnInstall runs
  ; before the files are removed, so service-mode.ps1 is still there.
  ${IfNot} ${Silent}
    MessageBox MB_YESNO|MB_ICONQUESTION \
      "Remove MeshBay's Windows Firewall rules and its boot-time service task, if you set one up? This needs one administrator confirmation. Both are harmless if left." \
      /SD IDNO IDNO mb_keep_privileged
      ExecShellWait "runas" "${MB_PWSH}" \
        '-NoProfile -ExecutionPolicy Bypass -File "$INSTDIR\resources\service-mode.ps1" -Action remove' \
        SW_HIDE
    mb_keep_privileged:
  ${EndIf}

  ; meshbay_node.platform.autostart_install() -- if the user picked "at sign-in"
  ; (here, or later in the client), this points wscript at the binary we are
  ; about to delete, and would error at every sign-in.
  Delete "$APPDATA\Microsoft\Windows\Start Menu\Programs\Startup\MeshBay Node.vbs"
!macroend