Bridge Shepherd to FreeBSD services
This commit is contained in:
111
docs/PROGRESS.md
111
docs/PROGRESS.md
@@ -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
|
||||
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`
|
||||
|
||||
## 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
|
||||
|
||||
@@ -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.
|
||||
|
||||
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!
|
||||
|
||||
142
docs/reports/phase4-freebsd-shepherd-bridge.md
Normal file
142
docs/reports/phase4-freebsd-shepherd-bridge.md
Normal 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
|
||||
104
modules/fruix/shepherd/freebsd.scm
Normal file
104
modules/fruix/shepherd/freebsd.scm
Normal 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))
|
||||
324
tests/shepherd/run-freebsd-shepherd-bridge-prototype.sh
Executable file
324
tests/shepherd/run-freebsd-shepherd-bridge-prototype.sh
Executable 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"
|
||||
Reference in New Issue
Block a user