diff --git a/packaging/windows/farmcontrol.nsi b/packaging/windows/farmcontrol.nsi index 2944039..f376a24 100644 --- a/packaging/windows/farmcontrol.nsi +++ b/packaging/windows/farmcontrol.nsi @@ -73,6 +73,8 @@ Section "Farm Control" SecMain "NoRepair" 1 WriteUninstaller "$INSTDIR\Uninstall.exe" + + !insertmacro grantUsersInstallAccess SectionEnd Section "Uninstall" diff --git a/packaging/windows/grant-users-access.nsh b/packaging/windows/grant-users-access.nsh index 47f64e5..42cead9 100644 --- a/packaging/windows/grant-users-access.nsh +++ b/packaging/windows/grant-users-access.nsh @@ -1,12 +1,31 @@ ; Grant standard (non-admin) users read/execute on the install tree. ; Elevated installs into Program Files often create admin-only ACLs, which -; breaks launcher.exe and bun reading deeplink.js for normal users. +; breaks launcher.exe and deeplink handling for normal users. !macro grantUsersInstallAccess Push $0 + Push $1 DetailPrint "Granting standard users read and execute access to $INSTDIR" - ExecWait '"$SYSDIR\icacls.exe" "$INSTDIR" /inheritance:e /T /C' $0 - DetailPrint "Enabled inherited permissions (exit code: $0)" - ExecWait '"$SYSDIR\icacls.exe" "$INSTDIR" /grant *S-1-5-32-545:(OI)(CI)RX /T /C' $0 + StrCpy $1 "$INSTDIR" + InitPluginsDir + FileOpen $0 "$PLUGINSDIR\grant-access.ps1" w + FileWrite $0 '$$path = "' + FileWrite $0 $1 + FileWrite $0 '"$\r$\n' + FileWrite $0 '$$icacls = Join-Path $$env:SystemRoot "System32\icacls.exe"$\r$\n' + FileWrite $0 'function Invoke-Icacls {$\r$\n' + FileWrite $0 ' param([string[]]$$Arguments)$\r$\n' + FileWrite $0 ' $$proc = Start-Process -FilePath $$icacls -ArgumentList $$Arguments -Wait -PassThru -NoNewWindow$\r$\n' + FileWrite $0 ' Write-Host ("icacls " + ($$Arguments -join " ") + " -> " + $$proc.ExitCode)$\r$\n' + FileWrite $0 '}$\r$\n' + FileWrite $0 'Invoke-Icacls @($$path, "/reset", "/T", "/C")$\r$\n' + FileWrite $0 'Invoke-Icacls @($$path, "/inheritance:e", "/T", "/C")$\r$\n' + FileWrite $0 'Invoke-Icacls @($$path, "/grant", "BUILTIN\Users:(OI)(CI)RX", "/T", "/C")$\r$\n' + FileWrite $0 'Invoke-Icacls @($$path, "/grant", "NT AUTHORITY\Authenticated Users:(OI)(CI)RX", "/T", "/C")$\r$\n' + FileWrite $0 'Invoke-Icacls @($$path, "/grant", "ALL APPLICATION PACKAGES:(OI)(CI)RX", "/T", "/C")$\r$\n' + FileClose $0 + ExecWait '"$SYSDIR\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -File "$PLUGINSDIR\grant-access.ps1"' $0 DetailPrint "Granted Users read/execute (exit code: $0)" + Delete "$PLUGINSDIR\grant-access.ps1" + Pop $1 Pop $0 !macroend diff --git a/packaging/windows/installer.nsh b/packaging/windows/installer.nsh index 65ee57c..4fd380c 100644 --- a/packaging/windows/installer.nsh +++ b/packaging/windows/installer.nsh @@ -78,8 +78,6 @@ !include "grant-users-access.nsh" !macro customInstall - !insertmacro grantUsersInstallAccess - DetailPrint "Register farmcontrol URI Handler" DeleteRegKey HKCR "farmcontrol" WriteRegStr HKCR "farmcontrol" "" "URL:farmcontrol" @@ -87,7 +85,7 @@ WriteRegStr HKCR "farmcontrol\DefaultIcon" "" "$INSTDIR\bin\launcher.exe" WriteRegStr HKCR "farmcontrol\shell" "" "" WriteRegStr HKCR "farmcontrol\shell\Open" "" "" - WriteRegStr HKCR "farmcontrol\shell\Open\command" "" '"$INSTDIR\bin\bun.exe" "$INSTDIR\bin\deeplink.js" "%1"' + WriteRegStr HKCR "farmcontrol\shell\Open\command" "" '"$INSTDIR\bin\deeplink.exe" "%1"' DetailPrint "Creating shortcuts" !insertmacro createDesktopShortcut diff --git a/scripts/build-windows-deeplink.mjs b/scripts/build-windows-deeplink.mjs index 5a1b05f..c208ca3 100644 --- a/scripts/build-windows-deeplink.mjs +++ b/scripts/build-windows-deeplink.mjs @@ -11,15 +11,16 @@ export function stageWindowsDeeplink(appDir) { throw new Error(`stage-windows-deeplink: entrypoint not found: ${entrypoint}`) } - const outputPath = path.join(appDir, 'bin', 'deeplink.js') + const outputPath = path.join(appDir, 'bin', 'deeplink.exe') mkdirSync(path.dirname(outputPath), { recursive: true }) const result = spawnSync( 'bun', [ 'build', + '--compile', '--minify', - '--target=bun', + '--target=bun-windows-x64', entrypoint, '--outfile', outputPath diff --git a/scripts/build-windows-nsis.ps1 b/scripts/build-windows-nsis.ps1 index 7f5270d..87ee010 100644 --- a/scripts/build-windows-nsis.ps1 +++ b/scripts/build-windows-nsis.ps1 @@ -71,12 +71,12 @@ if ($stagedAppBytes -lt 5242880) { } $requiredExe = Join-Path $stagingAppDir "bin\launcher.exe" -$requiredDeeplinkScript = Join-Path $stagingAppDir "bin\deeplink.js" +$requiredDeeplinkExe = Join-Path $stagingAppDir "bin\deeplink.exe" if (-not (Test-Path $requiredExe)) { throw "Staged application is missing bin\launcher.exe" } -if (-not (Test-Path $requiredDeeplinkScript)) { - throw "Staged application is missing bin\deeplink.js" +if (-not (Test-Path $requiredDeeplinkExe)) { + throw "Staged application is missing bin\deeplink.exe" } Copy-Item -LiteralPath $nsiPath -Destination (Join-Path $workDir "farmcontrol.nsi") diff --git a/scripts/finalize-desktop-artifacts.mjs b/scripts/finalize-desktop-artifacts.mjs index 205b26e..abeb262 100644 --- a/scripts/finalize-desktop-artifacts.mjs +++ b/scripts/finalize-desktop-artifacts.mjs @@ -20,6 +20,7 @@ import { expandWindowsAppFromArchive } from './expand-windows-installer.mjs' import { stageWindowsDeeplink } from './build-windows-deeplink.mjs' +import { patchWindowsBinaries } from './patch-windows-binaries.mjs' import { codesignMacAppBundle } from './codesign-macos-app.mjs' const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') @@ -662,6 +663,7 @@ async function main() { platformDir ) stageWindowsDeeplinkScript(appDir) + await patchWindowsBinaries(appDir) let published try { diff --git a/scripts/patch-windows-binaries.mjs b/scripts/patch-windows-binaries.mjs new file mode 100644 index 0000000..43406f1 --- /dev/null +++ b/scripts/patch-windows-binaries.mjs @@ -0,0 +1,39 @@ +import { existsSync, readdirSync } from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { rcedit } from 'rcedit' + +const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') + +export async function patchWindowsBinaries(appDir) { + if (process.platform !== 'win32') { + return + } + + const binDir = path.join(appDir, 'bin') + if (!existsSync(binDir)) { + throw new Error(`patch-windows-binaries: bin directory not found at ${binDir}`) + } + + const executables = readdirSync(binDir).filter((name) => + name.toLowerCase().endsWith('.exe') + ) + + for (const executable of executables) { + const executablePath = path.join(binDir, executable) + await rcedit(executablePath, { + 'requested-execution-level': 'asInvoker' + }) + console.log(`patch-windows-binaries: set asInvoker on ${executablePath}`) + } +} + +if (process.argv[1] === fileURLToPath(import.meta.url)) { + const appDirArg = process.argv[2] + if (!appDirArg) { + console.error('Usage: bun scripts/patch-windows-binaries.mjs ') + process.exit(1) + } + + await patchWindowsBinaries(path.resolve(appDirArg)) +} diff --git a/src/desktop/windows-app-paths.js b/src/desktop/windows-app-paths.js index 6d5cac2..e3511e6 100644 --- a/src/desktop/windows-app-paths.js +++ b/src/desktop/windows-app-paths.js @@ -1,4 +1,4 @@ -import { existsSync } from 'node:fs' +import { chdirSync, existsSync } from 'node:fs' import { dirname, join } from 'node:path' const NATIVE_WRAPPER_DLL = 'libNativeWrapper.dll' @@ -34,7 +34,7 @@ export function ensureWindowsWorkingDirectory() { if (!wrapperInCwd && wrapperInBinDir) { try { - existsSync(binDir) + chdirSync(binDir) } catch { // Ignore if we cannot change directory. }