Restore live operator polish + formatting gate fixes (Codex) #28
7 changed files with 205 additions and 26 deletions
58
build.sh
58
build.sh
|
|
@ -944,6 +944,8 @@ install_live_npm_globals() {
|
|||
ln -sf "/opt/clawdie/npm-global/bin/$(basename "${_bin}")" "${MOUNT_POINT}/usr/local/bin/$(basename "${_bin}")"
|
||||
done
|
||||
|
||||
patch_live_pi_footer_hostname "${_live_prefix}"
|
||||
|
||||
# npm runs as root on the build host and writes everything as
|
||||
# root:wheel. configure_live_operator_session() used to do this
|
||||
# chown but ran BEFORE install_live_npm_globals (caller order at
|
||||
|
|
@ -973,6 +975,33 @@ install_live_npm_globals() {
|
|||
fi
|
||||
}
|
||||
|
||||
patch_live_pi_footer_hostname() {
|
||||
_npm_prefix="$1"
|
||||
_patch_script="${SCRIPT_DIR}/firstboot/patch-pi-footer-hostname.js"
|
||||
_patched=0
|
||||
|
||||
if [ ! -f "${_patch_script}" ]; then
|
||||
echo "ERROR: Pi footer patch script missing: ${_patch_script}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for _footer in \
|
||||
"${_npm_prefix}/lib/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js" \
|
||||
"${_npm_prefix}/lib/node_modules/@mariozechner/pi-coding-agent/dist/modes/interactive/components/footer.js"
|
||||
do
|
||||
[ -f "${_footer}" ] || continue
|
||||
echo " patch Pi footer hostname: ${_footer}"
|
||||
node "${_patch_script}" "${_footer}"
|
||||
node --check "${_footer}"
|
||||
_patched=1
|
||||
done
|
||||
|
||||
if [ "${_patched}" -ne 1 ]; then
|
||||
echo "ERROR: Pi footer.js not found under ${_npm_prefix}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
seed_live_ai_source_repo() {
|
||||
_repo_src="$1"
|
||||
_repo_name="$2"
|
||||
|
|
@ -1273,6 +1302,8 @@ export npm_config_prefix="${_clawdie_npm_prefix}"
|
|||
export NPM_CONFIG_PREFIX="${_clawdie_npm_prefix}"
|
||||
export NPM_CONFIG_UPDATE_NOTIFIER=false
|
||||
export NO_UPDATE_NOTIFIER=1
|
||||
export LANG="${LANG:-en_US.UTF-8}"
|
||||
export LC_ALL="${LC_ALL:-en_US.UTF-8}"
|
||||
if [ -z "${PATH:-}" ]; then
|
||||
PATH="${_clawdie_base_path}"
|
||||
else
|
||||
|
|
@ -1342,22 +1373,40 @@ if [ -n "${ZSH:-}" ] && [ -r "${ZSH}/oh-my-zsh.sh" ]; then
|
|||
source "${ZSH}/oh-my-zsh.sh"
|
||||
fi
|
||||
EOF
|
||||
cat > "${MOUNT_POINT}/home/clawdie/.tmux.conf" <<'EOF'
|
||||
# Clawdie operator tmux defaults.
|
||||
# Pi uses modified Enter/key chords; tmux must pass CSI-u extended keys or Pi
|
||||
# warns that modified Enter keys may not work.
|
||||
set -g extended-keys on
|
||||
set -g extended-keys-format csi-u
|
||||
set -g escape-time 10
|
||||
set -g mouse on
|
||||
set -g base-index 1
|
||||
setw -g pane-base-index 1
|
||||
set -g renumber-windows on
|
||||
EOF
|
||||
mkdir -p "${MOUNT_POINT}/usr/local/etc"
|
||||
cp "${MOUNT_POINT}/home/clawdie/.tmux.conf" "${MOUNT_POINT}/usr/local/etc/tmux.conf"
|
||||
cp "${MOUNT_POINT}/home/clawdie/.profile" "${MOUNT_POINT}/etc/skel/.profile"
|
||||
cp "${MOUNT_POINT}/home/clawdie/.bash_profile" "${MOUNT_POINT}/etc/skel/.bash_profile"
|
||||
cp "${MOUNT_POINT}/home/clawdie/.bashrc" "${MOUNT_POINT}/etc/skel/.bashrc"
|
||||
cp "${MOUNT_POINT}/home/clawdie/.zprofile" "${MOUNT_POINT}/etc/skel/.zprofile"
|
||||
cp "${MOUNT_POINT}/home/clawdie/.zshrc" "${MOUNT_POINT}/etc/skel/.zshrc"
|
||||
cp "${MOUNT_POINT}/home/clawdie/.tmux.conf" "${MOUNT_POINT}/etc/skel/.tmux.conf"
|
||||
chmod 0644 \
|
||||
"${MOUNT_POINT}/home/clawdie/.profile" \
|
||||
"${MOUNT_POINT}/home/clawdie/.bash_profile" \
|
||||
"${MOUNT_POINT}/home/clawdie/.bashrc" \
|
||||
"${MOUNT_POINT}/home/clawdie/.zprofile" \
|
||||
"${MOUNT_POINT}/home/clawdie/.zshrc" \
|
||||
"${MOUNT_POINT}/home/clawdie/.tmux.conf" \
|
||||
"${MOUNT_POINT}/usr/local/etc/tmux.conf" \
|
||||
"${MOUNT_POINT}/etc/skel/.profile" \
|
||||
"${MOUNT_POINT}/etc/skel/.bash_profile" \
|
||||
"${MOUNT_POINT}/etc/skel/.bashrc" \
|
||||
"${MOUNT_POINT}/etc/skel/.zprofile" \
|
||||
"${MOUNT_POINT}/etc/skel/.zshrc"
|
||||
"${MOUNT_POINT}/etc/skel/.zshrc" \
|
||||
"${MOUNT_POINT}/etc/skel/.tmux.conf"
|
||||
install -m 0755 "${LIVE_SESSION_DIR}/xprofile" \
|
||||
"${MOUNT_POINT}/home/clawdie/.xprofile"
|
||||
cat > "${MOUNT_POINT}/home/clawdie/.xinitrc" <<'EOF'
|
||||
|
|
@ -1373,6 +1422,13 @@ EOF
|
|||
"${MOUNT_POINT}/home/clawdie/.config/xfce4/xinitrc" \
|
||||
"${MOUNT_POINT}/etc/skel/.xinitrc" \
|
||||
"${MOUNT_POINT}/etc/skel/.config/xfce4/xinitrc"
|
||||
if [ -f "${MOUNT_POINT}/usr/local/etc/xdg/tumbler/tumbler.rc" ]; then
|
||||
mkdir -p "${MOUNT_POINT}/home/clawdie/.config/tumbler" "${MOUNT_POINT}/etc/skel/.config/tumbler"
|
||||
install -m 0644 "${MOUNT_POINT}/usr/local/etc/xdg/tumbler/tumbler.rc" \
|
||||
"${MOUNT_POINT}/home/clawdie/.config/tumbler/tumbler.rc"
|
||||
install -m 0644 "${MOUNT_POINT}/usr/local/etc/xdg/tumbler/tumbler.rc" \
|
||||
"${MOUNT_POINT}/etc/skel/.config/tumbler/tumbler.rc"
|
||||
fi
|
||||
chroot "$MOUNT_POINT" chown -R clawdie:clawdie /home/clawdie
|
||||
|
||||
mkdir -p "${MOUNT_POINT}/usr/local/share/applications"
|
||||
|
|
|
|||
|
|
@ -11,16 +11,16 @@
|
|||
|
||||
## Target machine
|
||||
|
||||
| Detail | Value |
|
||||
|---|---|
|
||||
| **Make / Model** | HPE ProLiant ML350p Gen8 tower |
|
||||
| **Serial** | `CZ22160QQY` |
|
||||
| **Product ID** | `646676-421` |
|
||||
| **Management** | iLO 4 (firmware 2.76 → needs 2.82 update) |
|
||||
| **iLO License** | Advanced (remote console + virtual media) |
|
||||
| **iLO IP** | `10.0.0.2` (dedicated iLO management port) |
|
||||
| **Server NICs** | 4× onboard GbE (MAC 9c:8e:99:4c:43:e6–e9) |
|
||||
| **Server IP** | DHCP from LAN port 1 (currently no OS booted) |
|
||||
| Detail | Value |
|
||||
| ---------------- | -------------------------------------------------- |
|
||||
| **Make / Model** | HPE ProLiant ML350p Gen8 tower |
|
||||
| **Serial** | `CZ22160QQY` |
|
||||
| **Product ID** | `646676-421` |
|
||||
| **Management** | iLO 4 (firmware 2.76 → needs 2.82 update) |
|
||||
| **iLO License** | Advanced (remote console + virtual media) |
|
||||
| **iLO IP** | `10.0.0.2` (dedicated iLO management port) |
|
||||
| **Server NICs** | 4× onboard GbE (MAC 9c:8e:99:4c:43:e6–e9) |
|
||||
| **Server IP** | DHCP from LAN port 1 (currently no OS booted) |
|
||||
| **iLO password** | Physical pull-tab tag on chassis (factory default) |
|
||||
|
||||
## Network layout (sanitised)
|
||||
|
|
@ -63,6 +63,7 @@ ipmitool -H 10.0.0.2 -U Administrator -P <tag-password> chassis power reset
|
|||
### Phase 2 — USB live boots on server
|
||||
|
||||
Once the ISO boots on the server hardware:
|
||||
|
||||
1. Server gets DHCP on its LAN port (visible in ARP)
|
||||
2. `colibri-daemon` starts, skills catalog loaded
|
||||
3. `service clawdie health` passes
|
||||
|
|
@ -106,6 +107,7 @@ Target: 2.82 (Aug 2023)
|
|||
Download: https://support.hpe.com/ → ProLiant ML350p Gen8 → Firmware → iLO 4
|
||||
|
||||
**Method A (from USB live):**
|
||||
|
||||
```sh
|
||||
# Upload firmware via iLO REST API
|
||||
curl -sk -u Administrator:<pw> -X POST \
|
||||
|
|
@ -114,6 +116,7 @@ curl -sk -u Administrator:<pw> -X POST \
|
|||
```
|
||||
|
||||
**Method B (via iLO web UI):**
|
||||
|
||||
1. Log into `https://10.0.0.2/`
|
||||
2. Administration → Firmware → Upload
|
||||
3. Select `ilo4_282.bin`, apply, iLO reboots (~2 min)
|
||||
|
|
@ -121,6 +124,7 @@ curl -sk -u Administrator:<pw> -X POST \
|
|||
## System ROM / BIOS
|
||||
|
||||
Check version after iLO login:
|
||||
|
||||
```sh
|
||||
curl -sk -u Administrator:<pw> https://10.0.0.2/xmldata?item=all | grep -i rom
|
||||
```
|
||||
|
|
@ -129,13 +133,13 @@ Likely needs update — Gen8 latest is 2019.05.00 (P79). Check HPE support.
|
|||
|
||||
## Required packages on ISO
|
||||
|
||||
| Package | Purpose |
|
||||
|---|---|
|
||||
| Package | Purpose |
|
||||
| ---------- | ------------------------------------------------ |
|
||||
| `ipmitool` | IPMI/BMC management (power, sensors, boot order) |
|
||||
| `freeipmi` | Alternative IPMI toolset (optional, heavier) |
|
||||
| `curl` | iLO REST API calls ✅ already included |
|
||||
| `openssl` | Certificate handling ✅ already included |
|
||||
| `python3` | Scripting + JSON ✅ already included |
|
||||
| `freeipmi` | Alternative IPMI toolset (optional, heavier) |
|
||||
| `curl` | iLO REST API calls ✅ already included |
|
||||
| `openssl` | Certificate handling ✅ already included |
|
||||
| `python3` | Scripting + JSON ✅ already included |
|
||||
|
||||
## Notes
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ ml350p build server (Poudriere)
|
|||
```
|
||||
|
||||
Benefits:
|
||||
|
||||
- Versioned packages with dependencies
|
||||
- Clean-room builds (no host contamination)
|
||||
- `pkg upgrade colibri` on deployed machines
|
||||
|
|
@ -158,6 +159,7 @@ sysutils/colibri/
|
|||
```
|
||||
|
||||
**Makefile** (Rust port pattern):
|
||||
|
||||
```makefile
|
||||
PORTNAME= colibri
|
||||
PORTVERSION= 0.0.1
|
||||
|
|
@ -205,6 +207,7 @@ service nginx start
|
|||
### 3.2 Client config
|
||||
|
||||
On ISO builds and deployed machines:
|
||||
|
||||
```sh
|
||||
# /usr/local/etc/pkg/repos/clawdie.conf
|
||||
clawdie: {
|
||||
|
|
@ -223,6 +226,7 @@ pkg -r ${MOUNT_POINT} install colibri
|
|||
```
|
||||
|
||||
This gives us:
|
||||
|
||||
- `colibri`
|
||||
- `colibri-daemon`
|
||||
- `colibri-tui`
|
||||
|
|
@ -270,6 +274,7 @@ zfs create zroot/bhyve/freebsd-test
|
|||
### 6.4 Test VMs
|
||||
|
||||
**FreeBSD ISO test VM** (boots clawdie-iso after each build):
|
||||
|
||||
```sh
|
||||
vm create -t freebsd iso-test
|
||||
vm install iso-test clawdie-iso.iso
|
||||
|
|
@ -278,12 +283,14 @@ vm start iso-test
|
|||
```
|
||||
|
||||
**Linux cross-compile test VM** (validates non-FreeBSD targets):
|
||||
|
||||
```sh
|
||||
vm create -t linux linux-test
|
||||
# → test colibri builds on Linux target
|
||||
```
|
||||
|
||||
**FreeBSD Poudriere test jail VM** (full pkg build validation):
|
||||
|
||||
```sh
|
||||
vm create -t freebsd freebsd-test
|
||||
# → clone poudriere setup, run bulk build as validation
|
||||
|
|
@ -301,15 +308,15 @@ vm-bhyve
|
|||
|
||||
## Timeline
|
||||
|
||||
| Step | Effort | Depends on |
|
||||
|---|---|---|
|
||||
| 1. Server provision (ZFS, base system) | ~1h | iLO password, ISO boots |
|
||||
| 2. Poudriere setup | ~30m | base system running |
|
||||
| 3. colibri port creation | ~1h | Poudriere running |
|
||||
| 4. First pkg build | ~30m (compile) | port ready |
|
||||
| 5. pkg repo + nginx | ~15m | packages built |
|
||||
| 6. ISO integration | ~15m | repo hosted |
|
||||
| 7. bhyve + test VMs | ~30m | base system + ZFS |
|
||||
| Step | Effort | Depends on |
|
||||
| -------------------------------------- | -------------- | ----------------------- |
|
||||
| 1. Server provision (ZFS, base system) | ~1h | iLO password, ISO boots |
|
||||
| 2. Poudriere setup | ~30m | base system running |
|
||||
| 3. colibri port creation | ~1h | Poudriere running |
|
||||
| 4. First pkg build | ~30m (compile) | port ready |
|
||||
| 5. pkg repo + nginx | ~15m | packages built |
|
||||
| 6. ISO integration | ~15m | repo hosted |
|
||||
| 7. bhyve + test VMs | ~30m | base system + ZFS |
|
||||
|
||||
**Total: ~4h** once iLO password is available.
|
||||
|
||||
|
|
|
|||
69
firstboot/patch-pi-footer-hostname.js
Executable file
69
firstboot/patch-pi-footer-hostname.js
Executable file
|
|
@ -0,0 +1,69 @@
|
|||
#!/usr/bin/env node
|
||||
// Patch the bundled Pi footer so operator sessions show the runtime hostname.
|
||||
// This is intentionally a post-install patch because the live image ships Pi as
|
||||
// an offline npm-global tarball rather than as source built in this repo.
|
||||
|
||||
const fs = require("node:fs");
|
||||
|
||||
const file = process.argv[2];
|
||||
if (!file) {
|
||||
console.error("usage: patch-pi-footer-hostname.js FOOTER_JS");
|
||||
process.exit(64);
|
||||
}
|
||||
|
||||
let src = fs.readFileSync(file, "utf8");
|
||||
|
||||
if (!src.includes('import { hostname } from "node:os";')) {
|
||||
if (src.includes('import { isAbsolute, relative, resolve, sep } from "node:path";\n')) {
|
||||
src = src.replace(
|
||||
'import { isAbsolute, relative, resolve, sep } from "node:path";\n',
|
||||
'import { isAbsolute, relative, resolve, sep } from "node:path";\nimport { hostname } from "node:os";\n',
|
||||
);
|
||||
} else {
|
||||
src = src.replace(/^(import .*?;\n)/, 'import { hostname } from "node:os";\n$1');
|
||||
}
|
||||
}
|
||||
|
||||
const oldPwdBlock = ` const pwdLine = truncateToWidth(theme.fg("dim", pwd), width, theme.fg("dim", "..."));
|
||||
const lines = [pwdLine, dimStatsLeft + dimRemainder];`;
|
||||
|
||||
const newPwdBlock = ` // Show runtime hostname on the right of the cwd line. Use the kernel
|
||||
// hostname instead of parsing FreeBSD rc.conf so Pi stays portable and
|
||||
// reflects changes made after boot.
|
||||
const host = hostname();
|
||||
const minPwdHostPadding = 2;
|
||||
let hostForLine = host;
|
||||
let hostWidth = visibleWidth(hostForLine);
|
||||
if (hostWidth > width) {
|
||||
hostForLine = truncateToWidth(hostForLine, width, "...");
|
||||
hostWidth = visibleWidth(hostForLine);
|
||||
}
|
||||
const availableForPwd = width - hostWidth - minPwdHostPadding;
|
||||
let pwdLine;
|
||||
if (hostForLine && availableForPwd > 0) {
|
||||
const pwdForLine = visibleWidth(pwd) > availableForPwd ? truncateToWidth(pwd, availableForPwd, "...") : pwd;
|
||||
const pwdWidth = visibleWidth(pwdForLine);
|
||||
const padding = " ".repeat(Math.max(minPwdHostPadding, width - pwdWidth - hostWidth));
|
||||
pwdLine = theme.fg("dim", pwdForLine) + padding + theme.fg("dim", hostForLine);
|
||||
}
|
||||
else if (hostForLine) {
|
||||
pwdLine = theme.fg("dim", hostForLine);
|
||||
}
|
||||
else {
|
||||
pwdLine = truncateToWidth(theme.fg("dim", pwd), width, theme.fg("dim", "..."));
|
||||
}
|
||||
const lines = [pwdLine, dimStatsLeft + dimRemainder];`;
|
||||
|
||||
if (!src.includes("const minPwdHostPadding = 2;")) {
|
||||
if (!src.includes(oldPwdBlock)) {
|
||||
throw new Error(`Pi footer cwd block not found in ${file}`);
|
||||
}
|
||||
src = src.replace(oldPwdBlock, newPwdBlock);
|
||||
}
|
||||
|
||||
if (!src.includes('lines.push("");\n return lines;')) {
|
||||
src = src.replace(' return lines;\n', ' lines.push("");\n return lines;\n');
|
||||
}
|
||||
|
||||
fs.writeFileSync(file, src);
|
||||
console.log(`patched ${file}`);
|
||||
|
|
@ -84,11 +84,44 @@ clawdie_shell_npm_globals_install() {
|
|||
return 0
|
||||
fi
|
||||
|
||||
clawdie_shell_npm_globals_patch_pi_footer
|
||||
|
||||
echo "[NPM-GLOBALS] SUCCESS" >> "$PROGRESS_FILE"
|
||||
log_msg "[npm-globals] Install complete"
|
||||
return 0
|
||||
}
|
||||
|
||||
clawdie_shell_npm_globals_patch_pi_footer() {
|
||||
_patch_script="${SHARE}/firstboot/patch-pi-footer-hostname.js"
|
||||
_patched=0
|
||||
|
||||
if [ ! -f "$_patch_script" ]; then
|
||||
log_msg "[npm-globals] ERROR: Pi footer patch script missing: $_patch_script"
|
||||
return 1
|
||||
fi
|
||||
|
||||
for footer in \
|
||||
"$NPM_PREFIX/lib/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js" \
|
||||
"$NPM_PREFIX/lib/node_modules/@mariozechner/pi-coding-agent/dist/modes/interactive/components/footer.js"
|
||||
do
|
||||
[ -f "$footer" ] || continue
|
||||
log_msg "[npm-globals] Patching Pi footer hostname: $footer"
|
||||
if node "$_patch_script" "$footer" >>"$LOG_FILE" 2>&1 && node --check "$footer" >>"$LOG_FILE" 2>&1; then
|
||||
_patched=1
|
||||
else
|
||||
log_msg "[npm-globals] ERROR: failed to patch Pi footer hostname"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$_patched" -ne 1 ]; then
|
||||
log_msg "[npm-globals] ERROR: Pi footer.js not found under $NPM_PREFIX"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# LOGGING HELPER
|
||||
# ============================================================================
|
||||
|
|
|
|||
|
|
@ -444,8 +444,14 @@ if [ -n "${ZSH:-}" ] && [ -r "${ZSH}/oh-my-zsh.sh" ]; then
|
|||
source "${ZSH}/oh-my-zsh.sh"
|
||||
fi
|
||||
EOF
|
||||
if [ -f /usr/local/etc/xdg/tumbler/tumbler.rc ]; then
|
||||
mkdir -p /home/clawdie/.config/tumbler
|
||||
cp /usr/local/etc/xdg/tumbler/tumbler.rc /home/clawdie/.config/tumbler/tumbler.rc
|
||||
fi
|
||||
chown -R clawdie:clawdie /home/clawdie/.config/tumbler 2>/dev/null || true
|
||||
chown clawdie:clawdie /home/clawdie/.profile /home/clawdie/.bash_profile /home/clawdie/.bashrc /home/clawdie/.zprofile /home/clawdie/.zshrc 2>/dev/null || true
|
||||
chmod 644 /home/clawdie/.profile /home/clawdie/.bash_profile /home/clawdie/.bashrc /home/clawdie/.zprofile /home/clawdie/.zshrc 2>/dev/null || true
|
||||
chmod 644 /home/clawdie/.config/tumbler/tumbler.rc 2>/dev/null || true
|
||||
fi
|
||||
|
||||
log_msg "[system] Created $clawdie_profile"
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@ bastille
|
|||
# and Micro for friendly terminal/SSH edits without pulling an Electron IDE.
|
||||
geany
|
||||
micro
|
||||
# Pi/operator terminal search helpers. Keep these in the live image, not only
|
||||
# the host package baseline, because Pi and debugging run directly from XFCE.
|
||||
ripgrep
|
||||
fd-find
|
||||
|
||||
# Vulkan diagnostics. Zed is deferred off the live rootfs until Intel+AMD
|
||||
# Vulkan are proven, but vulkaninfo is tiny and gives Pi Builder a direct
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue