Harden FreeBSD guest /etc and activation diagnostics

This commit is contained in:
2026-04-02 22:45:34 +02:00
parent a04e650326
commit 901d0a8448
7 changed files with 392 additions and 7 deletions

View File

@@ -2711,3 +2711,102 @@ Next recommended step:
1. continue with Phase 12.2 and tighten the guest-side runtime/operator diagnostics 1. continue with Phase 12.2 and tighten the guest-side runtime/operator diagnostics
2. remove or reduce the most distracting remaining boot/runtime rough edges where the fixes are small and local 2. remove or reduce the most distracting remaining boot/runtime rough edges where the fixes are small and local
3. keep the deployment path stable so Phase 13 can start from a sharper baseline 3. keep the deployment path stable so Phase 13 can start from a sharper baseline
## 2026-04-02 — Phase 12.2: guest runtime diagnostics tightened and `/etc` handling improved
Completed work:
- wrote the Phase 12.2 report:
- `docs/reports/phase12-runtime-diagnostics-freebsd.md`
- updated `modules/fruix/system/freebsd.scm` so selected database-backed `/etc` files are now materialized as regular files in the guest rootfs instead of symlinks:
- `/etc/passwd`
- `/etc/master.passwd`
- `/etc/group`
- `/etc/login.conf`
- the generated activation script now refreshes those files from `/run/current-system/etc` before rebuilding FreeBSD databases
- activation now writes a guest-visible log:
- `/var/log/fruix-activate.log`
- with markers including:
- `fruix-activate:start`
- `fruix-activate:cap_mkdb=ok`
- `fruix-activate:pwd_mkdb=ok`
- `fruix-activate:done`
- exit status marker via shell trap
- tightened closure permissions slightly by making:
- `etc/master.passwd`
mode `0600`
- upgraded validation harnesses so they now assert the improved runtime behavior directly:
- `tests/system/run-phase8-system-image.sh`
- now checks that image `/etc/login.conf` is a regular file
- now checks that image `/etc/master.passwd` is a regular file
- `tests/system/run-phase9-xcpng-boot.sh`
- `tests/system/run-phase11-shepherd-pid1-xcpng.sh`
- `tests/system/run-phase11-shepherd-pid1-qemu.sh`
- now check for:
- `login_conf_kind=regular`
- `login_conf_db=present`
- `pwd_dbs=present`
- activation log completion marker
- fixed a small follow-up bug in the activation log path:
- initial implementation used `touch`, which is not staged in the minimal guest
- switched to shell redirection instead:
- `: >> "$logfile"`
Validation:
- `tests/system/run-phase8-system-image.sh` passes locally with the new image-layout checks:
- workdir: `/tmp/phase12-2-image-1775159011`
- confirmed:
- `login_conf_kind=regular`
- `master_passwd_kind=regular`
- `tests/system/run-phase11-shepherd-pid1-qemu.sh` passes locally again with the new activation/runtime checks:
- workdir: `/tmp/phase12-2b-qemu-1775161367`
- confirmed:
- `activate_log=fruix-activate:start ... fruix-activate:done ...`
- `login_conf_kind=regular`
- `login_conf_db=present`
- `pwd_dbs=present`
- `shepherd_pid=1`
- `sshd_status=running`
- `tests/system/run-phase9-xcpng-boot.sh` passes on the real VM with the new checks:
- workdir: `/tmp/phase12-2b-phase9-1775161731`
- confirmed:
- `activate_log=fruix-activate:start ... fruix-activate:done ...`
- `login_conf_kind=regular`
- `login_conf_db=present`
- `pwd_dbs=present`
- `compat_prefix_shims=absent`
- `guile_module_smoke=ok`
- `shepherd_status=running`
- `sshd_status=running`
- `tests/system/run-phase11-shepherd-pid1-xcpng.sh` passes on the real VM with the new checks:
- workdir: `/tmp/phase12-2b-phase11-1775162210`
- confirmed:
- `activate_log=fruix-activate:start ... fruix-activate:done ...`
- `login_conf_kind=regular`
- `login_conf_db=present`
- `pwd_dbs=present`
- `compat_prefix_shims=absent`
- `guile_module_smoke=ok`
- `shepherd_pid=1`
- `sshd_status=running`
Important findings:
- the old symlink-based handling for login/password database inputs was a real mismatch with FreeBSD expectations; making those files regular in the guest was a better fit than leaving them store-backed symlinks
- adding a direct activation log materially improves post-boot diagnosis and avoids guessing whether activation actually completed
- the first attempt exposed a missing-userland dependency (`touch`) quickly; because the new diagnostics were explicit, the follow-up fix was immediate and local
- both validated boot paths still hold after this change:
- `freebsd-init+rc.d-shepherd`
- `shepherd-pid1`
Current assessment:
- the current Fruix guest remains intentionally minimal, but its runtime behavior is now less prototype-noisy and easier to inspect as a basic FreeBSD-like system
- this is exactly the kind of targeted hardening that makes the existing system a better launch point for native FreeBSD base-build work
Next recommended step:
1. complete Phase 12.3 by making the host-staged FreeBSD base boundary explicit in the package/model layer and docs
2. document the first intended replacement order for native world/kernel work
3. then begin Phase 13 with a clearer transitional boundary

