Test staged-env shell-quoting helpers; rescope ISO priority 1 to boot validation #66
2 changed files with 109 additions and 49 deletions
|
|
@ -966,3 +966,73 @@ mod jail_tests {
|
|||
assert!(err.to_string().contains("requires JailConfig.root_path"));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod staged_env_tests {
|
||||
use super::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
fn shell_single_quote_plain() {
|
||||
assert_eq!(shell_single_quote("bar"), "'bar'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_single_quote_escapes_single_quote() {
|
||||
// a'b -> 'a'"'"'b' : close quote, escaped quote, reopen quote
|
||||
assert_eq!(shell_single_quote("a'b"), r#"'a'"'"'b'"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_single_quote_neutralizes_metachars() {
|
||||
// Shell metacharacters stay literal inside the single quotes.
|
||||
let payload = "$(rm -rf /); `id`; a&&b || c; $HOME";
|
||||
let quoted = shell_single_quote(payload);
|
||||
assert!(quoted.starts_with('\''));
|
||||
assert!(quoted.ends_with('\''));
|
||||
// No unescaped single quote ever appears, so the shell cannot break out.
|
||||
assert_eq!(quoted, format!("'{payload}'"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_env_key_accepts_good_keys() {
|
||||
for key in ["PATH", "_X", "A1", "COLIBRI_FOO", "_"] {
|
||||
assert!(valid_env_key(key), "expected {key} to be valid");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_env_key_rejects_bad_keys() {
|
||||
for key in ["", "1A", "A-B", "A B", "A.B", "A=B"] {
|
||||
assert!(!valid_env_key(key), "expected {key:?} to be rejected");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_env_script_sorts_and_quotes() {
|
||||
let mut env = HashMap::new();
|
||||
env.insert("FOO".to_string(), "bar".to_string());
|
||||
env.insert("BAZ".to_string(), "x y".to_string());
|
||||
let script = build_env_script(&env).unwrap();
|
||||
assert_eq!(script, "export BAZ='x y'\nexport FOO='bar'\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_env_script_escapes_values() {
|
||||
let mut env = HashMap::new();
|
||||
env.insert("TOKEN".to_string(), "a'b".to_string());
|
||||
let script = build_env_script(&env).unwrap();
|
||||
assert_eq!(
|
||||
script,
|
||||
format!("export TOKEN={}\n", shell_single_quote("a'b"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_env_script_rejects_invalid_key() {
|
||||
let mut env = HashMap::new();
|
||||
env.insert("BAD KEY".to_string(), "v".to_string());
|
||||
let err = build_env_script(&env).unwrap_err();
|
||||
assert!(err.to_string().contains("invalid env key"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,53 +14,39 @@ the final step. Items can be worked in parallel by different agents.
|
|||
|
||||
---
|
||||
|
||||
## Priority 1: Wire the ISO staging script into the clawdie-iso build
|
||||
## Priority 1: Validate the staged colibri_daemon boots and runs on the ISO
|
||||
|
||||
### Why this is #1
|
||||
|
||||
`scripts/stage-colibri-iso.sh` already exists and copies binaries, rc.d,
|
||||
directories, and a sample rc.conf into a DESTDIR. But it has **never been run
|
||||
against the actual ISO image root**, and the clawdie-iso build process does
|
||||
not call it. Without this wiring, colibri_daemon cannot boot on the ISO, which
|
||||
blocks Gate 1 (passive service) entirely.
|
||||
The build-side wiring is **done**. The clawdie-iso build now stages the
|
||||
Colibri binaries, installs the rc.d script, creates the `colibri` user, and
|
||||
enables the service. What has **not** happened yet is booting a freshly built
|
||||
image and confirming `colibri_daemon` actually starts and the acceptance
|
||||
runbook passes on real FreeBSD. Until that boot/runtime validation is done,
|
||||
Gate 1 (passive service) is unproven.
|
||||
|
||||
### What exists
|
||||
### What's done (build wiring)
|
||||
|
||||
| Artifact | Location | Status |
|
||||
| ------------------ | ------------------------------------------ | ----------------------------------------------------------------------------------------------- |
|
||||
| staging script | `scripts/stage-colibri-iso.sh` | done — copies `colibri-daemon`, `colibri`, `colibri-smoke-agent`, rc.d, newsyslog, creates dirs |
|
||||
| rc.d script | `packaging/freebsd/colibri_daemon.in` | done — `start_precmd`, pidfile, daemon(8) wrapper, `COLIBRI_COST_MODE` propagation |
|
||||
| newsyslog config | `packaging/freebsd/newsyslog-colibri.conf` | done |
|
||||
| rc.conf.sample | generated by staging script | done |
|
||||
| acceptance runbook | `docs/ISO-ACCEPTANCE-RUNBOOK.md` | done |
|
||||
| Artifact / step | Location | Status |
|
||||
| -------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------- |
|
||||
| staging script | `scripts/stage-colibri-iso.sh` | done — copies `colibri-daemon`, `colibri`, `colibri-smoke-agent`, rc.d, newsyslog, creates dirs |
|
||||
| rc.d script | `packaging/freebsd/colibri_daemon.in` | done — `start_precmd`, pidfile, daemon(8) wrapper, `COLIBRI_COST_MODE` propagation |
|
||||
| newsyslog config | `packaging/freebsd/newsyslog-colibri.conf` | done |
|
||||
| rc.conf.sample | generated by staging script | done |
|
||||
| acceptance runbook | `docs/ISO-ACCEPTANCE-RUNBOOK.md` | done |
|
||||
| build integration | clawdie-iso `build.sh::install_colibri_service` | done — calls `stage-colibri-iso.sh` against the image root |
|
||||
| `colibri` user/group | clawdie-iso `build.sh` (`pw useradd colibri`) | done — created in the image during build |
|
||||
| service enable | clawdie-iso `build.sh` (`colibri_daemon_enable`) | done — written into image rc.conf |
|
||||
| prebuilt binaries | build-host Rust toolchain (preflight-gated) | done — `build.sh` stages prebuilt release binaries and fails preflight if missing |
|
||||
|
||||
### What's missing
|
||||
### What's missing (boot/runtime validation)
|
||||
|
||||
1. **`colibri` user/group creation in the image.**
|
||||
The staging script documents this as a manual step in
|
||||
`ETC_DIR/README.iso` but does not create the user. Options:
|
||||
- Add `pw useradd`/`pw groupadd` to the staging script (requires root, or
|
||||
runs during image build which has root)
|
||||
- Add it to the clawdie-iso firstboot wizard
|
||||
- Add it as an `etc/passwd`/`etc/group` entry in the image root
|
||||
1. **Boot a freshly built image on FreeBSD** (bhyve VM or hardware) and confirm
|
||||
the `colibri` user, binaries, rc.d script, and rc.conf entry are present in
|
||||
the running system.
|
||||
|
||||
2. **clawdie-iso build integration.**
|
||||
The clawdie-iso build process needs to:
|
||||
- Cross-compile (or pre-build) `colibri-daemon` for `x86_64-unknown-freebsd`
|
||||
- Call `stage-colibri-iso.sh` against the image root
|
||||
- Merge `rc.conf.sample` into `/etc/rc.conf.d/colibri_daemon` or `/etc/rc.conf`
|
||||
- Ensure the `colibri` user exists in `/etc/passwd` and `/etc/group` in the image
|
||||
2. **Run the acceptance runbook on the booted image:**
|
||||
|
||||
3. **Pre-built FreeBSD binaries.**
|
||||
Linux agents cannot produce FreeBSD binaries directly (no cross-compile
|
||||
target in the workspace). Either:
|
||||
- The FreeBSD agent (Codex) builds `cargo build --workspace --release` on
|
||||
FreeBSD and the ISO build consumes `target/release/*.bin`
|
||||
- A CI step on a FreeBSD runner produces the artifacts
|
||||
- The ISO build host has Rust installed and builds in-place
|
||||
|
||||
4. **Verification on FreeBSD.**
|
||||
After staging, run the acceptance runbook commands on the booted image:
|
||||
```sh
|
||||
service colibri_daemon start
|
||||
colibri status
|
||||
|
|
@ -72,19 +58,23 @@ blocks Gate 1 (passive service) entirely.
|
|||
service colibri_daemon stop
|
||||
```
|
||||
|
||||
3. **Confirm logging + lifecycle:** pidfile is created, newsyslog rotation
|
||||
config is in place, and `service colibri_daemon stop` cleanly stops the
|
||||
daemon and removes the pidfile.
|
||||
|
||||
### Key files
|
||||
|
||||
- `scripts/stage-colibri-iso.sh` — the staging script (line 44-93: dir creation, bin copy, rc.d install, rc.conf.sample generation)
|
||||
- `scripts/stage-colibri-iso.sh` — the staging script (dir creation, bin copy, rc.d install, rc.conf.sample generation)
|
||||
- `packaging/freebsd/colibri_daemon.in` — rc.d script
|
||||
- `docs/ISO-ACCEPTANCE-RUNBOOK.md` — acceptance commands
|
||||
- `docs/ISO-ACCEPTANCE-RUNBOOK.md` — acceptance commands to run on the booted image
|
||||
- `docs/ISO-INTEGRATION-PLAN.md` §Lane A — full plan with gap audit
|
||||
- clawdie-iso repo — image build scripts that need to call the staging script
|
||||
- clawdie-iso `build.sh` — `install_colibri_service()` already wires staging, user creation, and service enable
|
||||
|
||||
### Suggested owner
|
||||
|
||||
ISO/build lane (Codex on FreeBSD for binary production + Sam for build
|
||||
integration). Linux agents can prepare the staging script changes and
|
||||
clawdie-iso build wiring.
|
||||
ISO/build lane — FreeBSD agent (Codex) or Sam boots a built image and runs the
|
||||
acceptance runbook. No Linux-side code change is required; this is a
|
||||
runtime-proof step.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -284,11 +274,11 @@ wiring, no platform-specific behavior.
|
|||
|
||||
## Summary table
|
||||
|
||||
| # | Item | Blocks | Linux-doable | Effort |
|
||||
| --- | --------------------- | ------------------- | ---------------------------------- | ------ |
|
||||
| 1 | ISO staging wiring | Gate 1 | partially (needs FreeBSD binaries) | medium |
|
||||
| 2 | Pi spawn end-to-end | Gate 2 | yes (with fake-pi-agent.py) | medium |
|
||||
| 3 | Cost mode enforcement | core design promise | yes (pure logic) | medium |
|
||||
| # | Item | Blocks | Linux-doable | Effort |
|
||||
| --- | --------------------------- | ------------------- | --------------------------- | ------ |
|
||||
| 1 | ISO boot/runtime validation | Gate 1 | no (needs FreeBSD boot) | small |
|
||||
| 2 | Pi spawn end-to-end | Gate 2 | yes (with fake-pi-agent.py) | medium |
|
||||
| 3 | Cost mode enforcement | core design promise | yes (pure logic) | medium |
|
||||
|
||||
All three are medium effort and can be worked in parallel. None require
|
||||
FreeBSD to implement — only to validate the final result.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue