Prototype FreeBSD store management

This commit is contained in:
2026-04-01 11:46:23 +02:00
parent d65b2afb27
commit e404e2e08d
3 changed files with 530 additions and 0 deletions

View File

@@ -0,0 +1,234 @@
#!/bin/sh
set -eu
store_root=${STORE_ROOT:-/frx/store}
state_root=${STATE_ROOT:-/frx/var}
sysconf_root=${SYSCONF_ROOT:-/frx/etc}
fruix_state_dir=$state_root/fruix
fruix_gcroots_dir=$fruix_state_dir/gcroots
store_group=${STORE_GROUP:-fruixbuild}
workdir=${WORKDIR:-$(mktemp -d /tmp/fruix-store-prototype.XXXXXX)}
cleanup_workdir=1
metadata_file=$workdir/freebsd-store-prototype-metadata.txt
access_read_out=$workdir/access-read.out
access_write_err=$workdir/access-write.err
first_gc_listing=$workdir/first-gc-listing.txt
second_gc_listing=$workdir/second-gc-listing.txt
store_root_stat=$workdir/store-root.stat
gcroots_stat=$workdir/gcroots.stat
if [ -n "${WORKDIR:-}" ]; then
cleanup_workdir=0
fi
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
cleanup_workdir=0
fi
cleanup() {
set +e
if [ "$cleanup_workdir" -eq 1 ]; then
rm -rf "$workdir"
fi
}
trap cleanup EXIT INT TERM
ensure_store_group() {
if ! pw groupshow "$store_group" >/dev/null 2>&1; then
if ! sudo pw groupadd "$store_group" -g 35000 >/dev/null 2>&1; then
sudo pw groupadd "$store_group" >/dev/null
fi
group_created=yes
else
group_created=no
fi
group_gid=$(pw groupshow "$store_group" | awk -F: '{print $3}')
}
clean_demo_items() {
sudo find "$store_root" -mindepth 2 -maxdepth 2 -name .fruix-demo -print 2>/dev/null |
while IFS= read -r marker; do
[ -n "$marker" ] || continue
sudo rm -rf "$(dirname "$marker")"
done
sudo rm -f "$fruix_gcroots_dir/demo-root"
}
install_store_item() {
item_name=$1
stage_dir=$2
item_hash=$3
destination=$store_root/${item_hash}-${item_name}
sudo rm -rf "$destination"
sudo mkdir -p "$destination"
sudo cp -a "$stage_dir/." "$destination/"
sudo find "$destination" -type d -exec chmod 0555 {} +
sudo find "$destination" -type f -exec chmod 0444 {} +
if [ -d "$destination/bin" ]; then
sudo find "$destination/bin" -type f -exec chmod 0555 {} +
fi
printf '%s\n' "$destination"
}
mark_reachable() {
item=$1
[ -n "$item" ] || return 0
[ -d "$item" ] || return 0
if grep -Fxq "$item" "$reachable_file" 2>/dev/null; then
return 0
fi
printf '%s\n' "$item" >> "$reachable_file"
if [ -f "$item/.references" ]; then
while IFS= read -r ref; do
[ -n "$ref" ] || continue
mark_reachable "$ref"
done < "$item/.references"
fi
}
run_gc() {
reachable_file=$1
: > "$reachable_file"
if [ -d "$fruix_gcroots_dir" ]; then
for root in "$fruix_gcroots_dir"/*; do
[ -L "$root" ] || continue
mark_reachable "$(readlink -f "$root")"
done
fi
sudo find "$store_root" -mindepth 2 -maxdepth 2 -name .fruix-demo -print |
while IFS= read -r marker; do
item=$(dirname "$marker")
if ! grep -Fxq "$item" "$reachable_file" 2>/dev/null; then
sudo rm -rf "$item"
fi
done
}
mkdir -p "$workdir/stage"
ensure_store_group
sudo install -d -m 0755 -o root -g wheel /frx
sudo install -d -m 1775 -o root -g "$store_group" "$store_root"
sudo install -d -m 0755 -o root -g wheel "$state_root" "$sysconf_root" "$fruix_state_dir" "$fruix_gcroots_dir"
clean_demo_items
sudo stat -f '%Su %Sg %OLp %Sp' "$store_root" > "$store_root_stat"
sudo stat -f '%Su %Sg %OLp %Sp' "$fruix_gcroots_dir" > "$gcroots_stat"
data_stage=$workdir/stage/greeting-data
app_stage=$workdir/stage/hello-app
orphan_stage=$workdir/stage/orphan-data
mkdir -p "$data_stage/share" "$app_stage/bin" "$orphan_stage/share"
printf 'hello-from-frx-store\n' > "$data_stage/share/greeting.txt"
printf 'demo-item\n' > "$data_stage/.fruix-demo"
: > "$data_stage/.references"
data_manifest=$(printf 'name=demo-greeting-data\nfile=share/greeting.txt\ncontent=%s\n' "hello-from-frx-store")
data_hash=$(printf '%s' "$data_manifest" | sha256 -q)
data_path=$(install_store_item demo-greeting-data "$data_stage" "$data_hash")
cat > "$app_stage/bin/demo-hello" <<EOF
#!/bin/sh
exec /bin/cat "$data_path/share/greeting.txt"
EOF
printf '%s\n' "$data_path" > "$app_stage/.references"
printf 'demo-item\n' > "$app_stage/.fruix-demo"
app_manifest=$(printf 'name=demo-hello-app\nfile=bin/demo-hello\nref=%s\n' "$data_path")
app_hash=$(printf '%s' "$app_manifest" | sha256 -q)
app_path=$(install_store_item demo-hello-app "$app_stage" "$app_hash")
printf 'this-should-be-collected\n' > "$orphan_stage/share/orphan.txt"
: > "$orphan_stage/.references"
printf 'demo-item\n' > "$orphan_stage/.fruix-demo"
orphan_manifest=$(printf 'name=demo-orphan-data\nfile=share/orphan.txt\ncontent=%s\n' "this-should-be-collected")
orphan_hash=$(printf '%s' "$orphan_manifest" | sha256 -q)
orphan_path=$(install_store_item demo-orphan-data "$orphan_stage" "$orphan_hash")
sudo ln -sfn "$app_path" "$fruix_gcroots_dir/demo-root"
sudo -u nobody /bin/sh -eu -c "
cat '$data_path/share/greeting.txt'
'$app_path/bin/demo-hello'
" > "$access_read_out"
set +e
sudo -u nobody /bin/sh -eu -c "touch '$store_root/should-not-write'" >/dev/null 2> "$access_write_err"
write_rc=$?
set -e
if [ "$write_rc" -eq 0 ]; then
echo "unexpected unprivileged store write succeeded" >&2
exit 1
fi
reachable_after_first_gc=$workdir/reachable-first.txt
run_gc "$reachable_after_first_gc"
find "$store_root" -maxdepth 1 -mindepth 1 | sort > "$first_gc_listing"
if [ -d "$orphan_path" ]; then
echo "orphan store item survived rooted GC unexpectedly" >&2
exit 1
fi
if [ ! -d "$app_path" ] || [ ! -d "$data_path" ]; then
echo "reachable store items missing after rooted GC" >&2
exit 1
fi
sudo rm -f "$fruix_gcroots_dir/demo-root"
reachable_after_second_gc=$workdir/reachable-second.txt
run_gc "$reachable_after_second_gc"
find "$store_root" -maxdepth 1 -mindepth 1 | sort > "$second_gc_listing"
if [ -d "$app_path" ] || [ -d "$data_path" ]; then
echo "unrooted store items survived GC unexpectedly" >&2
exit 1
fi
cat > "$metadata_file" <<EOF
store_root=$store_root
state_root=$state_root
sysconf_root=$sysconf_root
fruix_state_dir=$fruix_state_dir
fruix_gcroots_dir=$fruix_gcroots_dir
store_group=$store_group
store_group_gid=$group_gid
group_created=$group_created
store_root_stat=$(cat "$store_root_stat")
gcroots_stat=$(cat "$gcroots_stat")
data_hash=$data_hash
app_hash=$app_hash
orphan_hash=$orphan_hash
data_path=$data_path
app_path=$app_path
orphan_path=$orphan_path
nobody_read_output=$(tr '\n' '|' < "$access_read_out")
unprivileged_write_rc=$write_rc
unprivileged_write_err=$(tr '\n' '|' < "$access_write_err")
first_gc_listing_file=$first_gc_listing
second_gc_listing_file=$second_gc_listing
first_gc_listing_begin
$(cat "$first_gc_listing")
first_gc_listing_end
second_gc_listing_begin
$(cat "$second_gc_listing")
second_gc_listing_end
EOF
if [ -n "${METADATA_OUT:-}" ]; then
mkdir -p "$(dirname "$METADATA_OUT")"
cp "$metadata_file" "$METADATA_OUT"
fi
printf 'PASS freebsd-store-prototype\n'
printf 'Metadata file: %s\n' "$metadata_file"
if [ -n "${METADATA_OUT:-}" ]; then
printf 'Copied metadata to: %s\n' "$METADATA_OUT"
fi
printf '%s\n' '--- nobody read output ---'
cat "$access_read_out"
printf '%s\n' '--- first GC listing ---'
cat "$first_gc_listing"
printf '%s\n' '--- second GC listing ---'
cat "$second_gc_listing"
printf '%s\n' '--- metadata ---'
cat "$metadata_file"