fix(upgrade): refuse a pre-#385 library Console unit before units stop (#385)
This commit is contained in:
1 parent
d0ddfa8de2
commit
484aef7e2a
6 files changed
+153
-15
No files matched your search
@@ -5820,3 +5820,4 @@ Before: agents can read the library; supporting-path and push-credential depende
|
|||||||
2026-10-29T03:27:31Z | Codex implementation sub-agent | #385 OIDC-only writer preparation on exact base 09a847edaa25b609bac7728ff83f224708a29399 | Added fail-closed CLI, programmatic and service-render refusals for `--send` without `--sso`; retained read-only header preview/proof; migrated inventoried writer tests to real in-process OIDC login/callback sessions and updated docs. Focused regression 5/5 and selected service cases 4/4 pass; syntax and diff checks pass. The broader writer suite is blocked before execution because the clean checkout has no `node_modules` and exact pinned `[email protected]` is unavailable; no install, commit, push, PR, queue move or green/completion claim.
|
2026-10-29T03:27:31Z | Codex implementation sub-agent | #385 OIDC-only writer preparation on exact base 09a847edaa25b609bac7728ff83f224708a29399 | Added fail-closed CLI, programmatic and service-render refusals for `--send` without `--sso`; retained read-only header preview/proof; migrated inventoried writer tests to real in-process OIDC login/callback sessions and updated docs. Focused regression 5/5 and selected service cases 4/4 pass; syntax and diff checks pass. The broader writer suite is blocked before execution because the clean checkout has no `node_modules` and exact pinned `[email protected]` is unavailable; no install, commit, push, PR, queue move or green/completion claim.
|
||||||
2026-10-29T05:49:53Z | Codex Daybreak Blue xhigh implementation sub-agent | CORRECTION and Mos2255 #385 review repair R2 | The 03:27:31Z dependency/no-commit statements are historical: exact pinned `[email protected]` is now materialized through the ignored `node_modules` fixture with no install, and original preparation is committed at `9936a1be1a37fdb30ba53cf2f14b7897225c3e4a`. On that unchanged HEAD, uncommitted R2 source/tests/docs make Case 2 library preview read-only until #412, drop inherited non-SSO `--send` during release planning, and refuse duplicate service `--send`. Focused RED was 0/2, 0/1, 0/1; focused GREEN and selected T53/T55/T57/T58 fixture blocks pass. No root/native/live/full-suite/CI action, commit, push, PR/API, merge or frozen/live-pin change.
|
2026-10-29T05:49:53Z | Codex Daybreak Blue xhigh implementation sub-agent | CORRECTION and Mos2255 #385 review repair R2 | The 03:27:31Z dependency/no-commit statements are historical: exact pinned `[email protected]` is now materialized through the ignored `node_modules` fixture with no install, and original preparation is committed at `9936a1be1a37fdb30ba53cf2f14b7897225c3e4a`. On that unchanged HEAD, uncommitted R2 source/tests/docs make Case 2 library preview read-only until #412, drop inherited non-SSO `--send` during release planning, and refuse duplicate service `--send`. Focused RED was 0/2, 0/1, 0/1; focused GREEN and selected T53/T55/T57/T58 fixture blocks pass. No root/native/live/full-suite/CI action, commit, push, PR/API, merge or frozen/live-pin change.
|
||||||
2026-11-02T18:03:53Z | Claude Opus 5.5 implementation sub-agent (lead T3 c5c1e8ba) | #385 PR 1836 repair R3 on ab2bc23c7 | R2-F1: `scripts/stack-upgrade.sh` `read_args` now scans the serve argv after `--` and refuses `args-unreadable` before anything stops when the profile is library (no `--profile`, or the last one says library, as `case2-unit.sh` reads it) and `--send` is present; the message names the remediation, and the args file is never rewritten. `do_upgrade` and `do_rollback` both call `read_args` first, so rollback is covered; factory and tester paths are unchanged. RED on ab2bc23c7: new stack-upgrade case 0/1 (exit 2 rollback-failed, A4 and render-old failed, both units stopped), controls 1/1; GREEN 2/2. R2-I1: one component case runs the real `sso_for_target` then `send_for_target` on a legacy preview `--send` pointer, carried after `--sso` and before `--tester-socket`. R2-I2: TOOLS.md says a same-copy plain activate on a legacy pointer refuses already-active and needs `--no-send`. R2-I3: no code change; `uninstall`/`status` with `--send --send` exit 1, not 4, fail-closed either way. host-run-sheet section 7 gains the migration step. Whole files, one at a time: stack-upgrade 23/23 (69 s), stack-release 225/225 (608 s; a first run was cut by my 600 s background limit at 221 passing, its orphan fixture holder killed), case2-unit 48/48 (73 s). No root, systemd, container, network, push, PR or live action.
|
2026-11-02T18:03:53Z | Claude Opus 5.5 implementation sub-agent (lead T3 c5c1e8ba) | #385 PR 1836 repair R3 on ab2bc23c7 | R2-F1: `scripts/stack-upgrade.sh` `read_args` now scans the serve argv after `--` and refuses `args-unreadable` before anything stops when the profile is library (no `--profile`, or the last one says library, as `case2-unit.sh` reads it) and `--send` is present; the message names the remediation, and the args file is never rewritten. `do_upgrade` and `do_rollback` both call `read_args` first, so rollback is covered; factory and tester paths are unchanged. RED on ab2bc23c7: new stack-upgrade case 0/1 (exit 2 rollback-failed, A4 and render-old failed, both units stopped), controls 1/1; GREEN 2/2. R2-I1: one component case runs the real `sso_for_target` then `send_for_target` on a legacy preview `--send` pointer, carried after `--sso` and before `--tester-socket`. R2-I2: TOOLS.md says a same-copy plain activate on a legacy pointer refuses already-active and needs `--no-send`. R2-I3: no code change; `uninstall`/`status` with `--send --send` exit 1, not 4, fail-closed either way. host-run-sheet section 7 gains the migration step. Whole files, one at a time: stack-upgrade 23/23 (69 s), stack-release 225/225 (608 s; a first run was cut by my 600 s background limit at 221 passing, its orphan fixture holder killed), case2-unit 48/48 (73 s). No root, systemd, container, network, push, PR or live action.
|
||||||
|
2026-11-02T18:19:04Z | Claude Opus 5.5 implementation sub-agent (lead T3 c5c1e8ba) | #385 PR 1836 repair R4 on d0ddfa8de | R3-F1 fixed fail-closed, no migration: `scripts/stack-upgrade.sh` adds `legacy_console`, called right after `read_args` in `do_upgrade` and `do_rollback`. For the library profile (same default and last-wins rule as R3), an installed Console unit under the driver's UNIT_DIR whose ExecStart has a `--send` token after `serve` refuses new code `console-unit-legacy` (exit 3, nothing stopped). A missing unit is left to `read_old` (`units-unreadable`). The R3 args refusal stays. `case2-unit.sh`, factory, tester, stack-release.sh and OIDC/#412 untouched. Tests in stack-upgrade.test.mjs run the real base renderer (`git show 09a847eda` of case2-unit.sh and both templates) in the module fixture with fake getent/systemctl to make the legacy unit, prove the current case2-unit.sh refuses it, then drive upgrade and rollback with the real current renderer as A4's RENDER. Controls: read-only library unit, factory host, missing Console unit. RED on d0ddfa8de code 24/25 (the main case exits 2 rollback-failed, both units stopped), GREEN 25/25 (70.1 s). host-run-sheet section 7 now gives a read-only grep of the unit and says stop and report when it carries `--send`. TOOLS.md lists the code. No hash pins on changed files. stack-release.test.mjs and case2-unit.test.mjs read no changed file and were not rerun. One local commit, no push.
|
||||||
@@ -1882,3 +1882,4 @@ are never rewritten or removed; corrections are new entries.
|
|||||||
2026-10-29T03:27:31Z | Codex Daybreak Blue xhigh implementation sub-agent | #385 OIDC-only Console writes, branch work/vikunja-19-385-q284-complete at exact base 09a847edaa25b609bac7728ff83f224708a29399 | Source/test/docs preparation complete for review; focused refusal 5/5 and selected service 4/4 pass, broader real-OIDC writer tests blocked by absent pinned oauth4webapi@3.8.8; no install/commit/push/PR/queue move or overall-green claim.
|
2026-10-29T03:27:31Z | Codex Daybreak Blue xhigh implementation sub-agent | #385 OIDC-only Console writes, branch work/vikunja-19-385-q284-complete at exact base 09a847edaa25b609bac7728ff83f224708a29399 | Source/test/docs preparation complete for review; focused refusal 5/5 and selected service 4/4 pass, broader real-OIDC writer tests blocked by absent pinned oauth4webapi@3.8.8; no install/commit/push/PR/queue move or overall-green claim.
|
||||||
2026-10-29T05:49:53Z | Codex Daybreak Blue xhigh implementation sub-agent | Mos2255 #385 review repair R2 on work/vikunja-19-385-q284-complete at unchanged HEAD 9936a1be1a37fdb30ba53cf2f14b7897225c3e4a/base 09a847edaa25b609bac7728ff83f224708a29399 | Corrects the 03:27:31Z historical blocker: pinned oauth4webapi3.8.8 fixture is now present without install and original prep is committed. F1/F2/F3 repaired in uncommitted source/tests/docs; F4/F7 disclosed only, F6 append conflicts preserved. Three focused test files only: intentional RED captured, GREEN controls plus T53 12/12, T55 8/8, T57 13/13 and T58 11/11 pass. No root/native/live/full-suite/CI, commit/push/PR/API/merge, frozen packet or live-pin action.
|
2026-10-29T05:49:53Z | Codex Daybreak Blue xhigh implementation sub-agent | Mos2255 #385 review repair R2 on work/vikunja-19-385-q284-complete at unchanged HEAD 9936a1be1a37fdb30ba53cf2f14b7897225c3e4a/base 09a847edaa25b609bac7728ff83f224708a29399 | Corrects the 03:27:31Z historical blocker: pinned oauth4webapi3.8.8 fixture is now present without install and original prep is committed. F1/F2/F3 repaired in uncommitted source/tests/docs; F4/F7 disclosed only, F6 append conflicts preserved. Three focused test files only: intentional RED captured, GREEN controls plus T53 12/12, T55 8/8, T57 13/13 and T58 11/11 pass. No root/native/live/full-suite/CI, commit/push/PR/API/merge, frozen packet or live-pin action.
|
||||||
2026-11-02T18:03:53Z | Claude Opus 5.5 implementation sub-agent (lead T3 c5c1e8ba) | #385 PR 1836 repair R3 on work/vikunja-19-385-q284-complete at ab2bc23c7 | R2-F1 fixed: stack-upgrade refuses a library args file holding `--send` after `--` before any unit stops, for upgrade and rollback; R2-I1 component case and R2-I2 TOOLS.md precision added, R2-I3 noted. RED 0/1 then GREEN 2/2; stack-upgrade 23/23, stack-release 225/225, case2-unit 48/48. One local commit; no push, PR, Vikunja, Gitea or live action.
|
2026-11-02T18:03:53Z | Claude Opus 5.5 implementation sub-agent (lead T3 c5c1e8ba) | #385 PR 1836 repair R3 on work/vikunja-19-385-q284-complete at ab2bc23c7 | R2-F1 fixed: stack-upgrade refuses a library args file holding `--send` after `--` before any unit stops, for upgrade and rollback; R2-I1 component case and R2-I2 TOOLS.md precision added, R2-I3 noted. RED 0/1 then GREEN 2/2; stack-upgrade 23/23, stack-release 225/225, case2-unit 48/48. One local commit; no push, PR, Vikunja, Gitea or live action.
|
||||||
|
2026-11-02T18:19:04Z | Claude Opus 5.5 implementation sub-agent (lead T3 c5c1e8ba) | #385 PR 1836 repair R4 on work/vikunja-19-385-q284-complete at d0ddfa8de | R3-F1 fixed: stack-upgrade refuses `console-unit-legacy` before any unit stops when a library host's installed Console unit runs serve with `--send`, for upgrade and rollback; host-run-sheet section 7 and TOOLS.md updated. RED 24/25 then GREEN 25/25 in stack-upgrade.test.mjs; other suites not rerun (no changed file read). One local commit; no push, PR, Vikunja, Gitea or live action.
|
||||||
+8
-1
@@ -857,7 +857,14 @@ writes: one `case2-unit.sh` argument per line, the serve flags after `--`,
|
|||||||
owned by root with no group or other write, exactly one `--copy`, and that
|
owned by root with no group or other write, exactly one `--copy`, and that
|
||||||
copy the one both installed units run. Otherwise the run refuses
|
copy the one both installed units run. Otherwise the run refuses
|
||||||
`args-unreadable` or `args-differ`. The driver writes the new copy id into
|
`args-unreadable` or `args-differ`. The driver writes the new copy id into
|
||||||
that file only after `confirm` passes.
|
that file only after `confirm` passes. For the `library` profile, an installed
|
||||||
|
Console unit whose `ExecStart` has a `--send` token after `serve` was rendered
|
||||||
|
before #385, and the current `case2-unit.sh` refuses it. `upgrade` and
|
||||||
|
`rollback` refuse `console-unit-legacy` right after reading the args file,
|
||||||
|
before any step runs, because no migration exists yet
|
||||||
|
(`docs/guides/host-run-sheet.md` section 7). A missing Console unit is not
|
||||||
|
this refusal; the later unit read refuses it `units-unreadable`, also before
|
||||||
|
anything stops.
|
||||||
|
|
||||||
`upgrade`, in order. Every stack user step runs as
|
`upgrade`, in order. Every stack user step runs as
|
||||||
`sudo -u USER -H env PATH=<node dir>:/usr/local/bin:/usr/bin:/bin` from the
|
`sudo -u USER -H env PATH=<node dir>:/usr/local/bin:/usr/bin:/bin` from the
|
||||||
|
|||||||
@@ -385,21 +385,37 @@ but omits `--send`. That Console is read-only until Case 2 gains real OIDC in #4
|
|||||||
explicitly refuses `--send`. Proxy preview and proof-protected reads remain available. The `factory`
|
explicitly refuses `--send`. Proxy preview and proof-protected reads remain available. The `factory`
|
||||||
profile and its private tester socket are unchanged.
|
profile and its private tester socket are unchanged.
|
||||||
|
|
||||||
A library host installed before #385 has a `--send` line after `--`, because the earlier
|
A library host installed before #385 has `--send` in two places, because the earlier `case2-unit.sh`
|
||||||
`case2-unit.sh` required it. `stack-upgrade.sh upgrade` and `rollback` read the args file before
|
required it: a `--send` line after `--` in the args file, and `--send` in the installed Console unit's
|
||||||
anything stops, and when the profile is `library` (no `--profile` line, or the last one says
|
`ExecStart`. `stack-upgrade.sh upgrade` and `rollback` check both before anything stops, when the
|
||||||
`library`) and `--send` appears after `--`, they refuse `args-unreadable` with both units still
|
profile is `library` (no `--profile` line, or the last one says `library`):
|
||||||
running. The script never rewrites the file. To migrate, as root:
|
|
||||||
|
|
||||||
1. Open `/etc/mosaic-stack/dev.args` and delete the one line that reads `--send`. Change nothing else.
|
- `--send` after `--` in the args file refuses `args-unreadable`.
|
||||||
2. Check it: `stat -c '%U %a' /etc/mosaic-stack/dev.args` still gives `root` and a mode with no group or
|
- A `--send` token after `serve` in `/etc/systemd/system/mosaic-webui.service`'s `ExecStart` refuses
|
||||||
other write, and `grep -cx -- --send /etc/mosaic-stack/dev.args` prints `0`.
|
`console-unit-legacy`. The current `case2-unit.sh` refuses that installed unit, so an upgrade or
|
||||||
3. Rerun the same `stack-upgrade.sh` command.
|
rollback would stop both units and then fail both renders.
|
||||||
|
|
||||||
|
Both refusals leave the units running, and the script rewrites neither file. Check the installed unit
|
||||||
|
read-only, as root:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
grep -n '^ExecStart=' /etc/systemd/system/mosaic-webui.service | grep -c -- ' --send'
|
||||||
|
```
|
||||||
|
|
||||||
|
- `0`, and no installed unit carries `--send`: removing the args line is enough. Open
|
||||||
|
`/etc/mosaic-stack/dev.args` and delete the one line that reads `--send`, changing nothing else. Check
|
||||||
|
that `stat -c '%U %a' /etc/mosaic-stack/dev.args` still gives `root` and a mode with no group or other
|
||||||
|
write, and that `grep -cx -- --send /etc/mosaic-stack/dev.args` prints `0`. Then rerun the same
|
||||||
|
`stack-upgrade.sh` command.
|
||||||
|
- `1` or more: the host needs a reviewed migration of the unit, the args file and the release pointer
|
||||||
|
together, and none exists yet. Stop. Leave the units running, change neither file and report to the
|
||||||
|
lead with the command's output. Removing only the args line does not help: the run then refuses
|
||||||
|
`console-unit-legacy`.
|
||||||
|
|
||||||
The Console stays read-only after the upgrade, until #412 (#385).
|
The Console stays read-only after the upgrade, until #412 (#385).
|
||||||
|
|
||||||
Precheck line: none. `stack-upgrade.sh` refuses `args-unreadable` on any of these rules, before it stops
|
Precheck line: none. `stack-upgrade.sh` refuses `args-unreadable` or `console-unit-legacy` on any of these
|
||||||
a unit.
|
rules, before it stops a unit.
|
||||||
|
|
||||||
## 8. Enrollment, once per host
|
## 8. Enrollment, once per host
|
||||||
|
|
||||||
|
|||||||
@@ -5,14 +5,15 @@
|
|||||||
// stack-release-fixture.mjs's. Root's sudo, case2-unit.sh render and row 240 hostcheck are stand-ins
|
// stack-release-fixture.mjs's. Root's sudo, case2-unit.sh render and row 240 hostcheck are stand-ins
|
||||||
// here; the stack's user's steps are the real stack-release.sh from the user's clone, except the
|
// here; the stack's user's steps are the real stack-release.sh from the user's clone, except the
|
||||||
// rehearsal, which the fixture's stub copies can't pass (its own tests are in stack-release.test.mjs).
|
// rehearsal, which the fixture's stub copies can't pass (its own tests are in stack-release.test.mjs).
|
||||||
|
// The #385 R3-F1 cases run the real base and current case2-unit.sh instead of the render stand-in.
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import {execFileSync,spawn} from 'node:child_process';
|
import {execFileSync,spawn,spawnSync} from 'node:child_process';
|
||||||
import {mkdirSync,writeFileSync,readFileSync,existsSync,readdirSync,rmSync,chmodSync,copyFileSync,symlinkSync,statSync} from 'node:fs';
|
import {mkdirSync,writeFileSync,readFileSync,existsSync,readdirSync,rmSync,chmodSync,copyFileSync,symlinkSync,statSync} from 'node:fs';
|
||||||
import {DatabaseSync} from 'node:sqlite';
|
import {DatabaseSync} from 'node:sqlite';
|
||||||
import {join,dirname} from 'node:path';
|
import {join,dirname} from 'node:path';
|
||||||
import {once} from 'node:events';
|
import {once} from 'node:events';
|
||||||
import {B,C,C1,U,COPIES,NODE,SBIN,base,fault,git,lines,logLines,mk,pointer,repo,sha,storeVersion,sysctl,units} from './stack-release-fixture.mjs';
|
import {B,C,C1,U,COPIES,NODE,ROOT,SBIN,base,fault,git,lines,logLines,mk,pointer,repo,sha,storeVersion,sysctl,units} from './stack-release-fixture.mjs';
|
||||||
|
|
||||||
// --- Root's stand-ins. Each is serialized into its own file with K, its baked paths. ---
|
// --- Root's stand-ins. Each is serialized into its own file with K, its baked paths. ---
|
||||||
|
|
||||||
@@ -479,6 +480,90 @@ test('#385 R2-F1 controls: library args without --send and a factory args file p
|
|||||||
assert.ok(!existsSync(join(e.sd,'root-systemctl.log')));assert.deepEqual(live(e),['active','active',B,B]);
|
assert.ok(!existsSync(join(e.sd,'root-systemctl.log')));assert.deepEqual(live(e),['active','active',B,B]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// R3-F1 (#385, PR 1836): a library host rendered by base case2-unit.sh has --send in its installed Console
|
||||||
|
// unit's ExecStart, not only in its args file. With the args line removed, A2 would stop both units and A4
|
||||||
|
// and render-old would both refuse the installed unit (check_unit's ExecStart and the full-text match), so
|
||||||
|
// the driver refuses console-unit-legacy right after read_args. These cases run real renderers: base
|
||||||
|
// 09a847eda's case2-unit.sh, from git show into the module fixture, renders the legacy pair, and the
|
||||||
|
// checkout's own case2-unit.sh is A4's RENDER, both into an owned unit directory with fake getent and
|
||||||
|
// systemctl.
|
||||||
|
const BASE_REV='09a847eda';
|
||||||
|
let baseRenderer;
|
||||||
|
const baseCase2=()=>{
|
||||||
|
if(baseRenderer)return baseRenderer;
|
||||||
|
const d=join(base,'case2-base');
|
||||||
|
for(const p of ['scripts/case2-unit.sh','packages/cli/systemd/mosaic-bus.service.in','packages/webui/systemd/mosaic-webui.service.in']){
|
||||||
|
mkdirSync(dirname(join(d,p)),{recursive:true});writeFileSync(join(d,p),execFileSync('git',['-C',ROOT,'show',`${BASE_REV}:${p}`]),{mode:p.endsWith('.sh')?0o755:0o644});
|
||||||
|
}
|
||||||
|
return baseRenderer=join(d,'scripts','case2-unit.sh');
|
||||||
|
};
|
||||||
|
// The stack's home gets a proof file; dir is a Case 2 unit directory that A4's real renderer and the driver
|
||||||
|
// read. args(copy, extra) is a library args file's lines without --send; legacy() renders base's pair into dir.
|
||||||
|
function case2Host(e,dir){
|
||||||
|
const home=e.env.HOME,secrets=join(home,'.config','mosaic-dev','secrets');mkdirSync(secrets,{recursive:true,mode:0o700});
|
||||||
|
const proof=join(secrets,'proxy-proof');writeFileSync(proof,'made-up-proof\n');chmodSync(proof,0o600);
|
||||||
|
const c2=join(e.root,'case2'),bin=join(c2,'bin'),log=join(c2,'systemctl.log');mkdirSync(bin,{recursive:true});mkdirSync(dir,{recursive:true});
|
||||||
|
const uid=process.getuid(),gid=process.getgid();
|
||||||
|
writeFileSync(join(bin,'getent'),`#!/bin/sh\ncase "$1 $2" in\n "passwd fixture") echo "fixture:x:${uid}:${gid}::${home}:/bin/bash" ;;\n "group ${gid}") echo "fixture:x:${gid}:" ;;\n *) exit 2 ;;\nesac\n`,{mode:0o755});
|
||||||
|
writeFileSync(join(bin,'systemctl'),`#!/bin/sh\necho "$*" >> '${log}'\n[ "$1" != is-active ] || exit 3\n`,{mode:0o755});
|
||||||
|
const env={CASE2_UNIT_DIR:dir,CASE2_GETENT:join(bin,'getent'),CASE2_SYSTEMCTL:join(bin,'systemctl')};
|
||||||
|
const serve=['--port','443','--business','acme','--public-host','books.example','--auth-header','X-Auth-User','--allow-user','melanie','--proof-header','X-Proxy-Proof','--proof-file',proof];
|
||||||
|
const args=(copy=B,extra=[])=>['--user','fixture','--home',home,'--business','acme','--copy',copy,'--node',NODE,...extra,'--',...serve];
|
||||||
|
const render=(script,a)=>{const r=spawnSync('bash',[script,...a],{env:{PATH:e.env.PATH,...env},encoding:'utf8'});return {code:r.status,out:r.stdout,err:r.stderr}};
|
||||||
|
const legacy=()=>{const a=args();a.splice(a.indexOf('melanie')+1,0,'--send');const r=render(baseCase2(),a);assert.equal(r.code,0,r.err);return r};
|
||||||
|
const bytes=()=>Object.fromEntries(readdirSync(dir).sort().map((n)=>[n,readFileSync(join(dir,n),'utf8')]));
|
||||||
|
const syslog=()=>lines(log);
|
||||||
|
// The driver reads dir, and A4's RENDER is the checkout's real case2-unit.sh with the same seams.
|
||||||
|
const use=(text)=>{writeFileSync(e.args,text);chmodSync(e.args,0o640);e.argsText=text;e.denv.STACK_UPGRADE_UNIT_DIR=dir;delete e.denv.STACK_UPGRADE_RENDER;Object.assign(e.denv,env)};
|
||||||
|
return {home,dir,args,render,legacy,bytes,syslog,use,con:join(dir,'mosaic-webui.service')};
|
||||||
|
}
|
||||||
|
const legacyUnit=(f)=>`${f} runs serve with --send, so it was rendered before #385, and stack-upgrade.sh cannot migrate it to the read-only library Console: the units are left running; stop and report (docs/guides/host-run-sheet.md section 7) (nothing was stopped)`;
|
||||||
|
test('#385 R3-F1: an installed pre-#385 library Console unit with --send refuses console-unit-legacy before anything stops, for upgrade and rollback',async(t)=>{
|
||||||
|
const e=await useed(t),h=case2Host(e,join(e.root,'case2','units'));
|
||||||
|
h.legacy();
|
||||||
|
const c=readFileSync(h.con,'utf8');
|
||||||
|
assert.ok(c.includes(`\nExecStart=${NODE} ${e.hc}/${B}/packages/webui/src/cli.mjs serve --port 443 --business acme --public-host books.example --auth-header X-Auth-User --allow-user melanie --send --proof-header X-Proxy-Proof --proof-file `),c);
|
||||||
|
// The operator removed the args file's --send line, as R3's section 7 said, so read_args passes.
|
||||||
|
h.use(h.args().join('\n')+'\n');
|
||||||
|
assert.ok(!e.argsText.includes('--send'));
|
||||||
|
const before=h.bytes(),sys=h.syslog().length;
|
||||||
|
// The premise: the current renderer, as A4 runs it, refuses the installed legacy unit and writes nothing.
|
||||||
|
const p=h.render(join(checkout(C1),'scripts','case2-unit.sh'),h.args(C1));
|
||||||
|
assert.equal(p.code,1,p.out+p.err);
|
||||||
|
assert.ok(p.err.startsWith(`case2-unit: installed ${h.con}: ExecStart= must be exactly once in [Service], as ExecStart=${NODE} ${e.hc}/${B}/packages/webui/src/cli.mjs serve --port 443 `),p.err);
|
||||||
|
assert.deepEqual(h.bytes(),before);assert.equal(h.syslog().length,sys);
|
||||||
|
for(const [why,run] of [['upgrade',()=>upgrade(e,C1)],['rollback',()=>drive(e,B,'rollback','--instance','dev','--to',B,...TO)]]){
|
||||||
|
const n=existsSync(e.slog)?slog(e).length:0;
|
||||||
|
const r=await run();assert.equal(r.code,3,`${why}: ${r.all}`);
|
||||||
|
assert.equal(r.err.split('\n').at(-2),`stack-upgrade: console-unit-legacy: ${legacyUnit(h.con)}`,why);
|
||||||
|
assert.deepEqual(slog(e).slice(n).map((x)=>x.split(' ')[0]),['start','result'],why);
|
||||||
|
assert.equal(slog(e).at(-1),`result exit=3 console-unit-legacy ${legacyUnit(h.con)}`,why);
|
||||||
|
argsKept(e);assert.deepEqual(h.bytes(),before,why);
|
||||||
|
}
|
||||||
|
assert.ok(!existsSync(join(e.sd,'sudo.log')));assert.ok(!existsSync(join(e.sd,'render.log')));
|
||||||
|
assert.ok(!existsSync(join(e.sd,'root-systemctl.log')));assert.equal(h.syslog().length,sys);
|
||||||
|
assert.deepEqual(live(e),['active','active',B,B]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('#385 R3-F1 controls: a read-only library Console unit, a factory host\'s unit and a missing Console unit pass the check',async(t)=>{
|
||||||
|
const e=await useed(t);
|
||||||
|
const ro=case2Host(e,join(e.root,'case2','ro')),old=case2Host(e,join(e.root,'case2','legacy')),none=case2Host(e,join(e.root,'case2','none'));
|
||||||
|
const r0=ro.render(join(checkout(C1),'scripts','case2-unit.sh'),ro.args());assert.equal(r0.code,0,r0.err);
|
||||||
|
assert.ok(!readFileSync(ro.con,'utf8').includes('--send'));
|
||||||
|
old.legacy();
|
||||||
|
none.legacy();rmSync(none.con);
|
||||||
|
// A bundle that does not exist stops the run at its first step after the check, with nothing stopped.
|
||||||
|
const gone=join(e.root,'no.bundle');
|
||||||
|
for(const [why,h,extra] of [['a read-only library unit',ro,[]],['a factory args file over a unit holding --send',old,['--profile','factory']],['no installed Console unit',none,[]]]){
|
||||||
|
h.use(h.args(B,extra).join('\n')+'\n');
|
||||||
|
const n=existsSync(e.slog)?slog(e).length:0;
|
||||||
|
const r=await drive(e,C1,'upgrade','--instance','dev','--bundle',gone,'--sha',C1,...TO);assert.equal(r.code,3,`${why}: ${r.all}`);
|
||||||
|
assert.deepEqual(slog(e).slice(n).map((x)=>x.split(' ')[0]),['start','fetch','result'],why);
|
||||||
|
assert.match(slog(e).at(-1),/^result exit=3 fetch-failed /,why);
|
||||||
|
}
|
||||||
|
assert.ok(!existsSync(join(e.sd,'root-systemctl.log')));assert.deepEqual(live(e),['active','active',B,B]);
|
||||||
|
});
|
||||||
|
|
||||||
test('row 233: hostcheck (row 240): a nonzero --check or a missing file refuses profile-drift before the plan',async(t)=>{
|
test('row 233: hostcheck (row 240): a nonzero --check or a missing file refuses profile-drift before the plan',async(t)=>{
|
||||||
const e=await useed(t);
|
const e=await useed(t);
|
||||||
fault(e,'hostcheck-rc','1');
|
fault(e,'hostcheck-rc','1');
|
||||||
|
|||||||
@@ -120,7 +120,8 @@ logl "start rc=0 argv:$(printf ' %q' "$0" "${ARGV[@]}")"
|
|||||||
# --- The args file and the stack's user ---
|
# --- The args file and the stack's user ---
|
||||||
|
|
||||||
# I2's file: one case2-unit.sh argument per line, owned by root with no group or other write. Sets
|
# I2's file: one case2-unit.sh argument per line, owned by root with no group or other write. Sets
|
||||||
# ARGS (the lines), S_USER, S_HOME, BUSINESS, NODE and ARGS_COPY, and the paths derived from them.
|
# ARGS (the lines), S_USER, S_HOME, BUSINESS, NODE, ARGS_COPY and ARGS_PROFILE, and the paths derived
|
||||||
|
# from them.
|
||||||
# #385 R2-F1: base case2-unit.sh required --send for the library profile (its default, and the last
|
# #385 R2-F1: base case2-unit.sh required --send for the library profile (its default, and the last
|
||||||
# --profile wins, as there), and the current one refuses it. A2 would stop both units, and A4 and
|
# --profile wins, as there), and the current one refuses it. A2 would stop both units, and A4 and
|
||||||
# render-old would both fail on the same args, so a library file holding --send after -- refuses
|
# render-old would both fail on the same args, so a library file holding --send after -- refuses
|
||||||
@@ -148,6 +149,7 @@ read_args() {
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
[ "$copies" = 1 ] || refuse args-unreadable "$f needs exactly one --copy before --, not $copies"
|
[ "$copies" = 1 ] || refuse args-unreadable "$f needs exactly one --copy before --, not $copies"
|
||||||
|
ARGS_PROFILE="$profile"
|
||||||
if [ "$profile" = library ]; then
|
if [ "$profile" = library ]; then
|
||||||
for ((i = i + 1; i < ${#ARGS[@]}; i++)); do
|
for ((i = i + 1; i < ${#ARGS[@]}; i++)); do
|
||||||
[ "${ARGS[i]}" != --send ] ||
|
[ "${ARGS[i]}" != --send ] ||
|
||||||
@@ -165,6 +167,30 @@ read_args() {
|
|||||||
AS_USER=("$SUDO" -u "$S_USER" -H env "PATH=$(dirname "$NODE"):/usr/local/bin:/usr/bin:/bin")
|
AS_USER=("$SUDO" -u "$S_USER" -H env "PATH=$(dirname "$NODE"):/usr/local/bin:/usr/bin:/bin")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# #385 R3-F1: base case2-unit.sh also put --send in the library Console unit's ExecStart. The current
|
||||||
|
# one checks an installed unit's ExecStart against its own read-only command, so A4 and render-old
|
||||||
|
# would both refuse that unit after A2 stopped both. No reviewed migration exists, so a library host
|
||||||
|
# whose installed Console unit has a --send token after serve refuses here, before anything runs. A
|
||||||
|
# missing unit is read_old's to refuse.
|
||||||
|
legacy_console() {
|
||||||
|
local f="$UNIT_DIR/$CON_UNIT" line t past
|
||||||
|
local -a lines words
|
||||||
|
[ "$ARGS_PROFILE" = library ] && [ -f "$f" ] && [ ! -L "$f" ] || return 0
|
||||||
|
mapfile -t lines < "$f" || refuse units-unreadable "cannot read $f"
|
||||||
|
for line in "${lines[@]}"; do
|
||||||
|
[[ "$line" =~ ^[[:space:]]*ExecStart[[:space:]]*=(.*)$ ]] || continue
|
||||||
|
read -r -a words <<< "${BASH_REMATCH[1]}"
|
||||||
|
past=""
|
||||||
|
for t in "${words[@]}"; do
|
||||||
|
if [ -n "$past" ]; then
|
||||||
|
[ "$t" != --send ] ||
|
||||||
|
refuse console-unit-legacy "$f runs serve with --send, so it was rendered before #385, and stack-upgrade.sh cannot migrate it to the read-only library Console: the units are left running; stop and report (docs/guides/host-run-sheet.md section 7)"
|
||||||
|
elif [ "$t" = serve ]; then past=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
# RENDER_ARGS: the args file's lines with copy $1 in place of --copy's value.
|
# RENDER_ARGS: the args file's lines with copy $1 in place of --copy's value.
|
||||||
args_with() {
|
args_with() {
|
||||||
local i past=""
|
local i past=""
|
||||||
@@ -296,6 +322,7 @@ run_steps() {
|
|||||||
|
|
||||||
do_upgrade() {
|
do_upgrade() {
|
||||||
read_args
|
read_args
|
||||||
|
legacy_console
|
||||||
# Before A1: the bundle into the stack's user's clone, then the copy from it (I1).
|
# Before A1: the bundle into the stack's user's clone, then the copy from it (I1).
|
||||||
step fetch "${AS_USER[@]}" git -C "$CLONE" fetch -q "$BUNDLE" refs/heads/refactor:refs/remotes/origin/refactor ||
|
step fetch "${AS_USER[@]}" git -C "$CLONE" fetch -q "$BUNDLE" refs/heads/refactor:refs/remotes/origin/refactor ||
|
||||||
refuse fetch-failed "the stack's user could not fetch $BUNDLE into $CLONE"
|
refuse fetch-failed "the stack's user could not fetch $BUNDLE into $CLONE"
|
||||||
@@ -342,6 +369,7 @@ rehearse() {
|
|||||||
# check: the target is an older copy than this checkout, and no rehearsal.
|
# check: the target is an older copy than this checkout, and no rehearsal.
|
||||||
do_rollback() {
|
do_rollback() {
|
||||||
read_args
|
read_args
|
||||||
|
legacy_console
|
||||||
[ -x "$COPIES_DIR/$TO/scripts/mosaic" ] || refuse copy-missing "no $COPIES_DIR/$TO/scripts/mosaic"
|
[ -x "$COPIES_DIR/$TO/scripts/mosaic" ] || refuse copy-missing "no $COPIES_DIR/$TO/scripts/mosaic"
|
||||||
hostcheck
|
hostcheck
|
||||||
read_old
|
read_old
|
||||||
|
|||||||
Reference in new issue
Block a user