View File

@@ -0,0 +1,210 @@
# Phase 12.2: tightened guest runtime diagnostics and reduced early-boot rough edges
Date: 2026-04-02
## Goal
The current Fruix FreeBSD guest already booted, reached the network, and ran Shepherd. The next hardening step was to make the guest easier to diagnose and to remove a few distracting runtime rough edges that were still coming from the prototype-style `/etc` handling.
The main targets were:
- reduce early-boot/login-class noise around `/etc/login.conf`
- ensure the FreeBSD password/login databases are created in writable guest state, not implicitly in store-backed paths
- add a clear activation log inside the guest
- teach the validation harnesses to assert the new behavior directly
## Root cause addressed
The old rootfs layout symlinked several generated `/etc` files directly to `/run/current-system/etc/...`, including:
- `/etc/login.conf`
- `/etc/master.passwd`
- `/etc/passwd`
- `/etc/group`
That worked for static lookup, but it was a poor fit for FreeBSD tools such as:
- `cap_mkdb`
- `pwd_mkdb`
- early login-class lookups
because those tools expect to work with regular files and adjacent generated database files such as:
- `/etc/login.conf.db`
- `/etc/pwd.db`
- `/etc/spwd.db`
With symlink-backed inputs, the system could still limp along, but it produced avoidable warnings and less clear runtime behavior.
## Implementation
### 1. Selected `/etc` files now become regular files in the guest rootfs
`modules/fruix/system/freebsd.scm` now materializes these files as regular files in the rootfs instead of symlinks:
- `/etc/passwd`
- `/etc/master.passwd`
- `/etc/group`
- `/etc/login.conf`
Other generated configuration files that do not need this treatment remain symlinked to `/run/current-system/...`.
This gives FreeBSD's database tools writable, regular inputs in the guest filesystem while keeping the current system closure as the source of truth.
### 2. Activation now refreshes those files from `/run/current-system/etc`
The generated activation script now refreshes the regular guest copies from the currently selected system closure before rebuilding databases.
That means rebuild/redeploy still works coherently even though these specific files are no longer left as symlinks in the booted guest.
### 3. Activation now records a guest-visible log
Activation now writes:
- `/var/log/fruix-activate.log`
with explicit markers such as:
- `fruix-activate:start`
- `fruix-activate:cap_mkdb=ok`
- `fruix-activate:pwd_mkdb=ok`
- `fruix-activate:done`
- exit status marker from the shell trap
This gives a direct guest-side indicator of whether activation actually completed.
### 4. Closure permissions were tightened slightly
The generated closure now explicitly sets:
- `etc/master.passwd` => `0600`
before the rootfs/image path copies it into the guest.
### 5. Validation harnesses were upgraded
#### Local image validation
`tests/system/run-phase8-system-image.sh` now asserts that the mounted image contains:
- `/etc/login.conf` as a regular file
- `/etc/master.passwd` as a regular file
#### VM validation
These harnesses now check for the new runtime behavior:
- `tests/system/run-phase9-xcpng-boot.sh`
- `tests/system/run-phase11-shepherd-pid1-xcpng.sh`
- `tests/system/run-phase11-shepherd-pid1-qemu.sh`
New checks include:
- `login_conf_kind=regular`
- `login_conf_db=present`
- `pwd_dbs=present`
- activation log contains `fruix-activate:done`
They also capture the activation log into metadata.
### 6. Small follow-up fix
The first activation-log implementation briefly used `touch`, which is not staged in the minimal guest userland. This was corrected by switching to shell redirection:
- `: >> "$logfile"`
so the activation path no longer depends on an extra utility for log-file creation.
## Validation
### Local image structure
Passing run:
- `PASS phase8-system-image`
- workdir: `/tmp/phase12-2-image-1775159011`
Key metadata:
```text
login_conf_kind=regular
master_passwd_kind=regular
```
### Local QEMU Shepherd PID 1
Passing run:
- `PASS phase11-shepherd-pid1-qemu`
- workdir: `/tmp/phase12-2b-qemu-1775161367`
Key metadata:
```text
activate_log=fruix-activate:start fruix-activate:cap_mkdb=ok fruix-activate:pwd_mkdb=ok fruix-activate:done fruix-activate:exit status=0
login_conf_kind=regular
login_conf_db=present
pwd_dbs=present
shepherd_pid=1
sshd_status=running
```
### Real VM, `freebsd-init+rc.d-shepherd`
Passing run:
- `PASS phase9-xcpng-boot`
- workdir: `/tmp/phase12-2b-phase9-1775161731`
Key metadata:
```text
activate_log=fruix-activate:start fruix-activate:cap_mkdb=ok fruix-activate:pwd_mkdb=ok fruix-activate:done fruix-activate:exit status=0
login_conf_kind=regular
login_conf_db=present
pwd_dbs=present
compat_prefix_shims=absent
guile_module_smoke=ok
shepherd_status=running
sshd_status=running
```
### Real VM, `shepherd-pid1`
Passing run:
- `PASS phase11-shepherd-pid1-xcpng`
- workdir: `/tmp/phase12-2b-phase11-1775162210`
Key metadata:
```text
activate_log=fruix-activate:start fruix-activate:cap_mkdb=ok fruix-activate:pwd_mkdb=ok fruix-activate:done fruix-activate:exit status=0
login_conf_kind=regular
login_conf_db=present
pwd_dbs=present
shepherd_pid=1
compat_prefix_shims=absent
guile_module_smoke=ok
sshd_status=running
```
## Assessment
This was a small but high-value hardening step.
The guest now behaves more like a real FreeBSD system in one of the places where a store-backed prototype can otherwise feel awkward: password/login database management and early `/etc` expectations.
The important result is not cosmetic; it is operational:
- activation success is now visible inside the guest
- the login/password database inputs live as regular guest files where FreeBSD expects them
- both validated boot modes still work locally and on the real XCP-ng VM
## Next recommended step
Proceed to Phase 12.3:
- make the host-staged FreeBSD base boundary explicit in the code/documentation model
- group the transitional host-copy base packages more clearly
- document the first intended replacement order for native world/kernel artifacts in `/frx/store`

