Bridge Shepherd to FreeBSD services

This commit is contained in:
2026-04-01 12:47:24 +02:00
parent 83715f04dd
commit e98dbdf150
5 changed files with 708 additions and 0 deletions

View File

@@ -1425,3 +1425,114 @@ Next recommended step:
2. use that bridge layer in a runnable integration harness that validates both activation and cleanup of those FreeBSD concepts 2. use that bridge layer in a runnable integration harness that validates both activation and cleanup of those FreeBSD concepts
3. continue carrying the separate real-checkout runtime blocker for later integration work: 3. continue carrying the separate real-checkout runtime blocker for later integration work:
- investigate the `leave-on-EPIPE` failure in `./pre-inst-env guix --version` - investigate the `leave-on-EPIPE` failure in `./pre-inst-env guix --version`
## 2026-04-01 — Phase 4.3 completed: FreeBSD Shepherd bridge layer validated across rc.d, network, filesystem, and account management
Completed work:
- added a reusable FreeBSD Shepherd bridge module:
- `modules/fruix/shepherd/freebsd.scm`
- added a runnable integration harness exercising that bridge layer:
- `tests/shepherd/run-freebsd-shepherd-bridge-prototype.sh`
- wrote the Phase 4.3 report:
- `docs/reports/phase4-freebsd-shepherd-bridge.md`
- ran the bridge prototype successfully and captured metadata under:
- `/tmp/freebsd-shepherd-bridge-metadata.txt`
Important findings:
- the new module now exports concrete helper constructors for four FreeBSD integration categories:
- `freebsd-rc-service`
- `freebsd-loopback-alias-service`
- `freebsd-tmpfs-service`
- `freebsd-user-group-service`
- the integration harness used those helpers to manage a real chained host-side service graph under Shepherd covering:
- a temporary rc.d script in `/usr/local/etc/rc.d/`
- loopback alias configuration on `lo0`
- tmpfs mount/unmount with mode validation
- temporary user/group creation and removal via `pw`
- observed activation metadata confirmed all of those operations succeeded under Shepherd control:
- `target_running=yes`
- `rc_started=yes`
- `alias_present=yes`
- `tmpfs_mounted=yes`
- `tmpfs_mode=drwxr-x---`
- `user_present=yes`
- `group_present=yes`
- observed cleanup metadata confirmed that `stop root` also reversed all of those host-side effects successfully:
- `rc_stopped=yes`
- `alias_removed=yes`
- `tmpfs_unmounted=yes`
- `user_removed=yes`
- `group_removed=yes`
- the same expected FreeBSD runtime note remained true here as well:
- `System lacks support for 'signalfd'; using fallback mechanism.`
and again it did not prevent the prototype from working correctly
Current assessment:
- Phase 4.3 is now satisfied on the current prototype track
- Shepherd now has a concrete FreeBSD bridge layer in-repo rather than only ad hoc validation scripts
- with service supervision, rc.d integration, and FreeBSD host-concept bridging now all validated, Phase 4 is complete on the current FreeBSD amd64 prototype path
## 2026-04-01 — Phase 4 completed on the current FreeBSD prototype track
Phase 4 is now considered complete for the active FreeBSD amd64 prototype path.
Why this milestone is satisfied:
- **Phase 4.1** success criteria were met on the prototype track:
- Shepherd built successfully on FreeBSD with the fixed local Guile stack
- regular multi-service supervision worked
- dependency handling, status monitoring, privilege-aware execution, and respawn behavior were all validated
- **Phase 4.2** success criteria were met in init-integration prototype form:
- a real FreeBSD `rc.d` wrapper launched Shepherd successfully
- a minimal essential-service graph started automatically in correct dependency order
- orderly reverse shutdown through native FreeBSD service entry points was validated
- **Phase 4.3** success criteria were met on the prototype track:
- a reusable FreeBSD Shepherd bridge layer was added
- Shepherd services successfully bridged to rc.d service control, loopback/network configuration, filesystem mounting/permissions, and temporary user/group administration
- both activation and cleanup were validated
Important scope note:
- this completes the **Shepherd porting milestone** on the current prototype track, not a literal replacement of `/sbin/init` on the live host
- however, the core Shepherd questions have now been answered positively on FreeBSD:
- it builds
- it runs
- it supervises services
- it integrates with FreeBSD service-management conventions
- it can express concrete FreeBSD host-management tasks through Shepherd services
- the separate real-Guix-checkout runtime blocker still exists for later integration work:
- `./pre-inst-env guix --version` fails with `Wrong type to apply: #<syntax-transformer leave-on-EPIPE>`
but that is now clearly outside the scope of the completed Phase 4 Shepherd milestone
Recent commits:
- `e380e88``Add FreeBSD Guile verification harness`
- `cd721b1``Update progress after Guile verification`
- `27916cb``Diagnose Guile subprocess crash on FreeBSD`
- `02f7a7f``Validate local Guile fix on FreeBSD`
- `4aebea4``Add native GNU Hello FreeBSD build harness`
- `c944cdb``Validate Guix builder phases on FreeBSD`
- `0a2e48e``Validate GNU which builder phases on FreeBSD`
- `245a47d``Document gaps to real Guix FreeBSD builds`
- `d62e9b0``Investigate Guix derivation generation on FreeBSD`
- `c0a85ed``Build local Guile-GnuTLS on FreeBSD`
- `15b9037``Build local Guile-Git on FreeBSD`
- `47d31e8``Build local Guile-JSON on FreeBSD`
- `d82195b``Advance Guix checkout on FreeBSD`
- `9bf3d30``Document FreeBSD syscall mapping`
- `7621798``Prototype FreeBSD jail build isolation`
- `d65b2af``Prototype FreeBSD build user isolation`
- `e404e2e``Prototype FreeBSD store management`
- `eb0d77c``Adapt GNU build phases for FreeBSD`
- `d47dc9b``Prototype FreeBSD package definitions`
- `b36746f``Validate Shepherd services on FreeBSD`
- `83715f0``Prototype Shepherd rc.d integration`
Next recommended step:
1. return to the remaining real Guix checkout/runtime blocker and investigate the `leave-on-EPIPE` failure in `./pre-inst-env guix --version`
2. begin the next post-Phase-4 integration milestone by connecting the now-validated daemon/build/store/Shepherd prototypes more directly to real Guix checkout behavior on FreeBSD
3. continue using `/frx/store` rather than `/gnu/store` whenever future integration experiments need a persistent store root

