0.3.25: an installer started by the app's job runner detaches itself from the job's process tree (PC 2, 0.3.24 take 2, 8 October 2026, 09:24Z: the stop step ended the host and quit the engine, the engine's job runner ended the job's whole process tree on its way out (src/jobrun.rs kill_tree, taskkill /T) and the installer, the job script's child, died between PrepareToInstall and the copy: every exe still 0.3.21, install-running.flag left behind, no ssDone, no DeinitializeSetup). Igneum-Miner.iss: under a job (IGNEUM_JOB_ID set, no /IGDETACHED=1) InitializeSetup copies the installer to <localappdata>\igneum\app\updates\, registers and runs a one-shot scheduled task (a child of the Task Scheduler, outside the tree) with the same switches plus /IGDETACHED=1 and /LOG=install-<v>-detached.log, and exits before any marker or stop step; the detached run installs and deletes its task at ssDone; if the detached start fails it installs in place and says so in the log. The job's proof is the --version wait and the detached log, not this process's exit code (1). installer-stop-check rule 6 reads the path, the order (InitializeSetup first) and Inno's declare-before-use (RemoveDetachTask above CurStepChanged); self-test known-failed on 78dfe65e's installer
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
78dfe65e5e
commit
fa00b67bbd
2 changed files with 96 additions and 1 deletions
|
|
@ -143,19 +143,98 @@ begin
|
|||
DeleteFile(MarkerPath);
|
||||
end;
|
||||
|
||||
// An installer started by the app's own job runner is a CHILD of the engine; the stop step then quits the engine, and
|
||||
// the engine ends the job's whole process tree on its way out (src/jobrun.rs kill_tree: taskkill /T), the installer with
|
||||
// it, between PrepareToInstall and the copy (PC 2, 0.3.24 take 2, 8 October 2026, 09:24Z: the host gone, no file copied,
|
||||
// install-running.flag left behind, no ssDone and no DeinitializeSetup). So under a job (IGNEUM_JOB_ID set) this installer
|
||||
// copies itself to the app's updates folder and runs that copy DETACHED through a one-shot scheduled task (a child of
|
||||
// the Task Scheduler, outside the job's tree), with the same switches plus /IGDETACHED=1 and its own log, then exits
|
||||
// without touching the app; the detached run installs and removes the task at its end. The job's proof is the --version
|
||||
// wait and the detached log, not this process's exit code (1: "Setup failed to initialize").
|
||||
const
|
||||
DetachTask = 'Igneum Miner install';
|
||||
|
||||
function DetachedRun: Boolean;
|
||||
begin
|
||||
Result := ExpandConstant('{param:IGDETACHED|0}') = '1';
|
||||
end;
|
||||
|
||||
function UnderAppJob: Boolean;
|
||||
begin
|
||||
Result := (GetEnv('IGNEUM_JOB_ID') <> '') and (not DetachedRun);
|
||||
end;
|
||||
|
||||
function DetachLogPath: String;
|
||||
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.
|
||||
function RelaunchDetached: Boolean;
|
||||
var
|
||||
Dir, 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;
|
||||
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));
|
||||
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));
|
||||
Exec('schtasks.exe', '/Delete /F /TN "' + DetachTask + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
|
||||
exit;
|
||||
end;
|
||||
Log('detach: this installer runs under the app''s job ' + GetEnv('IGNEUM_JOB_ID') + '; the copy at ' + Copy + ' installs detached, log ' + DetachLogPath);
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
procedure RemoveDetachTask;
|
||||
var
|
||||
ResultCode: Integer;
|
||||
begin
|
||||
if DetachedRun then
|
||||
Exec('schtasks.exe', '/Delete /F /TN "' + DetachTask + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
|
||||
end;
|
||||
|
||||
procedure CurStepChanged(CurStep: TSetupStep);
|
||||
begin
|
||||
if CurStep = ssDone then ClearMarker;
|
||||
if CurStep = ssDone then
|
||||
begin
|
||||
ClearMarker;
|
||||
RemoveDetachTask;
|
||||
end;
|
||||
end;
|
||||
|
||||
// Two refusals before anything runs (0.3.23 take 1, PC 2, 22:07 BST): the installer's file name must carry ITS version
|
||||
// (a 0.3.21 kit once shipped a 0.3.22.0 host; a job that passes -Version reads this name), and a marker an earlier
|
||||
// installer left behind (older than 15 min: it died) is cleared with a line, so nothing holds on it.
|
||||
|
||||
function InitializeSetup: Boolean;
|
||||
var
|
||||
Name: String;
|
||||
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)');
|
||||
end;
|
||||
Name := ExtractFileName(ExpandConstant('{srcexe}'));
|
||||
if (Pos('Setup-', Name) > 0) and (Pos('Setup-{#AppVersion}', Name) = 0) then
|
||||
begin
|
||||
|
|
|
|||
|
|
@ -31,6 +31,18 @@ check() { # <iss file> <ps1 file> -> prints each broken rule, returns 1 when
|
|||
quit="$(grep -n "api/quit" "$ps1" | head -1 | cut -d: -f1 || true)"
|
||||
if [[ -z "$host" || -z "$quit" || "$host" -gt "$quit" ]]; then echo "rule 3: the window host is not ended before the engine is asked to quit"; bad=1; fi
|
||||
if ! grep -q "STILL LOCKED" "$ps1" || ! grep -q "FileShare\]::None" "$ps1"; then echo "rule 4: no unlock wait with a STILL LOCKED line"; bad=1; fi
|
||||
# rule 6 (PC 2, 0.3.24 take 2, 8 October 2026, 09:24Z): an installer started by the app's job runner is the engine's child and
|
||||
# dies with the job's tree when the stop step quits the engine; under a job (IGNEUM_JOB_ID) it must re-run itself detached
|
||||
# (a copy through a one-shot scheduled task, /IGDETACHED=1) and exit before the marker and the stop step
|
||||
if ! grep -q "GetEnv('IGNEUM_JOB_ID')" "$iss" || ! grep -q "IGDETACHED" "$iss" || ! grep -q "schtasks.exe', '/Create" "$iss" || ! grep -q "schtasks.exe', '/Run" "$iss"; then echo "rule 6: the installer does not detach itself from the app's job tree (IGNEUM_JOB_ID, a scheduled-task relaunch with /IGDETACHED=1)"; bad=1; fi
|
||||
local init
|
||||
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
|
||||
# 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)"
|
||||
if [[ -n "$def" && -n "$use" && "$def" -gt "$use" ]]; then echo "rule 6: RemoveDetachTask is declared after CurStepChanged, which calls it (Inno compiles top down)"; bad=1; fi
|
||||
return $bad
|
||||
}
|
||||
|
||||
|
|
@ -50,6 +62,10 @@ ISS
|
|||
printf '%s\n' '$install = Split-Path -Parent $MyInvocation.MyCommand.Path' 'Invoke-WebRequest -Uri ($url + "api/quit")' 'Stop-Process -Id $p.ProcessId' > "$t/old.ps1"
|
||||
if check "$t/old.iss" "$t/old.ps1" >/dev/null; then echo "self-test: tonight's shape passed, it must fail"; exit 1; fi
|
||||
if ! check packaging/windows/Igneum-Miner.iss packaging/windows/stop-igneum.ps1 >/dev/null; then echo "self-test: the tree's own files fail"; check packaging/windows/Igneum-Miner.iss packaging/windows/stop-igneum.ps1 || true; exit 1; fi
|
||||
# the job-tree shape (0.3.24 take 2): an installer with no detach path; known-failed first on release-0.3.25 9b93e649's .iss
|
||||
grep -v "IGNEUM_JOB_ID\|IGDETACHED\|schtasks.exe', '/Create\|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
|
||||
echo "installer-stop self-test: tonight's shape is caught, the tree's files pass"
|
||||
exit 0
|
||||
fi
|
||||
|
|
|
|||
Loading…
Reference in a new issue