View File

@@ -657,7 +657,18 @@
uid gid home))))) uid gid home)))))
users) users)
"")) ""))
(compat-prefixes "") (refresh-db-input-files
(string-join
(map (lambda (entry)
(match entry
((name mode)
(string-append
"if [ -f /run/current-system/etc/" name " ]; then rm -f /etc/" name "; cp /run/current-system/etc/" name " /etc/" name "; chmod " mode " /etc/" name "; fi\n"))))
'(("passwd" "0644")
("master.passwd" "0600")
("group" "0644")
("login.conf" "0644")))
""))
(ssh-section (ssh-section
(string-append (string-append
"mkdir -p /var/empty /etc/ssh /root/.ssh\n" "mkdir -p /var/empty /etc/ssh /root/.ssh\n"
@@ -671,13 +682,22 @@
(string-append (string-append
"#!/bin/sh\n" "#!/bin/sh\n"
"set -eu\n" "set -eu\n"
"logfile=/var/log/fruix-activate.log\n"
"mkdir -p /var/cron /var/db /var/lib/fruix /var/log /var/run /root /home /tmp\n" "mkdir -p /var/cron /var/db /var/lib/fruix /var/log /var/run /root /home /tmp\n"
": >> \"$logfile\"\n"
"trap 'status=$?; echo \"fruix-activate:exit status=$status\" >> \"$logfile\"' EXIT\n"
"echo \"fruix-activate:start\" >> \"$logfile\"\n"
"chmod 1777 /tmp\n" "chmod 1777 /tmp\n"
"if [ -x /usr/bin/cap_mkdb ] && [ -f /etc/login.conf ]; then /usr/bin/cap_mkdb /etc/login.conf || true; fi\n" refresh-db-input-files
"if [ -x /usr/sbin/pwd_mkdb ] && [ -f /etc/master.passwd ]; then /usr/sbin/pwd_mkdb -p /etc/master.passwd || true; fi\n" "if [ -x /usr/bin/cap_mkdb ] && [ -f /etc/login.conf ]; then\n"
" if /usr/bin/cap_mkdb /etc/login.conf; then echo \"fruix-activate:cap_mkdb=ok\" >> \"$logfile\"; else echo \"fruix-activate:cap_mkdb=failed\" >> \"$logfile\"; fi\n"
"fi\n"
"if [ -x /usr/sbin/pwd_mkdb ] && [ -f /etc/master.passwd ]; then\n"
" if /usr/sbin/pwd_mkdb -p /etc/master.passwd; then echo \"fruix-activate:pwd_mkdb=ok\" >> \"$logfile\"; else echo \"fruix-activate:pwd_mkdb=failed\" >> \"$logfile\"; fi\n"
"fi\n"
home-setup home-setup
compat-prefixes ssh-section
ssh-section))) "echo \"fruix-activate:done\" >> \"$logfile\"\n")))
(define (pid1-mount-commands os) (define (pid1-mount-commands os)
(string-join (string-join
@@ -1131,6 +1151,8 @@
(write-file (string-append closure-path "/" (car entry)) (cdr entry))) (write-file (string-append closure-path "/" (car entry)) (cdr entry)))
generated-files) generated-files)
(chmod (string-append closure-path "/activate") #o555) (chmod (string-append closure-path "/activate") #o555)
(when (file-exists? (string-append closure-path "/etc/master.passwd"))
(chmod (string-append closure-path "/etc/master.passwd") #o600))
(chmod (string-append closure-path "/usr/local/etc/rc.d/fruix-activate") #o555) (chmod (string-append closure-path "/usr/local/etc/rc.d/fruix-activate") #o555)
(chmod (string-append closure-path "/usr/local/etc/rc.d/fruix-shepherd") #o555) (chmod (string-append closure-path "/usr/local/etc/rc.d/fruix-shepherd") #o555)
(when (file-exists? (string-append closure-path "/boot/fruix-pid1")) (when (file-exists? (string-append closure-path "/boot/fruix-pid1"))
@@ -1202,8 +1224,11 @@
(for-each (lambda (path) (for-each (lambda (path)
(symlink-force (string-append "/run/current-system/etc/" path) (symlink-force (string-append "/run/current-system/etc/" path)
(string-append rootfs "/etc/" path))) (string-append rootfs "/etc/" path)))
'("rc.conf" "fstab" "hosts" "passwd" "master.passwd" "group" '("rc.conf" "fstab" "hosts" "shells" "motd" "ttys"))
"login.conf" "shells" "motd" "ttys")) (for-each (lambda (path)
(copy-regular-file (string-append closure-path "/etc/" path)
(string-append rootfs "/etc/" path)))
'("passwd" "master.passwd" "group" "login.conf"))
(when (file-exists? (string-append closure-path "/etc/ssh/sshd_config")) (when (file-exists? (string-append closure-path "/etc/ssh/sshd_config"))
(symlink-force "/run/current-system/etc/ssh/sshd_config" (symlink-force "/run/current-system/etc/ssh/sshd_config"
(string-append rootfs "/etc/ssh/sshd_config"))) (string-append rootfs "/etc/ssh/sshd_config")))

View File

@@ -113,6 +113,10 @@ logger_log=$(ssh_guest 'cat /var/log/fruix-shepherd.log' | tr '\n' ' ')
shepherd_bootstrap_tail=$(ssh_guest "awk 'BEGIN { c = 20 } { lines[NR % c] = \$0 } END { start = (NR > c ? NR - c + 1 : 1); for (i = start; i <= NR; i++) print lines[i % c] }' /var/log/shepherd-bootstrap.out 2>/dev/null || true" | tr '\n' ' ') shepherd_bootstrap_tail=$(ssh_guest "awk 'BEGIN { c = 20 } { lines[NR % c] = \$0 } END { start = (NR > c ? NR - c + 1 : 1); for (i = start; i <= NR; i++) print lines[i % c] }' /var/log/shepherd-bootstrap.out 2>/dev/null || true" | tr '\n' ' ')
shepherd_log_tail=$(ssh_guest "awk 'BEGIN { c = 20 } { lines[NR % c] = \$0 } END { start = (NR > c ? NR - c + 1 : 1); for (i = start; i <= NR; i++) print lines[i % c] }' /var/log/shepherd.log 2>/dev/null || true" | tr '\n' ' ') shepherd_log_tail=$(ssh_guest "awk 'BEGIN { c = 20 } { lines[NR % c] = \$0 } END { start = (NR > c ? NR - c + 1 : 1); for (i = start; i <= NR; i++) print lines[i % c] }' /var/log/shepherd.log 2>/dev/null || true" | tr '\n' ' ')
guest_dmesg_tail=$(ssh_guest "dmesg | awk 'BEGIN { c = 20 } { lines[NR % c] = \$0 } END { start = (NR > c ? NR - c + 1 : 1); for (i = start; i <= NR; i++) print lines[i % c] }'" | tr '\n' ' ') guest_dmesg_tail=$(ssh_guest "dmesg | awk 'BEGIN { c = 20 } { lines[NR % c] = \$0 } END { start = (NR > c ? NR - c + 1 : 1); for (i = start; i <= NR; i++) print lines[i % c] }'" | tr '\n' ' ')
activate_log=$(ssh_guest 'cat /var/log/fruix-activate.log 2>/dev/null || true' | tr '\n' ' ')
login_conf_kind=$(ssh_guest 'if [ -L /etc/login.conf ]; then echo symlink; elif [ -f /etc/login.conf ]; then echo regular; else echo missing; fi')
login_conf_db=$(ssh_guest 'test -f /etc/login.conf.db && echo present || echo missing')
pwd_dbs=$(ssh_guest 'if [ -f /etc/pwd.db ] && [ -f /etc/spwd.db ]; then echo present; else echo missing; fi')
uname_output=$(ssh_guest 'uname -sr') uname_output=$(ssh_guest 'uname -sr')
operator_home_listing=$(ssh_guest 'ls -d /home/operator') operator_home_listing=$(ssh_guest 'ls -d /home/operator')
@@ -128,6 +132,13 @@ operator_home_listing=$(ssh_guest 'ls -d /home/operator')
[ "$shepherd_socket" = present ] || { echo "shepherd socket is missing" >&2; exit 1; } [ "$shepherd_socket" = present ] || { echo "shepherd socket is missing" >&2; exit 1; }
[ "$shepherd_status" = running ] || { echo "shepherd is not running" >&2; exit 1; } [ "$shepherd_status" = running ] || { echo "shepherd is not running" >&2; exit 1; }
[ "$sshd_status" = running ] || { echo "sshd is not running" >&2; exit 1; } [ "$sshd_status" = running ] || { echo "sshd is not running" >&2; exit 1; }
[ "$login_conf_kind" = regular ] || { echo "/etc/login.conf is not a regular file in guest: $login_conf_kind" >&2; exit 1; }
[ "$login_conf_db" = present ] || { echo "/etc/login.conf.db is missing in guest" >&2; exit 1; }
[ "$pwd_dbs" = present ] || { echo "pwd.db/spwd.db are missing in guest" >&2; exit 1; }
case "$activate_log" in
*fruix-activate:done*) : ;;
*) echo "activation log does not show successful completion: $activate_log" >&2; exit 1 ;;
esac
[ "$operator_home_listing" = /home/operator ] || { echo "operator home missing" >&2; exit 1; } [ "$operator_home_listing" = /home/operator ] || { echo "operator home missing" >&2; exit 1; }
cat >"$metadata_file" <<EOF cat >"$metadata_file" <<EOF
@@ -155,6 +166,10 @@ logger_log=$logger_log
shepherd_bootstrap_tail=$shepherd_bootstrap_tail shepherd_bootstrap_tail=$shepherd_bootstrap_tail
shepherd_log_tail=$shepherd_log_tail shepherd_log_tail=$shepherd_log_tail
guest_dmesg_tail=$guest_dmesg_tail guest_dmesg_tail=$guest_dmesg_tail
activate_log=$activate_log
login_conf_kind=$login_conf_kind
login_conf_db=$login_conf_db
pwd_dbs=$pwd_dbs
uname_output=$uname_output uname_output=$uname_output
operator_home_listing=$operator_home_listing operator_home_listing=$operator_home_listing
boot_backend=qemu-uefi-tcg boot_backend=qemu-uefi-tcg

View File

@@ -158,6 +158,10 @@ operator_home_listing=$(ssh_guest 'ls -d /home/operator')
compat_prefix_shims=$(ssh_guest 'for p in /tmp/guile-freebsd-validate-install /tmp/guile-gnutls-freebsd-validate-install /tmp/shepherd-freebsd-validate-install; do if [ -e "$p" ] || [ -L "$p" ]; then echo present; exit 0; fi; done; echo absent') compat_prefix_shims=$(ssh_guest 'for p in /tmp/guile-freebsd-validate-install /tmp/guile-gnutls-freebsd-validate-install /tmp/shepherd-freebsd-validate-install; do if [ -e "$p" ] || [ -L "$p" ]; then echo present; exit 0; fi; done; echo absent')
guile_module_smoke=$(ssh_guest "env LANG='C.UTF-8' LC_ALL='C.UTF-8' LD_LIBRARY_PATH='$guile_extra_store/lib:$guile_store/lib:/usr/local/lib' GUILE_SYSTEM_PATH='$guile_store/share/guile/3.0:$guile_store/share/guile/site/3.0:$guile_store/share/guile/site:$guile_store/share/guile' GUILE_LOAD_PATH='$shepherd_store/share/guile/site/3.0:$guile_extra_store/share/guile/site/3.0' GUILE_SYSTEM_COMPILED_PATH='$guile_store/lib/guile/3.0/ccache:$guile_store/lib/guile/3.0/site-ccache' GUILE_LOAD_COMPILED_PATH='$shepherd_store/lib/guile/3.0/site-ccache:$guile_extra_store/lib/guile/3.0/site-ccache' GUILE_SYSTEM_EXTENSIONS_PATH='$guile_store/lib/guile/3.0/extensions' GUILE_EXTENSIONS_PATH='$guile_extra_store/lib/guile/3.0/extensions' '$guile_store/bin/guile' --no-auto-compile -c '(use-modules (fibers config) (gnutls) (shepherd config)) (display \"ok\") (newline)'") guile_module_smoke=$(ssh_guest "env LANG='C.UTF-8' LC_ALL='C.UTF-8' LD_LIBRARY_PATH='$guile_extra_store/lib:$guile_store/lib:/usr/local/lib' GUILE_SYSTEM_PATH='$guile_store/share/guile/3.0:$guile_store/share/guile/site/3.0:$guile_store/share/guile/site:$guile_store/share/guile' GUILE_LOAD_PATH='$shepherd_store/share/guile/site/3.0:$guile_extra_store/share/guile/site/3.0' GUILE_SYSTEM_COMPILED_PATH='$guile_store/lib/guile/3.0/ccache:$guile_store/lib/guile/3.0/site-ccache' GUILE_LOAD_COMPILED_PATH='$shepherd_store/lib/guile/3.0/site-ccache:$guile_extra_store/lib/guile/3.0/site-ccache' GUILE_SYSTEM_EXTENSIONS_PATH='$guile_store/lib/guile/3.0/extensions' GUILE_EXTENSIONS_PATH='$guile_extra_store/lib/guile/3.0/extensions' '$guile_store/bin/guile' --no-auto-compile -c '(use-modules (fibers config) (gnutls) (shepherd config)) (display \"ok\") (newline)'")
activate_preview=$(ssh_guest 'head -n 5 /run/current-system/activate' | tr '\n' ' ') activate_preview=$(ssh_guest 'head -n 5 /run/current-system/activate' | tr '\n' ' ')
activate_log=$(ssh_guest 'cat /var/log/fruix-activate.log 2>/dev/null || true' | tr '\n' ' ')
login_conf_kind=$(ssh_guest 'if [ -L /etc/login.conf ]; then echo symlink; elif [ -f /etc/login.conf ]; then echo regular; else echo missing; fi')
login_conf_db=$(ssh_guest 'test -f /etc/login.conf.db && echo present || echo missing')
pwd_dbs=$(ssh_guest 'if [ -f /etc/pwd.db ] && [ -f /etc/spwd.db ]; then echo present; else echo missing; fi')
[ "$ready_marker" = ready ] || { echo "unexpected ready marker contents: $ready_marker" >&2; exit 1; } [ "$ready_marker" = ready ] || { echo "unexpected ready marker contents: $ready_marker" >&2; exit 1; }
[ "$shepherd_pid" = 1 ] || { echo "shepherd is not PID 1: pid=$shepherd_pid command=$pid1_command" >&2; exit 1; } [ "$shepherd_pid" = 1 ] || { echo "shepherd is not PID 1: pid=$shepherd_pid command=$pid1_command" >&2; exit 1; }
@@ -166,6 +170,13 @@ activate_preview=$(ssh_guest 'head -n 5 /run/current-system/activate' | tr '\n'
[ "$sshd_status" = running ] || { echo "sshd is not running" >&2; exit 1; } [ "$sshd_status" = running ] || { echo "sshd is not running" >&2; exit 1; }
[ "$compat_prefix_shims" = absent ] || { echo "compatibility prefix shims are still present in /tmp" >&2; exit 1; } [ "$compat_prefix_shims" = absent ] || { echo "compatibility prefix shims are still present in /tmp" >&2; exit 1; }
[ "$guile_module_smoke" = ok ] || { echo "guest Guile module smoke failed: $guile_module_smoke" >&2; exit 1; } [ "$guile_module_smoke" = ok ] || { echo "guest Guile module smoke failed: $guile_module_smoke" >&2; exit 1; }
[ "$login_conf_kind" = regular ] || { echo "/etc/login.conf is not a regular file in guest: $login_conf_kind" >&2; exit 1; }
[ "$login_conf_db" = present ] || { echo "/etc/login.conf.db is missing in guest" >&2; exit 1; }
[ "$pwd_dbs" = present ] || { echo "pwd.db/spwd.db are missing in guest" >&2; exit 1; }
case "$activate_log" in
*fruix-activate:done*) : ;;
*) echo "activation log does not show successful completion: $activate_log" >&2; exit 1 ;;
esac
[ "$run_current_system_target" = "/frx/store/$closure_base" ] || { [ "$run_current_system_target" = "/frx/store/$closure_base" ] || {
echo "unexpected /run/current-system target in guest: $run_current_system_target" >&2 echo "unexpected /run/current-system target in guest: $run_current_system_target" >&2
exit 1 exit 1
@@ -210,6 +221,10 @@ operator_home_listing=$operator_home_listing
compat_prefix_shims=$compat_prefix_shims compat_prefix_shims=$compat_prefix_shims
guile_module_smoke=$guile_module_smoke guile_module_smoke=$guile_module_smoke
activate_preview=$activate_preview activate_preview=$activate_preview
activate_log=$activate_log
login_conf_kind=$login_conf_kind
login_conf_db=$login_conf_db
pwd_dbs=$pwd_dbs
boot_backend=xcp-ng-xo-cli boot_backend=xcp-ng-xo-cli
init_mode=shepherd-pid1 init_mode=shepherd-pid1
operator_access=ssh-root-key operator_access=ssh-root-key

View File

@@ -114,11 +114,15 @@ boot_loader_target=$(readlink "$mnt_root/boot/loader")
boot_loader_conf_target=$(readlink "$mnt_root/boot/loader.conf") boot_loader_conf_target=$(readlink "$mnt_root/boot/loader.conf")
rc_conf_target=$(readlink "$mnt_root/etc/rc.conf") rc_conf_target=$(readlink "$mnt_root/etc/rc.conf")
rc_script_target=$(readlink "$mnt_root/usr/local/etc/rc.d/fruix-shepherd") rc_script_target=$(readlink "$mnt_root/usr/local/etc/rc.d/fruix-shepherd")
if [ -L "$mnt_root/etc/login.conf" ]; then login_conf_kind=symlink; elif [ -f "$mnt_root/etc/login.conf" ]; then login_conf_kind=regular; else login_conf_kind=missing; fi
if [ -L "$mnt_root/etc/master.passwd" ]; then master_passwd_kind=symlink; elif [ -f "$mnt_root/etc/master.passwd" ]; then master_passwd_kind=regular; else master_passwd_kind=missing; fi
[ "$run_current_system_target" = "/frx/store/$closure_base" ] || { echo "unexpected /run/current-system target: $run_current_system_target" >&2; exit 1; } [ "$run_current_system_target" = "/frx/store/$closure_base" ] || { echo "unexpected /run/current-system target: $run_current_system_target" >&2; exit 1; }
[ "$boot_loader_target" = /run/current-system/boot/loader ] || { echo "unexpected /boot/loader target: $boot_loader_target" >&2; exit 1; } [ "$boot_loader_target" = /run/current-system/boot/loader ] || { echo "unexpected /boot/loader target: $boot_loader_target" >&2; exit 1; }
[ "$boot_loader_conf_target" = /run/current-system/boot/loader.conf ] || { echo "unexpected /boot/loader.conf target: $boot_loader_conf_target" >&2; exit 1; } [ "$boot_loader_conf_target" = /run/current-system/boot/loader.conf ] || { echo "unexpected /boot/loader.conf target: $boot_loader_conf_target" >&2; exit 1; }
[ "$rc_conf_target" = /run/current-system/etc/rc.conf ] || { echo "unexpected /etc/rc.conf target: $rc_conf_target" >&2; exit 1; } [ "$rc_conf_target" = /run/current-system/etc/rc.conf ] || { echo "unexpected /etc/rc.conf target: $rc_conf_target" >&2; exit 1; }
[ "$rc_script_target" = /run/current-system/usr/local/etc/rc.d/fruix-shepherd ] || { echo "unexpected fruix_shepherd rc target: $rc_script_target" >&2; exit 1; } [ "$rc_script_target" = /run/current-system/usr/local/etc/rc.d/fruix-shepherd ] || { echo "unexpected fruix_shepherd rc target: $rc_script_target" >&2; exit 1; }
[ "$login_conf_kind" = regular ] || { echo "/etc/login.conf is not a regular file in the image" >&2; exit 1; }
[ "$master_passwd_kind" = regular ] || { echo "/etc/master.passwd is not a regular file in the image" >&2; exit 1; }
loader_conf_image=$mnt_root/frx/store/$closure_base/boot/loader.conf loader_conf_image=$mnt_root/frx/store/$closure_base/boot/loader.conf
rc_conf_image=$mnt_root/frx/store/$closure_base/etc/rc.conf rc_conf_image=$mnt_root/frx/store/$closure_base/etc/rc.conf
grep -F 'comconsole' "$loader_conf_image" >/dev/null || { echo "loader.conf is missing serial console config" >&2; exit 1; } grep -F 'comconsole' "$loader_conf_image" >/dev/null || { echo "loader.conf is missing serial console config" >&2; exit 1; }
@@ -159,6 +163,8 @@ boot_loader_target=$boot_loader_target
boot_loader_conf_target=$boot_loader_conf_target boot_loader_conf_target=$boot_loader_conf_target
rc_conf_target=$rc_conf_target rc_conf_target=$rc_conf_target
rc_script_target=$rc_script_target rc_script_target=$rc_script_target
login_conf_kind=$login_conf_kind
master_passwd_kind=$master_passwd_kind
image_generation_mode=declarative-system-layer image_generation_mode=declarative-system-layer
frontend_invocation=$fruix_cmd system image frontend_invocation=$fruix_cmd system image
EOF EOF

View File

@@ -157,12 +157,23 @@ operator_home_listing=$(ssh_guest 'ls -d /home/operator')
compat_prefix_shims=$(ssh_guest 'for p in /tmp/guile-freebsd-validate-install /tmp/guile-gnutls-freebsd-validate-install /tmp/shepherd-freebsd-validate-install; do if [ -e "$p" ] || [ -L "$p" ]; then echo present; exit 0; fi; done; echo absent') compat_prefix_shims=$(ssh_guest 'for p in /tmp/guile-freebsd-validate-install /tmp/guile-gnutls-freebsd-validate-install /tmp/shepherd-freebsd-validate-install; do if [ -e "$p" ] || [ -L "$p" ]; then echo present; exit 0; fi; done; echo absent')
guile_module_smoke=$(ssh_guest "env LANG='C.UTF-8' LC_ALL='C.UTF-8' LD_LIBRARY_PATH='$guile_extra_store/lib:$guile_store/lib:/usr/local/lib' GUILE_SYSTEM_PATH='$guile_store/share/guile/3.0:$guile_store/share/guile/site/3.0:$guile_store/share/guile/site:$guile_store/share/guile' GUILE_LOAD_PATH='$shepherd_store/share/guile/site/3.0:$guile_extra_store/share/guile/site/3.0' GUILE_SYSTEM_COMPILED_PATH='$guile_store/lib/guile/3.0/ccache:$guile_store/lib/guile/3.0/site-ccache' GUILE_LOAD_COMPILED_PATH='$shepherd_store/lib/guile/3.0/site-ccache:$guile_extra_store/lib/guile/3.0/site-ccache' GUILE_SYSTEM_EXTENSIONS_PATH='$guile_store/lib/guile/3.0/extensions' GUILE_EXTENSIONS_PATH='$guile_extra_store/lib/guile/3.0/extensions' '$guile_store/bin/guile' --no-auto-compile -c '(use-modules (fibers config) (gnutls) (shepherd config)) (display \"ok\") (newline)'") guile_module_smoke=$(ssh_guest "env LANG='C.UTF-8' LC_ALL='C.UTF-8' LD_LIBRARY_PATH='$guile_extra_store/lib:$guile_store/lib:/usr/local/lib' GUILE_SYSTEM_PATH='$guile_store/share/guile/3.0:$guile_store/share/guile/site/3.0:$guile_store/share/guile/site:$guile_store/share/guile' GUILE_LOAD_PATH='$shepherd_store/share/guile/site/3.0:$guile_extra_store/share/guile/site/3.0' GUILE_SYSTEM_COMPILED_PATH='$guile_store/lib/guile/3.0/ccache:$guile_store/lib/guile/3.0/site-ccache' GUILE_LOAD_COMPILED_PATH='$shepherd_store/lib/guile/3.0/site-ccache:$guile_extra_store/lib/guile/3.0/site-ccache' GUILE_SYSTEM_EXTENSIONS_PATH='$guile_store/lib/guile/3.0/extensions' GUILE_EXTENSIONS_PATH='$guile_extra_store/lib/guile/3.0/extensions' '$guile_store/bin/guile' --no-auto-compile -c '(use-modules (fibers config) (gnutls) (shepherd config)) (display \"ok\") (newline)'")
activate_preview=$(ssh_guest 'head -n 5 /run/current-system/activate' | tr '\n' ' ') activate_preview=$(ssh_guest 'head -n 5 /run/current-system/activate' | tr '\n' ' ')
activate_log=$(ssh_guest 'cat /var/log/fruix-activate.log 2>/dev/null || true' | tr '\n' ' ')
login_conf_kind=$(ssh_guest 'if [ -L /etc/login.conf ]; then echo symlink; elif [ -f /etc/login.conf ]; then echo regular; else echo missing; fi')
login_conf_db=$(ssh_guest 'test -f /etc/login.conf.db && echo present || echo missing')
pwd_dbs=$(ssh_guest 'if [ -f /etc/pwd.db ] && [ -f /etc/spwd.db ]; then echo present; else echo missing; fi')
[ "$ready_marker" = ready ] || { echo "unexpected ready marker contents: $ready_marker" >&2; exit 1; } [ "$ready_marker" = ready ] || { echo "unexpected ready marker contents: $ready_marker" >&2; exit 1; }
[ "$shepherd_status" = running ] || { echo "fruix_shepherd is not running" >&2; exit 1; } [ "$shepherd_status" = running ] || { echo "fruix_shepherd is not running" >&2; exit 1; }
[ "$sshd_status" = running ] || { echo "sshd is not running" >&2; exit 1; } [ "$sshd_status" = running ] || { echo "sshd is not running" >&2; exit 1; }
[ "$compat_prefix_shims" = absent ] || { echo "compatibility prefix shims are still present in /tmp" >&2; exit 1; } [ "$compat_prefix_shims" = absent ] || { echo "compatibility prefix shims are still present in /tmp" >&2; exit 1; }
[ "$guile_module_smoke" = ok ] || { echo "guest Guile module smoke failed: $guile_module_smoke" >&2; exit 1; } [ "$guile_module_smoke" = ok ] || { echo "guest Guile module smoke failed: $guile_module_smoke" >&2; exit 1; }
[ "$login_conf_kind" = regular ] || { echo "/etc/login.conf is not a regular file in guest: $login_conf_kind" >&2; exit 1; }
[ "$login_conf_db" = present ] || { echo "/etc/login.conf.db is missing in guest" >&2; exit 1; }
[ "$pwd_dbs" = present ] || { echo "pwd.db/spwd.db are missing in guest" >&2; exit 1; }
case "$activate_log" in
*fruix-activate:done*) : ;;
*) echo "activation log does not show successful completion: $activate_log" >&2; exit 1 ;;
esac
[ "$run_current_system_target" = "/frx/store/$closure_base" ] || { [ "$run_current_system_target" = "/frx/store/$closure_base" ] || {
echo "unexpected /run/current-system target in guest: $run_current_system_target" >&2 echo "unexpected /run/current-system target in guest: $run_current_system_target" >&2
exit 1 exit 1
@@ -205,6 +216,10 @@ operator_home_listing=$operator_home_listing
compat_prefix_shims=$compat_prefix_shims compat_prefix_shims=$compat_prefix_shims
guile_module_smoke=$guile_module_smoke guile_module_smoke=$guile_module_smoke
activate_preview=$activate_preview activate_preview=$activate_preview
activate_log=$activate_log
login_conf_kind=$login_conf_kind
login_conf_db=$login_conf_db
pwd_dbs=$pwd_dbs
boot_backend=xcp-ng-xo-cli boot_backend=xcp-ng-xo-cli
operator_access=ssh-root-key operator_access=ssh-root-key
root_authorized_key_file=$root_authorized_key_file root_authorized_key_file=$root_authorized_key_file