View File

@@ -6,4 +6,31 @@ You can use `sudo` freely, install missing software via `sudo pkg install`. If y
Guix sources are in ~/repos/guix. FreeBSD sources are installed in /usr/src. Clone other helpful repos or extract sources to ~/repos/ as required. Guix sources are in ~/repos/guix. FreeBSD sources are installed in /usr/src. Clone other helpful repos or extract sources to ~/repos/ as required.
Note: Our goal is NOT to run Guix on top of FreeBSD, but more like to use it as inspiration for Fruix, which should embrace FreeBSD semantics - in the long run - like jails, bhyve, etc.:
1. The model you want to preserve
This is the real treasure:
- declarative builds
- content-addressed store
- reproducibility
- daemon-mediated privilege separation
- garbage collection from roots
- derivations as build descriptions
That is the soul.
2. The platform assumptions you should challenge
These are not sacred:
- Linux process/container assumptions
- GNU coreutils/bash/findutils as ambient baseline
- specific /proc or signal behavior
- GNU userland quirks in package recipes
- Linux-centric builder environment setup
That is just sediment from history.
Good luck! Good luck!

View File

@@ -0,0 +1,142 @@
# Phase 4.3: FreeBSD Shepherd bridge layer for rc.d, network, filesystem, and account management
Date: 2026-04-01
## Summary
This step adds a small FreeBSD-specific Shepherd bridge module and validates that Shepherd services can manage representative FreeBSD host concepts directly.
Added files:
- `modules/fruix/shepherd/freebsd.scm`
- `tests/shepherd/run-freebsd-shepherd-bridge-prototype.sh`
## Bridge module
The new module exports reusable helper constructors for Shepherd services on FreeBSD:
- `freebsd-rc-service`
- `freebsd-loopback-alias-service`
- `freebsd-tmpfs-service`
- `freebsd-user-group-service`
These helpers provide a minimal but practical bridge between Shepherd service definitions and important FreeBSD host-management primitives.
## What each helper does
### `freebsd-rc-service`
Wraps FreeBSD rc.d control through:
- `/usr/sbin/service <name> onestart`
- `/usr/sbin/service <name> onestop`
This is the key bridge for rcng-equivalent functionality.
### `freebsd-loopback-alias-service`
Manages loopback/network configuration by adding and removing a loopback alias using:
- `/sbin/ifconfig lo0 alias ...`
- `/sbin/ifconfig lo0 -alias ...`
### `freebsd-tmpfs-service`
Manages filesystem setup by creating a mount point and mounting/unmounting tmpfs using:
- `/bin/mkdir -p`
- `/sbin/mount -t tmpfs`
- `/sbin/umount`
### `freebsd-user-group-service`
Manages temporary user/group administration using:
- `/usr/sbin/pw groupadd`
- `/usr/sbin/pw useradd`
- `/usr/sbin/pw userdel -r`
- `/usr/sbin/pw groupdel`
## Prototype harness
Run command:
```sh
METADATA_OUT=/tmp/freebsd-shepherd-bridge-metadata.txt \
./tests/shepherd/run-freebsd-shepherd-bridge-prototype.sh
```
The harness:
1. installs a temporary mock rc.d service under `/usr/local/etc/rc.d/`
2. starts a root-launched Shepherd instance with the new bridge module in `GUILE_LOAD_PATH`
3. defines a chained service graph:
- `bridge-rc`
- `bridge-network`
- `bridge-filesystem`
- `bridge-account`
- `bridge-target`
4. automatically starts the top-level bridge target
5. validates host-side effects
6. stops the service graph and validates cleanup
## Observed activation results
Observed metadata included:
- `target_running=yes`
- `rc_started=yes`
- `alias_present=yes`
- `tmpfs_mounted=yes`
- `tmpfs_mode=drwxr-x---`
- `user_present=yes`
- `group_present=yes`
Concrete validated effects:
- the temporary rc.d script was started through Shepherd and wrote its expected start marker
- the loopback alias was present on `lo0`
- tmpfs was mounted successfully on the requested mount point
- the mounted directory had the expected mode corresponding to `0750`
- the temporary user and group were created successfully and were visible through standard account queries
## Observed cleanup results
Observed metadata after `stop root` included:
- `rc_stopped=yes`
- `alias_removed=yes`
- `tmpfs_unmounted=yes`
- `user_removed=yes`
- `group_removed=yes`
This confirms that the same Shepherd service graph also performed complete cleanup of the corresponding FreeBSD host state.
## Important findings
- the new bridge layer shows that Shepherd services can express useful FreeBSD host-management tasks directly without depending on Linux-specific service assumptions
- rcng integration does not need to be all-or-nothing; Shepherd can manage rc.d services where that is useful while also directly managing native host actions such as `ifconfig`, `mount`, and `pw`
- the signalfd fallback note remains present on FreeBSD:
- `System lacks support for 'signalfd'; using fallback mechanism.`
but it did not interfere with this host-management prototype
## Why this satisfies Phase 4.3 on the prototype track
Phase 4.3 asked to demonstrate that Shepherd services can bridge to FreeBSD service concepts and manage representative system tasks. On the current prototype track, that is satisfied because the harness demonstrated all of the requested categories:
- rcng-equivalent functionality:
- through `freebsd-rc-service`
- network configuration:
- through loopback alias management on `lo0`
- filesystem mounting and permissions:
- through tmpfs mount/unmount and mode validation
- user and group administration:
- through temporary account creation/removal with `pw`
## Conclusion
Phase 4.3 is satisfied on the current FreeBSD prototype track:
- a reusable FreeBSD Shepherd bridge layer now exists in-repo
- it validates concrete bridging of Shepherd to rc.d, networking, filesystem, and account-management concepts
- activation and cleanup both work correctly under Shepherd supervision

