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
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
|
; 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
; What this machine already runs, before the previous version's uninstaller
; deletes the sign-in launcher: an upgrade keeps the mode it finds, instead
; of defaulting to "background service" -- which a silent upgrade cannot even
; set up (no elevation), so an "at sign-in" install came out of one with no
; autostart at all and its node stopped (found upgrading a real install).
; A fresh install still defaults to the service.
StrCpy $MB_AutoMode "2"
nsExec::Exec 'schtasks /query /tn "MeshBay Node"'
Pop $0
${If} $0 == 0
StrCpy $MB_AutoMode "2"
${ElseIf} ${FileExists} "$APPDATA\Microsoft\Windows\Start Menu\Programs\Startup\MeshBay Node.vbs"
StrCpy $MB_AutoMode "1"
${ElseIf} ${FileExists} "$INSTDIR\${APP_EXECUTABLE_FILENAME}"
StrCpy $MB_AutoMode "0"
${EndIf}
!macroend
; ── stop the node before a single file is touched ─────────────────────────
; electron-builder inserts customCheckAppRunning in place of its own
; app-running check, which it runs before uninstallOldVersion and before the
; files are extracted (installSection.nsh); customInstall only runs after both.
; A service-mode daemon lives in the task's S4U logon session, where an
; unelevated taskkill gets "Access is denied". Left running, it keeps
; meshbay-node.exe and its DLLs locked, their copy fails, and electron-builder's
; last-resort extract ignores the failure. build/stop-node.ps1 asks the node to
; stop through its own control API first -- any session, no elevation, a proper
; shutdown -- then Task Scheduler, then taskkill. It is embedded and run from
; the plugins directory: the installed copy of anything may be what is being
; replaced.
; Defining customCheckAppRunning makes allowOnlyOneInstallerInstance.nsh skip
; these two, which its own _CHECK_APP_RUNNING (inserted below) still needs.
!include "getProcessInfo.nsh"
Var pid
!macro customCheckAppRunning
InitPluginsDir
File "/oname=$PLUGINSDIR\mb-stop-node.ps1" "${BUILD_RESOURCES_DIR}\stop-node.ps1"
mb_stop_node:
DetailPrint "Stopping the MeshBay node..."
nsExec::Exec `"${MB_PWSH}" -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "$PLUGINSDIR\mb-stop-node.ps1"`
Pop $0
${If} $0 != 0
MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION "The MeshBay node is still running and holds files that setup must replace. Stop it (meshbay-node service stop, or end meshbay-node.exe in Task Manager), then click Retry." /SD IDCANCEL IDRETRY mb_stop_node
Quit
${EndIf}
!insertmacro IS_POWERSHELL_AVAILABLE
!insertmacro _CHECK_APP_RUNNING
!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
; The node was stopped by customCheckAppRunning, before the files were
; copied -- by the time this runs they already have been.
; 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 is already there and current and so are the rules. A
; stale task (2: another executable, or the 72-hour / battery defaults
; of an older setup) is registered again -- the owner cannot change it
; without elevation.
nsExec::Exec '"${MB_PWSH}" -NoProfile -ExecutionPolicy Bypass -File "$INSTDIR\resources\service.ps1" status'
Pop $R1 ; 0 = installed and current, 1 = absent, 2 = stale
${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. A boot task left from an earlier "background service"
; choice would start the node a second time, at boot and at sign-in: take
; it out (service-mode.ps1 remove keeps the firewall rules every mode needs).
nsExec::Exec 'schtasks /query /tn "MeshBay Node"'
Pop $R1
${If} $R1 == 0
ExecShellWait "runas" "${MB_PWSH}" \
'-NoProfile -ExecutionPolicy Bypass -File "$INSTDIR\resources\service-mode.ps1" -Action remove' \
SW_HIDE
${EndIf}
; 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}
mb_auto_done:
${EndIf}
; The sign-in launcher as the mode wants it -- silent installs too: during an
; upgrade the previous version's uninstaller has just deleted it. No admin,
; idempotent; `autostart install` refuses while a boot task exists.
${If} $MB_AutoMode == "1"
nsExec::Exec '"${MB_NODE_BIN}\meshbay-node.exe" autostart install'
${Else}
nsExec::Exec '"${MB_NODE_BIN}\meshbay-node.exe" autostart remove'
${EndIf}
Pop $R2
; Start the node customCheckAppRunning stopped, the way this mode runs it,
; rather than leave it down until the next boot or sign-in. Only a node that
; has been set up: a fresh install's has no account yet, and node:start
; provisions and starts it. Mode 0 is the app's to start (runAfterFinish).
${If} ${FileExists} "$LOCALAPPDATA\meshbay\node.toml"
${If} $MB_AutoMode == "2"
nsExec::Exec 'schtasks /query /tn "MeshBay Node"'
Pop $R2
${If} $R2 == 0
nsExec::Exec 'schtasks /run /tn "MeshBay Node"'
Pop $R2
${EndIf}
${ElseIf} $MB_AutoMode == "1"
nsExec::Exec '"${MB_NODE_BIN}\meshbay-node.exe" autostart start'
Pop $R2
${EndIf}
${EndIf}
!macroend
!macro customUnInstall
; The node is already stopped: the uninstaller runs customCheckAppRunning
; (un.checkAppRunning) before this.
; 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}
${AndIfNot} ${isUpdated}
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 uninstall' \
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. Not in an upgrade: the
; new version is about to take this path's place, and deleting the launcher
; here is what left upgraded "at sign-in" installs with no autostart at all.
${IfNot} ${isUpdated}
Delete "$APPDATA\Microsoft\Windows\Start Menu\Programs\Startup\MeshBay Node.vbs"
${EndIf}
!macroend
|