diff --git a/README.md b/README.md index c00b805..a811bd3 100644 --- a/README.md +++ b/README.md @@ -161,10 +161,10 @@ or after declining: powershell -NoProfile -ExecutionPolicy Bypass -File scripts\install-autostart.ps1 ``` -That registers PM2 for both processes and two scheduled tasks — one to start -everything at logon, one to check `master` for updates every 5 minutes. It needs -no elevation and is safe to re-run; it replaces the tasks rather than stacking -them up. +That puts the dashboard, the clicker and an update checker under PM2, adds a +Startup-folder entry so PM2 comes back at logon, and lets PM2's cron restart run +the update check every 5 minutes. It needs no administrator rights, changes +nothing machine-wide, and is safe to re-run. ```powershell pm2 list # what is running @@ -172,11 +172,14 @@ pm2 logs autofirmer # dashboard output pm2 logs clicker # runner output ``` -**Why scheduled tasks and not a Windows service.** The clicker sends real mouse -and keyboard input and has to own a desktop. A service runs in session 0, which -has none, so the clicks would go nowhere. Both tasks therefore run as you with -"run only when user is logged on" — which also means an unattended reboot leaves -the instance down until somebody logs in. +**Why not a Windows service.** The clicker sends real mouse and keyboard input +and has to own a desktop. A service runs in session 0, which has none, so the +clicks would go nowhere. Everything therefore runs in your logged-in session — +which also means an unattended reboot leaves the instance down until somebody +logs in. + +To stop it starting at logon, delete the `AutoFirmer.cmd` shortcut from your +Startup folder (`shell:startup` in the Run dialog). To start everything by hand without waiting for a logon: diff --git a/scripts/install-autostart.ps1 b/scripts/install-autostart.ps1 index 28d9263..f440bb8 100644 --- a/scripts/install-autostart.ps1 +++ b/scripts/install-autostart.ps1 @@ -6,13 +6,22 @@ The only Windows-specific piece of the setup. Everything it schedules is plain Node, so porting to macOS or Linux means replacing this file alone. - Two scheduled tasks are created, both running as the current user with - LogonType Interactive. That is not incidental: the clicker drives real mouse - and keyboard input and must own a desktop, which a Windows service (session - 0) does not have. + Deliberately needs no administrator rights, and changes nothing + machine-wide: - Idempotent - re-running replaces the tasks rather than duplicating them. - No elevation required. + * PM2 supervises the dashboard, the clicker and the update checker. + * A Startup-folder entry runs scripts\start-all.bat at logon. + * PM2's own cron restart drives the update checks. + + Task Scheduler is avoided on purpose - Register-ScheduledTask needs + elevation to write to the root task folder. + + Everything runs in the logged-in user's interactive session. That is not + incidental: the clicker sends real mouse and keyboard input and must own a + desktop, which a Windows service (session 0) does not have. The trade-off + is that an unattended reboot leaves the instance down until someone logs in. + + Idempotent - safe to re-run. .PARAMETER IntervalMinutes How often to check master for updates. Default 5. @@ -28,8 +37,6 @@ param( $ErrorActionPreference = 'Stop' $Root = Split-Path -Parent $PSScriptRoot -$StartTask = 'AutoFirmer Start' -$UpdateTask = 'AutoFirmer Update' function Info($m) { Write-Host " $m" } function Warn($m) { Write-Host " ! $m" -ForegroundColor Yellow } @@ -127,46 +134,50 @@ try { } } - if ((Invoke-Pm2 @('save')) -ne 0) { throw 'pm2 save failed' } - Info 'pm2: process list saved' } finally { Pop-Location } -# -- Scheduled tasks --------------------------------------------------------- -# LogonType Interactive is what grants the desktop the clicker needs. -$principal = New-ScheduledTaskPrincipal -UserId "$env:USERDOMAIN\$env:USERNAME" ` - -LogonType Interactive -RunLevel Limited +# -- Start at logon (Startup folder) ----------------------------------------- +# Not Task Scheduler: Register-ScheduledTask needs elevation to write to the +# root task folder, and the whole point is an install that never asks for +# admin. A Startup-folder entry needs no privileges and runs in the user's +# interactive session, which is exactly what the clicker requires - a service +# in session 0 has no desktop and its clicks would go nowhere. +$startupDir = [Environment]::GetFolderPath('Startup') +$startupCmd = Join-Path $startupDir 'AutoFirmer.cmd' +$startAll = Join-Path $Root 'scripts\start-all.bat' -$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries ` - -DontStopIfGoingOnBatteries -StartWhenAvailable ` - -MultipleInstances IgnoreNew -ExecutionTimeLimit (New-TimeSpan -Hours 1) +@" +@echo off +REM Written by scripts\install-autostart.ps1 - delete this file to stop +REM AutoFirmer starting at logon. +call "$startAll" +"@ | Set-Content -Path $startupCmd -Encoding ASCII -function Register-Task($Name, $Action, $Triggers, $Description) { - Unregister-ScheduledTask -TaskName $Name -Confirm:$false -ErrorAction SilentlyContinue - Register-ScheduledTask -TaskName $Name -Action $Action -Trigger $Triggers ` - -Principal $principal -Settings $settings -Description $Description | Out-Null - Info "task registered: $Name" +Info "logon entry: $startupCmd" + +# -- Update checks (PM2 cron) ------------------------------------------------ +# Also not Task Scheduler, same reason. PM2 is already running and already +# comes back at logon, so it can own the schedule too: --cron-restart fires the +# script on a schedule and --no-autorestart stops PM2 relaunching it the moment +# it exits. +$updateScript = Join-Path $Root 'scripts\update-check.mjs' +Invoke-Pm2 @('delete', 'autofirmer-update') -Quiet | Out-Null +$cron = "*/$IntervalMinutes * * * *" +if ((Invoke-Pm2 @('start', $updateScript, '--name', 'autofirmer-update', + '--no-autorestart', '--cron-restart', $cron)) -eq 0) { + Info "update checks: every $IntervalMinutes minutes ($cron)" +} else { + Warn 'could not register the update checker - run scripts\update-check.mjs by hand to update' } -Register-Task $StartTask ` - (New-ScheduledTaskAction -Execute 'cmd.exe' ` - -Argument "/c `"$(Join-Path $Root 'scripts\start-all.bat')`"" -WorkingDirectory $Root) ` - (New-ScheduledTaskTrigger -AtLogOn) ` - 'Start AutoFirmer and the clicker at logon.' - -# AtLogOn covers a reboot; the repeating Once trigger covers the rest of the day. -$repeat = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1) ` - -RepetitionInterval (New-TimeSpan -Minutes $IntervalMinutes) ` - -RepetitionDuration (New-TimeSpan -Days 3650) - -Register-Task $UpdateTask ` - (New-ScheduledTaskAction -Execute 'node.exe' ` - -Argument 'scripts\update-check.mjs' -WorkingDirectory $Root) ` - @((New-ScheduledTaskTrigger -AtLogOn), $repeat) ` - "Check master for updates every $IntervalMinutes minutes; rebuild and restart when it moves." +# Save again so `pm2 resurrect` at logon brings the updater back too. +if ((Invoke-Pm2 @('save')) -ne 0) { Warn 'pm2 save failed - processes may not return after a reboot' } Write-Host '' -Info 'Done. Both processes are registered and will come back at logon.' -Info "Update checks run every $IntervalMinutes minutes; see scripts\update.log" +Info 'Done. No admin was needed and nothing machine-wide was changed.' +Info 'Running now, and again at every logon.' +Info "Updates checked every $IntervalMinutes minutes; see scripts\update.log" Info 'Useful: pm2 list | pm2 logs autofirmer | pm2 logs clicker' +Info "To disable autostart: delete $startupCmd"