View File

@@ -0,0 +1,104 @@
(define-module (fruix shepherd freebsd)
#:use-module (shepherd service)
#:use-module (shepherd support)
#:use-module (ice-9 popen)
#:export (freebsd-rc-service
freebsd-loopback-alias-service
freebsd-tmpfs-service
freebsd-user-group-service))
(define (run-command program . args)
(let ((status (apply system* program args)))
(unless (zero? status)
(error "command failed" (cons program args) status))
#t))
(define (run-command/ignore-errors program . args)
(apply system* program args)
#t)
(define* (freebsd-rc-service provision script-name
#:key
(requirement '())
(documentation
"Manage a FreeBSD rc.d service through 'service'."))
(service provision
#:documentation documentation
#:requirement requirement
#:start (lambda _
(run-command "/usr/sbin/service" script-name "onestart")
#t)
#:stop (lambda _
(run-command "/usr/sbin/service" script-name "onestop")
#f)
#:respawn? #f))
(define* (freebsd-loopback-alias-service provision address
#:key
(interface "lo0")
(cidr "32")
(requirement '())
(documentation
"Add and remove a loopback alias on FreeBSD."))
(service provision
#:documentation documentation
#:requirement requirement
#:start (lambda _
(run-command "/sbin/ifconfig" interface "alias"
(string-append address "/" cidr))
#t)
#:stop (lambda _
(run-command "/sbin/ifconfig" interface "-alias" address)
#f)
#:respawn? #f))
(define* (freebsd-tmpfs-service provision mount-point
#:key
(size "1m")
(mode "0750")
(requirement '())
(documentation
"Mount and unmount a tmpfs filesystem on FreeBSD."))
(service provision
#:documentation documentation
#:requirement requirement
#:start (lambda _
(run-command "/bin/mkdir" "-p" mount-point)
(run-command "/sbin/mount" "-t" "tmpfs"
"-o" (string-append "size=" size ",mode=" mode)
"tmpfs" mount-point)
#t)
#:stop (lambda _
(run-command "/sbin/umount" mount-point)
#f)
#:respawn? #f))
(define* (freebsd-user-group-service provision user group
#:key
uid
gid
home
(shell "/usr/sbin/nologin")
(comment "Fruix Shepherd prototype account")
(requirement '())
(documentation
"Create and remove a temporary FreeBSD user/group pair."))
(service provision
#:documentation documentation
#:requirement requirement
#:start (lambda _
(run-command "/usr/sbin/pw" "groupadd" group
"-g" (number->string gid))
(run-command "/usr/sbin/pw" "useradd" user
"-u" (number->string uid)
"-g" group
"-d" home
"-m"
"-s" shell
"-c" comment)
#t)
#:stop (lambda _
(run-command/ignore-errors "/usr/sbin/pw" "userdel" user "-r")
(run-command/ignore-errors "/usr/sbin/pw" "groupdel" group)
#f)
#:respawn? #f))

View File

@@ -0,0 +1,324 @@
#!/bin/sh
set -eu
repo_root=$(CDPATH= cd -- "$(dirname "$0")/../.." && pwd)
guile_bin=${GUILE_BIN:-/tmp/guile-freebsd-validate-install/bin/guile}
guile_extra_prefix=${GUILE_EXTRA_PREFIX:-/tmp/guile-gnutls-freebsd-validate-install}
shepherd_prefix=${SHEPHERD_PREFIX:-/tmp/shepherd-freebsd-validate-install}
shepherd_bin=$shepherd_prefix/bin/shepherd
herd_bin=$shepherd_prefix/bin/herd
metadata_target=${METADATA_OUT:-}
if [ ! -x "$guile_bin" ]; then
echo "Guile binary is not executable: $guile_bin" >&2
exit 1
fi
ensure_built() {
if [ ! -d "$guile_extra_prefix/share/guile/site" ] || \
! GUILE_LOAD_PATH="$guile_extra_prefix/share/guile/site/3.0${GUILE_LOAD_PATH:+:$GUILE_LOAD_PATH}" \
GUILE_LOAD_COMPILED_PATH="$guile_extra_prefix/lib/guile/3.0/site-ccache${GUILE_LOAD_COMPILED_PATH:+:$GUILE_LOAD_COMPILED_PATH}" \
GUILE_EXTENSIONS_PATH="$guile_extra_prefix/lib/guile/3.0/extensions${GUILE_EXTENSIONS_PATH:+:$GUILE_EXTENSIONS_PATH}" \
LD_LIBRARY_PATH="$guile_extra_prefix/lib:/tmp/guile-freebsd-validate-install/lib:/usr/local/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \
"$guile_bin" -c '(catch #t (lambda () (use-modules (fibers)) (display "ok") (newline)) (lambda _ (display "missing") (newline)))' | grep -qx ok; then
METADATA_OUT= ENV_OUT= "$repo_root/tests/shepherd/build-local-guile-fibers.sh"
fi
if [ ! -x "$shepherd_bin" ] || [ ! -x "$herd_bin" ]; then
METADATA_OUT= ENV_OUT= GUILE_EXTRA_PREFIX="$guile_extra_prefix" "$repo_root/tests/shepherd/build-local-shepherd.sh"
fi
}
ensure_built
run_root() {
if [ "$(id -u)" -eq 0 ]; then
"$@"
else
sudo "$@"
fi
}
guile_bindir=$(CDPATH= cd -- "$(dirname "$guile_bin")" && pwd)
guile_prefix=$(CDPATH= cd -- "$guile_bindir/.." && pwd)
ld_library_path=$guile_extra_prefix/lib:$guile_prefix/lib:/usr/local/lib
guile_load_path=$repo_root/modules:$guile_extra_prefix/share/guile/site/3.0
guile_load_compiled_path=$guile_extra_prefix/lib/guile/3.0/site-ccache
guile_extensions_path=$guile_extra_prefix/lib/guile/3.0/extensions
run_root_env() {
if [ "$(id -u)" -eq 0 ]; then
env \
LD_LIBRARY_PATH="$ld_library_path" \
GUILE_LOAD_PATH="$guile_load_path" \
GUILE_LOAD_COMPILED_PATH="$guile_load_compiled_path" \
GUILE_EXTENSIONS_PATH="$guile_extensions_path" \
"$@"
else
sudo env \
LD_LIBRARY_PATH="$ld_library_path" \
GUILE_LOAD_PATH="$guile_load_path" \
GUILE_LOAD_COMPILED_PATH="$guile_load_compiled_path" \
GUILE_EXTENSIONS_PATH="$guile_extensions_path" \
"$@"
fi
}
cleanup=0
if [ -n "${WORKDIR:-}" ]; then
workdir=$WORKDIR
mkdir -p "$workdir"
else
workdir=$(mktemp -d /tmp/freebsd-shepherd-bridge.XXXXXX)
cleanup=1
fi
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
cleanup=0
fi
chmod 0755 "$workdir"
config_file=$workdir/bridge-config.scm
socket_file=$workdir/shepherd.sock
pid_file=$workdir/shepherd.pid
shepherd_log=$workdir/shepherd.log
shepherd_stdout=$workdir/shepherd.out
metadata_file=$workdir/freebsd-shepherd-bridge-metadata.txt
status_file=$workdir/bridge-target.status
mock_rc_log=$workdir/mock-rc.log
mount_point=$workdir/tmpfs-mount
home_dir=$workdir/home
mock_rc_name=fruix_bridge_rc_$$
mock_rc_script=/usr/local/etc/rc.d/$mock_rc_name
loopback_alias=127.251.$(($$ % 200 + 1)).1
pick_free_id() {
for candidate in $(jot 100 36000 36099); do
if ! getent passwd "$candidate" >/dev/null 2>&1 && ! getent group "$candidate" >/dev/null 2>&1; then
echo "$candidate"
return 0
fi
done
return 1
}
temp_id=$(pick_free_id)
temp_user=fruixbridge$temp_id
temp_group=$temp_user
cleanup_workdir() {
run_root_env "$herd_bin" -s "$socket_file" stop root >/dev/null 2>&1 || true
run_root rm -f "$mock_rc_script"
run_root /sbin/ifconfig lo0 -alias "$loopback_alias" >/dev/null 2>&1 || true
if mount | grep -F "on $mount_point " >/dev/null 2>&1; then
run_root /sbin/umount "$mount_point" >/dev/null 2>&1 || true
fi
run_root /usr/sbin/pw userdel "$temp_user" -r >/dev/null 2>&1 || true
run_root /usr/sbin/pw groupdel "$temp_group" >/dev/null 2>&1 || true
if [ "$cleanup" -eq 1 ]; then
run_root rm -rf "$workdir"
fi
}
trap cleanup_workdir EXIT INT TERM
cat >"$workdir/mock-rc-template.in" <<'EOF'
#!/bin/sh
. /etc/rc.subr
name=__RC_NAME__
start_cmd="__RC_START_FN__"
stop_cmd="__RC_STOP_FN__"
__RC_START_FN__()
{
echo started >> __RC_LOG__
}
__RC_STOP_FN__()
{
echo stopped >> __RC_LOG__
}
load_rc_config $name
run_rc_command "$1"
EOF
mock_start_fn=${mock_rc_name}_start
mock_stop_fn=${mock_rc_name}_stop
sed \
-e "s|__RC_NAME__|$mock_rc_name|g" \
-e "s|__RC_START_FN__|$mock_start_fn|g" \
-e "s|__RC_STOP_FN__|$mock_stop_fn|g" \
-e "s|__RC_LOG__|$mock_rc_log|g" \
"$workdir/mock-rc-template.in" | run_root tee "$mock_rc_script" >/dev/null
run_root chmod +x "$mock_rc_script"
cat >"$config_file" <<EOF
(use-modules (fruix shepherd freebsd)
(shepherd service)
(shepherd support))
(register-services
(list
(freebsd-rc-service '(bridge-rc) "$mock_rc_name"
#:documentation "Run a temporary FreeBSD rc.d script through Shepherd.")
(freebsd-loopback-alias-service '(bridge-network) "$loopback_alias"
#:requirement '(bridge-rc)
#:documentation "Configure a temporary loopback alias through Shepherd.")
(freebsd-tmpfs-service '(bridge-filesystem) "$mount_point"
#:requirement '(bridge-network)
#:size "1m"
#:mode "0750"
#:documentation "Mount a temporary tmpfs filesystem through Shepherd.")
(freebsd-user-group-service '(bridge-account) "$temp_user" "$temp_group"
#:uid $temp_id
#:gid $temp_id
#:home "$home_dir"
#:requirement '(bridge-filesystem)
#:documentation "Create a temporary user and group through Shepherd.")
(service '(bridge-target)
#:documentation "Top-level FreeBSD bridge target."
#:requirement '(bridge-account)
#:start (lambda _ #t)
#:stop (lambda _ #f)
#:respawn? #f)))
(start-service (lookup-service 'bridge-target))
EOF
run_root_env "$shepherd_bin" -I -s "$socket_file" -c "$config_file" --pid="$pid_file" -l "$shepherd_log" >"$shepherd_stdout" 2>&1 &
for _ in 1 2 3 4 5 6 7 8 9 10; do
if [ -f "$pid_file" ] && [ -S "$socket_file" ]; then
break
fi
sleep 1
done
if [ ! -f "$pid_file" ] || [ ! -S "$socket_file" ]; then
echo "Shepherd bridge prototype did not become ready" >&2
exit 1
fi
run_root_env "$herd_bin" -s "$socket_file" status bridge-target >"$status_file"
case $(cat "$status_file") in
*"It is running"*) target_running=yes ;;
*) target_running=no ;;
esac
[ "$target_running" = yes ] || {
echo "Bridge target is not reported as running" >&2
exit 1
}
if grep -qx started "$mock_rc_log"; then
rc_started=yes
else
rc_started=no
fi
if ifconfig lo0 | grep -F "$loopback_alias" >/dev/null 2>&1; then
alias_present=yes
else
alias_present=no
fi
if mount | grep -F "on $mount_point " >/dev/null 2>&1; then
tmpfs_mounted=yes
else
tmpfs_mounted=no
fi
tmpfs_mode=$(stat -f '%Sp' "$mount_point")
if id "$temp_user" >/dev/null 2>&1; then
user_present=yes
else
user_present=no
fi
if getent group "$temp_group" >/dev/null 2>&1; then
group_present=yes
else
group_present=no
fi
[ "$rc_started" = yes ] || { echo "Mock rc service did not start" >&2; exit 1; }
[ "$alias_present" = yes ] || { echo "Loopback alias was not created" >&2; exit 1; }
[ "$tmpfs_mounted" = yes ] || { echo "tmpfs mount was not created" >&2; exit 1; }
[ "$tmpfs_mode" = 'drwxr-x---' ] || { echo "Unexpected tmpfs mode: $tmpfs_mode" >&2; exit 1; }
[ "$user_present" = yes ] || { echo "Temporary user was not created" >&2; exit 1; }
[ "$group_present" = yes ] || { echo "Temporary group was not created" >&2; exit 1; }
run_root_env "$herd_bin" -s "$socket_file" stop root >/dev/null || true
for _ in 1 2 3 4 5 6 7 8 9 10; do
[ ! -f "$pid_file" ] && break
sleep 1
done
if grep -qx stopped "$mock_rc_log"; then
rc_stopped=yes
else
rc_stopped=no
fi
if ifconfig lo0 | grep -F "$loopback_alias" >/dev/null 2>&1; then
alias_removed=no
else
alias_removed=yes
fi
if mount | grep -F "on $mount_point " >/dev/null 2>&1; then
tmpfs_unmounted=no
else
tmpfs_unmounted=yes
fi
if id "$temp_user" >/dev/null 2>&1; then
user_removed=no
else
user_removed=yes
fi
if getent group "$temp_group" >/dev/null 2>&1; then
group_removed=no
else
group_removed=yes
fi
case $(cat "$shepherd_stdout") in
*"System lacks support for 'signalfd'; using fallback mechanism."*) signalfd_fallback=yes ;;
*) signalfd_fallback=no ;;
esac
[ "$rc_stopped" = yes ] || { echo "Mock rc service did not stop" >&2; exit 1; }
[ "$alias_removed" = yes ] || { echo "Loopback alias was not removed" >&2; exit 1; }
[ "$tmpfs_unmounted" = yes ] || { echo "tmpfs mount was not removed" >&2; exit 1; }
[ "$user_removed" = yes ] || { echo "Temporary user still exists after stop" >&2; exit 1; }
[ "$group_removed" = yes ] || { echo "Temporary group still exists after stop" >&2; exit 1; }
cat >"$metadata_file" <<EOF
workdir=$workdir
config_file=$config_file
socket_file=$socket_file
pid_file=$pid_file
shepherd_log=$shepherd_log
shepherd_stdout=$shepherd_stdout
status_file=$status_file
mock_rc_name=$mock_rc_name
mock_rc_script=$mock_rc_script
mock_rc_log=$mock_rc_log
loopback_alias=$loopback_alias
mount_point=$mount_point
tmpfs_mode=$tmpfs_mode
temp_user=$temp_user
temp_group=$temp_group
temp_id=$temp_id
target_running=$target_running
rc_started=$rc_started
alias_present=$alias_present
tmpfs_mounted=$tmpfs_mounted
user_present=$user_present
group_present=$group_present
rc_stopped=$rc_stopped
alias_removed=$alias_removed
tmpfs_unmounted=$tmpfs_unmounted
user_removed=$user_removed
group_removed=$group_removed
signalfd_fallback=$signalfd_fallback
EOF
if [ -n "$metadata_target" ]; then
mkdir -p "$(dirname "$metadata_target")"
cp "$metadata_file" "$metadata_target"
fi
printf 'PASS freebsd-shepherd-bridge-prototype\n'
printf 'Work directory: %s\n' "$workdir"
printf 'Metadata file: %s\n' "$metadata_file"
if [ -n "$metadata_target" ]; then
printf 'Copied metadata to: %s\n' "$metadata_target"
fi
printf '%s\n' '--- metadata ---'
cat "$metadata_file"