From 4d3aa6e49791b0e8d5b35fdce1b767c68e3fb068 Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Thu, 8 Oct 2026 14:03:46 +0000 Subject: [PATCH] 0.3.26: the installer's detach step names its error and never installs in place under a job (PC 2, 0.3.25 take 3b, 8 October 2026, 14:54:54 BST: "could not copy to app\updates\Igneum-Miner-Setup-0.3.25.exe" with no reason, then the fallback installed in place under the job, the stop step quit the engine and the job's tree ended the installer 3 s in, nothing installed, the app down until a start-app). Igneum-Miner.iss: LastErrorWords (DLLGetLastError + SysErrorMessage) on every failed step; DetachSource makes the updates folder (logging a failure), copies through a .part name then renames (a half-written copy never carries the final name), and when no copy can be made schedules the SOURCE in place (a job's folder persists after the job); InitializeSetup under a job either starts the detached task or stops with "REFUSED: ... no detached start could be made (the lines above say why); nothing is installed and the app is left running", never an in-place install. installer-stop-check rule 6b: the OS error logged, no in-place fallback, the REFUSED line; self-test known-failed on take 3b's shape Co-Authored-By: Claude Fable 5.1 --- packaging/windows/Igneum-Miner.iss | 87 ++++++++++++++++++++++++------ tools/ci/installer-stop-check.sh | 9 ++++ 2 files changed, 79 insertions(+), 17 deletions(-) diff --git a/packaging/windows/Igneum-Miner.iss b/packaging/windows/Igneum-Miner.iss index 29a94de37..dedcaa72e 100644 --- a/packaging/windows/Igneum-Miner.iss +++ b/packaging/windows/Igneum-Miner.iss @@ -169,31 +169,77 @@ begin Result := ExpandConstant('{localappdata}\igneum\app\updates\install-{#AppVersion}-detached.log'); end; -// Copies this installer beside the app's data and starts the copy through a one-shot task. True when the task started. +// The OS's words for the last error, for the log (PC 2, take 3b, 8 October 2026: "could not copy" with no reason). +function LastErrorWords: String; +var + E: Integer; +begin + E := DLLGetLastError; + Result := IntToStr(E) + ' ' + SysErrorMessage(E); +end; + +// The installer's copy beside the app's data, or the source itself when no copy can be made (the job folder persists +// after the job; the detached task may run from it). Empty when neither can be used. +function DetachSource: String; +var + Dir, Copy, Temp, Src: String; +begin + Result := ''; + Src := ExpandConstant('{srcexe}'); + if not FileExists(Src) then + begin + Log('detach: the source ' + Src + ' does not exist (' + LastErrorWords + ')'); + exit; + end; + Dir := ExpandConstant('{localappdata}\igneum\app\updates'); + if not DirExists(Dir) then + begin + if not ForceDirectories(Dir) then + Log('detach: could not create ' + Dir + ' (' + LastErrorWords + ')'); + end; + Copy := Dir + '\Igneum-Miner-Setup-{#AppVersion}.exe'; + Temp := Dir + '\Igneum-Miner-Setup-{#AppVersion}.exe.part'; + if DirExists(Dir) then + begin + DeleteFile(Temp); + if FileCopy(Src, Temp, False) then + begin + DeleteFile(Copy); + if RenameFile(Temp, Copy) then + begin + Result := Copy; + exit; + end; + Log('detach: copied to ' + Temp + ' but could not rename it to ' + Copy + ' (' + LastErrorWords + '); the task runs the .part copy'); + Result := Temp; + exit; + end; + Log('detach: could not copy ' + Src + ' to ' + Temp + ' (' + LastErrorWords + ')'); + end; + // no copy: the source itself, from where it is (a job's folder persists after the job) + Log('detach: the task runs the source in place: ' + Src); + Result := Src; +end; + +// Starts the detached installer through a one-shot task. True when the task started. function RelaunchDetached: Boolean; var - Dir, Copy, Args, Cmd: String; + Copy, Args, Cmd: String; ResultCode: Integer; begin Result := False; - Dir := ExpandConstant('{localappdata}\igneum\app\updates'); - ForceDirectories(Dir); - Copy := Dir + '\Igneum-Miner-Setup-{#AppVersion}.exe'; - if not FileCopy(ExpandConstant('{srcexe}'), Copy, False) then - begin - Log('detach: could not copy ' + ExpandConstant('{srcexe}') + ' to ' + Copy); - exit; - end; + Copy := DetachSource; + if Copy = '' then exit; Args := '/VERYSILENT /NORESTART /SUPPRESSMSGBOXES /IGNOTA=' + ExpandConstant('{param:IGNOTA|0}') + ' /IGDETACHED=1 /LOG="' + DetachLogPath + '"'; Cmd := '/Create /F /SC ONCE /ST 23:59 /RL LIMITED /TN "' + DetachTask + '" /TR "\"' + Copy + '\" ' + Args + '"'; if not Exec('schtasks.exe', Cmd, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) or (ResultCode <> 0) then begin - Log('detach: schtasks /Create returned ' + IntToStr(ResultCode)); + Log('detach: schtasks /Create returned ' + IntToStr(ResultCode) + ' (' + LastErrorWords + ') for ' + Cmd); exit; end; if not Exec('schtasks.exe', '/Run /TN "' + DetachTask + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) or (ResultCode <> 0) then begin - Log('detach: schtasks /Run returned ' + IntToStr(ResultCode)); + Log('detach: schtasks /Run returned ' + IntToStr(ResultCode) + ' (' + LastErrorWords + ')'); Exec('schtasks.exe', '/Delete /F /TN "' + DetachTask + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); exit; end; @@ -229,11 +275,18 @@ begin Result := True; if UnderAppJob then begin - // the detached copy does the install; this process ends here, before any marker or stop step, so the job's tree - // teardown finds nothing of the install to kill - Result := not RelaunchDetached; - if not Result then exit; - Log('detach: the detached start failed; installing in place under the job (the stop step will end this process with the engine)'); + // the detached installer does the install; this process ends here, before any marker or stop step, so the job's tree + // teardown finds nothing of the install to kill. When no detached start can be made the install STOPS with the + // reason in the log (take 3b, PC 2, 8 October 2026: the in-place fallback under the job ended the engine and died + // with it 3 s later, the app down, nothing installed); it never installs in place under a job. + if RelaunchDetached then + begin + Result := False; + exit; + end; + Log('REFUSED: this installer runs under the app''s job ' + GetEnv('IGNEUM_JOB_ID') + ' and no detached start could be made (the lines above say why); nothing is installed and the app is left running'); + Result := False; + exit; end; Name := ExtractFileName(ExpandConstant('{srcexe}')); if (Pos('Setup-', Name) > 0) and (Pos('Setup-{#AppVersion}', Name) = 0) then diff --git a/tools/ci/installer-stop-check.sh b/tools/ci/installer-stop-check.sh index 33f8c91bf..377a70f6f 100755 --- a/tools/ci/installer-stop-check.sh +++ b/tools/ci/installer-stop-check.sh @@ -39,6 +39,11 @@ check() { # -> prints each broken rule, returns 1 when init="$(sed -n '/^function InitializeSetup/,/^end;/p' "$iss")" if ! grep -q "UnderAppJob" <<<"$init" || ! grep -q "RelaunchDetached" <<<"$init"; then echo "rule 6: InitializeSetup does not run the detach before anything else"; bad=1; fi if ! grep -q "RemoveDetachTask" "$iss"; then echo "rule 6: the detached run does not remove its task at ssDone"; bad=1; fi + # rule 6b (take 3b, PC 2, 8 October 2026): the copy's OS error is logged, and a job install that cannot detach STOPS with the + # reason; it never installs in place under the job (the stop step would end the engine and the job's tree with it) + if ! grep -q "SysErrorMessage" "$iss" || ! grep -q "LastErrorWords" "$iss"; then echo "rule 6b: the detach step does not log the OS error of a failed copy or task"; bad=1; fi + if grep -q "installing in place under the job" "$iss"; then echo "rule 6b: a job install that cannot detach falls back to installing in place (it must stop with the reason)"; bad=1; fi + if ! grep -q "REFUSED: this installer runs under the app''s job" "$iss"; then echo "rule 6b: no REFUSED line for a job install that cannot detach"; bad=1; fi # Inno's Pascal: a routine must be declared before the line that calls it (CurStepChanged calls RemoveDetachTask) local def use def="$(grep -n '^procedure RemoveDetachTask' "$iss" | cut -d: -f1 || true)"; use="$(grep -n '^procedure CurStepChanged' "$iss" | cut -d: -f1 || true)" @@ -66,6 +71,10 @@ ISS grep -v "IGNEUM_JOB_ID\|IGDETACHED\|/Create /F /SC ONCE\|schtasks.exe', '/Run\|UnderAppJob\|RelaunchDetached\|RemoveDetachTask" packaging/windows/Igneum-Miner.iss > "$t/tree.iss" tree_out="$(check "$t/tree.iss" packaging/windows/stop-igneum.ps1 2>/dev/null || true)" if ! grep -q "rule 6" <<<"$tree_out"; then echo "self-test: the job-tree shape passed, it must fail rule 6"; exit 1; fi + # the take 3b shape (install-detach-25): a copy that fails with no reason and an in-place fallback under the job + { grep -v "SysErrorMessage\|LastErrorWords\|REFUSED: this installer runs under the app''s job" packaging/windows/Igneum-Miner.iss; echo " Log('detach: the detached start failed; installing in place under the job');"; } > "$t/take3b.iss" + t3b_out="$(check "$t/take3b.iss" packaging/windows/stop-igneum.ps1 2>/dev/null || true)" + if ! grep -q "rule 6b" <<<"$t3b_out"; then echo "self-test: the take 3b shape passed, it must fail rule 6b"; exit 1; fi echo "installer-stop self-test: tonight's shape is caught, the tree's files pass" exit 0 fi