Compare commits

...

10 Commits

31 changed files with 5999 additions and 1 deletions

360
docs/PLAN_2.md Normal file
View File

@@ -0,0 +1,360 @@
# Fruix on FreeBSD, Path B: Real Guix/Fruix Integration First, Then a Minimal bhyve System
This document extends `docs/PLAN.md` after the completion of Phases 1 through 4 on the current FreeBSD amd64 prototype track. The next major objective is no longer to validate isolated subsystems, but to connect them through a real Guix/Fruix execution path and ultimately boot a minimal Fruix system in bhyve.
Unlike a prototype-image-first strategy, this plan takes **Path B directly**:
- prioritize real Guix/Fruix checkout and runtime integration on FreeBSD,
- connect package lowering, derivation generation, daemon/store behavior, and system composition,
- and only then generate and boot a minimal bhyve image from that integrated path.
This is slower than assembling an image from ad hoc prototypes, but it better preserves the architectural goal: a genuine Fruix system built through Guix-style mechanisms rather than merely a FreeBSD image with Fruix-like parts.
Throughout this plan, the experimental store root remains:
- `/frx/store`
- `/frx/var`
- `/frx/etc`
rather than `/gnu/store`.
## Naming and Fork Identity Policy
This plan assumes that the project is now **Fruix** as a user-facing system, in the same way that the store root has already moved from `/gnu` to `/frx`.
The naming policy for the next phases is:
- the user-facing CLI should become `fruix`
- user-facing daemon, service, help text, generated documentation, and system identity should prefer `fruix` over `guix`
- new fork-specific modules and code should prefer `fruix` namespaces and naming
- the `/frx` path family remains the canonical store/state/config root for FreeBSD work
- internal upstream-derived module namespaces should **not** be renamed mechanically just for branding
- names containing `gnu` should be preserved where they refer to actual GNU concepts or components, such as:
- GNU packages
- GNU Shepherd
- `gnu-build-system`
This means the early integration work will often need to distinguish between:
- the **current upstream-derived internal checkout reality**, where the failing command path is still `./pre-inst-env guix ...`, and
- the **desired Fruix product boundary**, where the operator-facing command should become `fruix`.
Accordingly, the next phases should first make the current checkout runnable, then establish a controlled rename boundary for the user-facing command and related interfaces, rather than attempting a blanket internal rename of every `guix` or `gnu` identifier.
---
## Current State at the End of Phase 4
The following have already been validated on the active FreeBSD amd64 host:
- fixed local Guile path suitable for subprocess-heavy Scheme work
- FreeBSD jail-first build isolation prototype
- privilege drop and build-user isolation prototype
- `/frx/store` prototype with GC-style rooted retention
- reusable FreeBSD GNU build-system adaptation across representative packages
- prototype FreeBSD package-definition/profile layer
- Shepherd build and runtime supervision on FreeBSD
- Shepherd `rc.d` integration prototype
- Shepherd bridge layer for FreeBSD rc.d, loopback/network, tmpfs, and user/group administration
The largest remaining blocker before real system generation is still the live upstream-derived checkout/runtime path, especially:
- `./pre-inst-env guix --version`
- `Wrong type to apply: #<syntax-transformer leave-on-EPIPE>`
That blocker is the first item in the next phase because without it, system composition cannot honestly move from prototype scaffolding to real Fruix/Guix execution. Resolving it is also the prerequisite for introducing a trustworthy `fruix` user-facing command boundary on top of the current checkout.
---
## Phase 5: Real Guix Checkout and Host Runtime Unblocking on FreeBSD
### Intermediate Goal 5.1: Unblock the Uninstalled Checkout Command Path and Establish the `fruix` CLI Boundary
The immediate goal is to move from “the checkout configures and builds some generated scripts” to “the checkout actually runs on FreeBSD”.
Because the current checkout still exposes an upstream-derived `guix` command path, this step must also define how the user-facing `fruix` command is introduced without forcing a premature blanket rename of internal identifiers.
**Verification Goal 5.1:** Investigate and fix the runtime failure in the uninstalled checkout command path:
- `./pre-inst-env guix --version`
- `./pre-inst-env guix repl`
- `./pre-inst-env guix build --help`
Then, once that path works, introduce a first user-facing `fruix` command boundary for the checkout, whether by renamed frontend, generated wrapper, compatibility symlink policy, or another documented transition mechanism.
The investigation must identify whether the `leave-on-EPIPE` failure is due to:
- macro expansion / module ordering assumptions,
- Guile version or module interaction,
- FreeBSD-specific POSIX behavior,
- generated source ordering,
- or a latent upstream bug exposed by FreeBSD.
Produce:
- a dedicated reproducer harness,
- a root-cause report,
- and either:
- an in-repo patch queue for the Guix checkout, or
- a precise upstreamable fix series.
**Success Criteria:** `./pre-inst-env guix --version` succeeds on FreeBSD using the fixed local Guile stack, at least one additional non-trivial checkout subcommand works, and there is a documented first-step user-facing `fruix` frontend policy for the live checkout.
### Intermediate Goal 5.2: Validate Package Lowering and Derivation Generation
Once the checkout is runnable, the next boundary is host-side lowering rather than builder-side phase execution.
**Verification Goal 5.2:** Demonstrate that a real Guix checkout on FreeBSD can lower at least one tiny package to a derivation using the `/frx` store settings. This step should verify:
- package -> bag lowering,
- bag -> derivation lowering,
- imported module closure materialization,
- fixed-output source origin handling,
- and generation of a real derivation file targeting `/frx/store`.
The initial validation target should be intentionally small, such as a previously successful package from the build-system work (`hello`, `which`, or similar).
**Success Criteria:** a real derivation is emitted by Guix on FreeBSD, references `/frx/store`, and can be inspected as a normal derivation rather than only as a prototype metadata file.
### Intermediate Goal 5.3: Establish Store Connection and Minimal Daemon RPC Integration
At this point, the remaining gap is the connection between host-side lowering and actual store-backed builds.
**Verification Goal 5.3:** Connect the runnable checkout to a minimal FreeBSD-aware store/daemon path. This should include:
- confirming `open-connection` / store socket behavior on FreeBSD,
- minimal daemon configuration for `/frx/store`,
- validating store registration and metadata behavior,
- and ensuring that at least one real derivation can be submitted to the daemon.
This phase may still rely on prototype jail/build-user implementations while integrating them incrementally into daemon behavior.
**Success Criteria:** a real checkout command path can contact a FreeBSD-aware daemon/store path and submit at least one derivation-backed build request, with the intended user-facing interface moving toward `fruix`.
---
## Phase 6: Real FreeBSD-Backed Store Builds Through Guix/Fruix Mechanisms
### Intermediate Goal 6.1: Build a Real Package Into `/frx/store`
The builder-side phase runners proved that GNU packages can build on FreeBSD. This phase requires proving that they can do so through real lowering + store + daemon plumbing.
**Verification Goal 6.1:** Build at least one small GNU package from a real Guix package definition all the way into `/frx/store`, through the daemon path. Validate:
- store path creation,
- output registration,
- runtime execution from the store,
- and preservation of declared references.
**Success Criteria:** `fruix build`-style execution succeeds for at least one real package on FreeBSD and produces a usable output in `/frx/store`, even if the underlying checkout still retains transitional upstream-derived command plumbing.
### Intermediate Goal 6.2: Integrate FreeBSD Jail/Privilege Prototypes Into the Real Build Path
The current jail/build-user work is still prototype-scoped. It now needs to become part of the actual FreeBSD build path.
**Verification Goal 6.2:** Adapt the daemon build path so that real derivation builds use the already validated FreeBSD primitives:
- thin jails,
- explicit `nullfs` mount plans,
- dropped build users,
- `/frx/store` read semantics,
- and per-build writable scratch/output directories.
At least one real package build should be observed executing inside that integrated jail/build-user path.
**Success Criteria:** a real Guix/Fruix build runs inside a FreeBSD jail with dropped build credentials and succeeds into `/frx/store`.
### Intermediate Goal 6.3: Validate Profiles and User-Facing Package Installation
Once real builds land in the store, the next user-facing layer is profile generation.
**Verification Goal 6.3:** Demonstrate that a user profile can be produced from real store items on FreeBSD, not only from the earlier package/profile prototype layer. Validate:
- profile symlink generation,
- transaction semantics at least in a minimal form,
- PATH usability,
- and user access to built packages.
**Success Criteria:** a package built through the real FreeBSD store path can be installed into a user profile and executed through that profile.
---
## Phase 7: Declarative Fruix System Composition on FreeBSD
### Intermediate Goal 7.1: Define a Minimal Fruix Operating-System Model
The package layer alone is not enough; there must be a system-level object that describes an installable/bootable Fruix system.
**Verification Goal 7.1:** Define a minimal Fruix system model for FreeBSD, analogous in spirit to Guix Systems operating-system record, that can describe:
- kernel and boot assets,
- essential FreeBSD base packages,
- `/etc` configuration files,
- activation requirements,
- Shepherd configuration,
- file systems,
- users/groups,
- and host identity settings.
This may begin in Fruix-specific modules rather than attempting immediate full upstream Guix OS integration.
**Success Criteria:** a declarative system description can be evaluated into a coherent system closure specification rather than only a loose collection of packages.
### Intermediate Goal 7.2: Generate a Minimal System Closure and Activation Payload
The next step is to turn the system model into concrete filesystem content.
**Verification Goal 7.2:** Generate a minimal system closure for FreeBSD that includes at least:
- FreeBSD kernel and boot files,
- minimal userland,
- required `/etc` files,
- Shepherd service definitions and startup hook,
- activation scripts,
- and references into `/frx/store`.
This phase should decide and document whether the first boot target uses:
- FreeBSD init + `rc.d` to launch Shepherd, or
- Shepherd as PID 1.
For the first integrated boot target, FreeBSD init + `rc.d` is acceptable if it is generated declaratively from the system definition.
**Success Criteria:** a system closure directory tree can be generated reproducibly from a declarative Fruix system description.
### Intermediate Goal 7.3: Validate an Installable Root Filesystem Tree
Before generating a disk image, validate that the system closure can be materialized as a rootfs tree suitable for installation or image creation.
**Verification Goal 7.3:** Build a root filesystem tree that contains:
- bootable kernel/loader content,
- generated configuration,
- declared users/groups,
- Shepherd launch integration,
- and enough runtime content to reach a login prompt or equivalent ready state.
**Success Criteria:** the generated rootfs is internally coherent and passes static validation for image construction.
---
## Phase 8: FreeBSD/Fruix Image Construction for bhyve
### Intermediate Goal 8.1: Create a Reproducible Disk Image Build Path
Now that the system closure exists, produce a real boot medium from it.
**Verification Goal 8.1:** Create tooling to generate a bootable bhyve-compatible disk image from the Fruix system closure. This should include:
- partition map creation,
- filesystem creation,
- rootfs population,
- boot asset installation,
- and serial-console-friendly configuration.
The first target should prefer the simplest debuggable bhyve path, likely:
- raw disk image,
- UEFI boot,
- FreeBSD kernel/loader,
- serial console.
**Success Criteria:** a reproducible image file is generated from the declarative Fruix system description and passes boot-structure sanity checks.
### Intermediate Goal 8.2: Integrate Image Generation With the Fruix System Definition Layer
The image builder should not remain a detached shell-only utility.
**Verification Goal 8.2:** Connect image generation to the system description layer so that the image is the output of system composition, not a manual afterthought. Ideally, this begins to resemble a minimal FreeBSD analogue of `fruix system image`, even if some internal machinery still derives from upstream Guix code paths.
**Success Criteria:** a single declarative Fruix system description can drive image generation end-to-end.
---
## Phase 9: Boot a Minimal Fruix System in bhyve
### Intermediate Goal 9.1: Reach a Serial-Console Boot Milestone
The first VM target should be intentionally narrow: boot the generated image and prove it reaches a known-good ready state.
**Verification Goal 9.1:** Create a bhyve launcher/validation harness that:
- boots the generated image,
- captures serial output,
- waits for a ready marker,
- and tears down the VM cleanly.
The ready marker may initially be one of:
- a login prompt,
- a Shepherd status marker,
- or a deterministic boot-complete message.
**Success Criteria:** the Fruix image boots in bhyve and reaches a deterministic serial-console ready state.
### Intermediate Goal 9.2: Validate In-Guest Shepherd and Core Services
After basic boot, verify that the system behaves like a Fruix system and not merely a FreeBSD guest with some files copied in.
**Verification Goal 9.2:** Validate in the running bhyve guest that:
- Shepherd is active,
- the expected essential services are started,
- the generated configuration is in effect,
- and the system closure in the guest matches the declared build outputs.
**Success Criteria:** the bhyve guest boots the generated Fruix system and runs Shepherd-managed core services successfully.
### Intermediate Goal 9.3: Validate Minimal Operator Usability
The final milestone for the first bhyve system is not completeness, but minimal usefulness.
**Verification Goal 9.3:** Confirm that the guest supports a minimal operator workflow, such as:
- console login or root shell access,
- inspection of Shepherd service status,
- reading generated configuration files,
- and optionally basic networking.
**Success Criteria:** a human operator can boot the bhyve guest and verify a minimally usable Fruix environment from inside the VM.
---
## Optional Phase 10: Replace Transitional Pieces With More Native Fruix/Guix Machinery
This phase is intentionally deferred until after the first real bhyve success.
Possible follow-up work includes:
- replacing residual prototype-only composition layers with true Guix-native system-generation mechanisms,
- deeper integration of FreeBSD package definitions with real package records and lowering paths,
- improving daemon/store metadata fidelity,
- turning image generation into a true `fruix` command,
- and evaluating whether Shepherd-as-PID-1 is worth pursuing beyond the validated `rc.d` bridge path.
---
## Recommended Immediate Order of Work
If work begins on this plan now, the recommended near-term order is:
1. **Phase 5.1** — fix the `leave-on-EPIPE` checkout/runtime blocker
2. **Phase 5.2** — prove real derivation generation on FreeBSD with `/frx/store`
3. **Phase 5.3 / 6.1** — connect real builds to the daemon/store path and land one package in `/frx/store`
4. **Phase 6.2** — attach the real build path to FreeBSD jail/build-user isolation
5. **Phase 7** — define the minimal declarative Fruix system model and rootfs closure
6. **Phase 89** — generate and boot the first bhyve image
This ordering keeps the project honest: the bhyve image milestone will be the output of genuine Guix/Fruix integration work rather than a prototype assembled around unresolved core runtime issues. It also keeps the naming transition disciplined: user-facing `fruix` surfaces can be introduced deliberately on top of a working checkout instead of via a destabilizing whole-tree rename.
---
## Final Target of This Plan
The end goal of this Path B plan is:
> A minimal Fruix system for FreeBSD, generated through real Guix/Fruix-style mechanisms, exposed to operators through `fruix` user-facing interfaces, stored under `/frx`, bootable in bhyve, and capable of running Shepherd-managed core services.
This target explicitly assumes selective renaming rather than blanket renaming: Fruix at the product boundary, stable upstream-derived internal names where they continue to provide architectural or maintenance value, and preserved GNU naming where it refers to genuine GNU components.
That is the next major milestone after the completed foundational and prototype phases.

View File

@@ -972,3 +972,754 @@ Next recommended step:
2. if possible, use outputs or dependency relationships realistic enough to model how a future FreeBSD Guix daemon would retain referenced store items
3. continue carrying the separate Guix checkout runtime blocker:
- investigate the `leave-on-EPIPE` failure in `./pre-inst-env guix --version`
## 2026-04-01 — Phase 2.3 completed: `/frx/store` prototype validated on FreeBSD
Completed work:
- added a runnable `/frx/store` prototype harness:
- `tests/store/run-freebsd-store-prototype.sh`
- wrote the Phase 2.3 report:
- `docs/reports/phase2-freebsd-store-prototype.md`
- created and exercised the operator-requested `/frx` layout on-host:
- `/frx/store`
- `/frx/var`
- `/frx/etc`
- `/frx/var/fruix/gcroots`
- created a store group for the prototype path:
- `fruixbuild`
- ran the store prototype successfully and captured metadata under:
- `/tmp/freebsd-store-prototype-metadata.txt`
Important findings:
- the current host now has a working `/frx/store` prototype owned as:
- `root:fruixbuild`
with mode:
- `drwxrwxr-t`
- the prototype successfully created content-addressed demo store items under `/frx/store` using hash-based names
- the demo item set included:
- rooted greeting data
- a rooted app referencing that data through an absolute store path
- an unrooted orphan item intended for collection
- an unprivileged user (`nobody`) could:
- read store data
- execute the demo app from the store
- the same unprivileged user could not:
- create files directly in `/frx/store`
and the observed failure was:
- `Permission denied`
- the prototype GC logic followed rooted references successfully:
- with a GC root present, the app and its referenced data survived while the orphan item was collected
- after removing the GC root, the remaining demo items were collected as well
- the demo store returned to an empty state after the second GC pass, so the host is left with the `/frx` skeleton but without lingering prototype payloads
Current assessment:
- Phase 2.3 is now satisfied on the current FreeBSD prototype track
- the core store assumptions needed for a FreeBSD Guix-daemon design have practical validation now:
- `/frx/store` path viability
- root-controlled mutation
- unprivileged read access
- immutable absolute store references
- root-managed GC roots and mark/sweep retention behavior
- remaining gaps are now above this architectural layer rather than below it:
- real derivation registration
- SQLite-backed store metadata
- daemon RPC integration
- actual package lowering/build submission using these mechanisms
## 2026-04-01 — Phase 2 completed on the current FreeBSD prototype track
Phase 2 is now considered complete for the active FreeBSD amd64 prototype path.
Why this milestone is satisfied:
- **Phase 2.1** success criteria were met:
- a detailed jail-first build-isolation design was produced
- a runnable prototype successfully executed a build command in a restricted FreeBSD jail
- **Phase 2.2** success criteria were met:
- a concrete C privilege-dropping implementation was added
- build-user credential drop, inability to regain root, and concurrent cross-build isolation were demonstrated
- **Phase 2.3** success criteria were met on the prototype track:
- a working `/frx/store` equivalent was established
- content-addressed demo store items were created and consumed
- unprivileged read vs. privileged write behavior was validated
- garbage-collection behavior over rooted references was demonstrated
Important scope note:
- this completes the **core daemon architecture adaptation** milestone, not a full Guix-daemon port
- the separate real-checkout blocker from Phase 1 remains relevant for later integration work:
- `./pre-inst-env guix --version` still fails with `Wrong type to apply: #<syntax-transformer leave-on-EPIPE>`
- however, that runtime issue no longer blocks the specific Phase 2 architectural deliverables because the jail, privilege, and store assumptions have now been validated independently on FreeBSD
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`
Next recommended step:
1. begin Phase 3.1 by adapting Guix build-system expectations to the now-validated jail/privilege/store model on FreeBSD
2. carry forward the concrete real-checkout runtime blocker for later integration work:
- investigate the `leave-on-EPIPE` failure in `./pre-inst-env guix --version`
3. continue using `/frx/store` rather than `/gnu/store` for FreeBSD store experiments
## 2026-04-01 — Phase 3.1 completed: reusable FreeBSD GNU build-system adaptation validated across five packages
Completed work:
- added a reusable Scheme runner for FreeBSD-adapted GNU package builds:
- `tests/build-system/gnu-package-freebsd-phase-runner.scm`
- added a shell wrapper for the generic runner:
- `tests/build-system/run-gnu-package-freebsd-phase-runner.sh`
- added a five-package validation matrix:
- `tests/build-system/run-freebsd-gnu-package-matrix.sh`
- wrote the Phase 3.1 report:
- `docs/reports/phase3-freebsd-gnu-build-system.md`
- ran the matrix successfully and captured summary metadata under:
- `/tmp/freebsd-gnu-package-matrix-summary.txt`
Important findings:
- the build adaptation is now centralized rather than package-specific and is applied through a dedicated pre-configure FreeBSD environment phase
- the adaptation consistently uses:
- GNU `gmake` via a `make` path shim
- FreeBSD Clang via `cc`/`gcc` and `c++`/`g++` tool shims
- `CONFIG_SHELL=/bin/sh`
- `/usr/local` include/library/pkg-config search paths
- five representative GNU packages from current Guix package definitions now build successfully through the adapted runner on the current FreeBSD amd64 host:
- `hello` `2.12.3`
- `which` `2.21`
- `time` `1.9`
- `patch` `2.8`
- `nano` `8.7.1`
- the resulting binaries executed correctly with deterministic checks appropriate to each package:
- `hello` -> `Hello, world!`
- `which` -> `/bin/sh`
- `time` -> `time (GNU Time) 1.9`
- `patch` -> `GNU patch 2.8`
- `nano` -> `GNU nano, version 8.7.1`
- the matrix also validated a package with meaningful runtime dependencies:
- `nano` linked against FreeBSD/base and `/usr/local` libraries including `libintl`, `libmagic`, `libncursesw`, `libtinfow`, and `libz`
- one package-specific FreeBSD test boundary was recorded explicitly instead of being hidden:
- `time` required `RUN_TESTS=0` because the upstream `time-max-rss` test was not reliable on this host
Current assessment:
- Phase 3.1 is now satisfied on the current prototype track
- the main question has shifted from “can adapted GNU builder phases run on FreeBSD?” to “how should FreeBSD system components themselves be described and installed as profile-usable packages?”
- the next step is therefore Phase 3.2: define a minimal FreeBSD package set with explicit dependencies and validate profile-style installation/usability
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`
Next recommended step:
1. complete Phase 3.2 by defining a minimal FreeBSD system package set with explicit dependency relationships and profile-style installation validation
2. carry forward the concrete real-checkout runtime blocker for later integration work:
- investigate the `leave-on-EPIPE` failure in `./pre-inst-env guix --version`
3. continue using `/frx/store` rather than `/gnu/store` for future FreeBSD store experiments when the prototype work needs a persistent store root
## 2026-04-01 — Phase 3.2 completed: FreeBSD system package-definition prototype and profile validation added
Completed work:
- added a Guix-style FreeBSD system package-definition prototype module:
- `modules/fruix/packages/freebsd.scm`
- added a Scheme harness to materialize those package definitions into store-like outputs and a merged profile:
- `tests/packages/freebsd-package-profile-prototype.scm`
- added a shell wrapper for that harness:
- `tests/packages/run-freebsd-package-profile-prototype.sh`
- installed the missing host shell dependency needed to satisfy the requested package set:
- `bash`
- wrote the Phase 3.2 report:
- `docs/reports/phase3-freebsd-package-definitions.md`
- ran the profile prototype successfully and captured metadata under:
- `/tmp/freebsd-package-profile-prototype-metadata.txt`
Important findings:
- the prototype now defines a minimal FreeBSD core package set covering the categories requested by Phase 3.2:
- kernel
- kernel headers
- libc
- userland utilities
- development tools (`clang`, `make`, autotools)
- minimum system libraries (`openssl`, `zlib`)
- shells (`sh`, `bash`)
- the current package-definition layer uses an explicit Guix-like record shape with fields for:
- name
- version
- build system
- inputs
- synopsis/description/home-page/license
- install plan
- explicit dependency relationships are now encoded and resolved recursively during materialization, including examples such as:
- `freebsd-libc` -> `freebsd-kernel-headers`
- `freebsd-userland` -> `freebsd-libc`, `freebsd-sh`
- `freebsd-clang-toolchain` -> `freebsd-libc`, `freebsd-kernel-headers`, `freebsd-sh`
- `freebsd-autotools` -> `freebsd-gmake`, `freebsd-bash`, `freebsd-libc`
- the harness successfully materialized:
- `11` core package outputs
into a store-like directory tree under the work directory
- it then merged those outputs into a development profile and validated that the profile contains working:
- `bash`
- `make`
- `autoconf`
- `cc`
- kernel image path
- kernel-header path
- core shared-library paths
- the generated profile compiled and ran a C test program successfully, with observed output:
- `hello-from-freebsd-profile`
- for executables installed under `bin/`, the prototype uses wrappers that `exec` the host tool by absolute path; this preserved correct behavior for prefix-sensitive tools such as `autoconf`
Current assessment:
- Phase 3.2 is now satisfied on the current prototype track
- Phase 3 as a whole is now completed on the current FreeBSD amd64 path because both:
- adapted GNU build-system execution, and
- minimal FreeBSD system package-definition/profile validation
have been demonstrated successfully
- the next remaining project milestone is now Phase 4, centered on Shepherd rather than package-building foundations
## 2026-04-01 — Phase 3 completed on the current FreeBSD prototype track
Phase 3 is now considered complete for the active FreeBSD amd64 prototype path.
Why this milestone is satisfied:
- **Phase 3.1** success criteria were met on the prototype track:
- a reusable FreeBSD adaptation layer for GNU builder phases was added
- five representative GNU packages built successfully through that adapted runner
- the resulting binaries executed correctly on the host
- **Phase 3.2** success criteria were met on the prototype track:
- a minimal FreeBSD system package-definition layer was added
- explicit dependency relationships were modeled and resolved
- the package outputs installed into a merged profile successfully
- the generated profile was validated by compiling and running a test program with the staged toolchain
Important scope note:
- this completes the **build-system adaptation milestone** in prototype form, not the full Guix package-lowering/daemon integration path
- the earlier concrete upstream/runtime blocker still exists for later integration work:
- `./pre-inst-env guix --version` fails with `Wrong type to apply: #<syntax-transformer leave-on-EPIPE>`
- however, that blocker no longer prevents Phase 4 work because the core build-system and package-definition assumptions have now been validated independently
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`
Next recommended step:
1. begin Phase 4.1 by validating whether Shepherd itself now builds and runs as a regular service on FreeBSD with the fixed local Guile path
2. carry forward the separate real-checkout runtime blocker for later integration work:
- investigate the `leave-on-EPIPE` failure in `./pre-inst-env guix --version`
3. continue using `/frx/store` rather than `/gnu/store` for future FreeBSD integration experiments when a persistent store root is required
## 2026-04-01 — Phase 4.1 completed: Shepherd built and validated as a regular FreeBSD service manager
Completed work:
- added a reproducible local Guile Fibers build harness:
- `tests/shepherd/build-local-guile-fibers.sh`
- added a reproducible local Shepherd build harness:
- `tests/shepherd/build-local-shepherd.sh`
- added a runnable multi-service Shepherd validation harness for FreeBSD:
- `tests/shepherd/run-freebsd-shepherd-service-prototype.sh`
- wrote the Phase 4.1 report:
- `docs/reports/phase4-freebsd-shepherd-service.md`
- ran the service-management prototype successfully and captured metadata under:
- `/tmp/freebsd-shepherd-service-metadata.txt`
Important findings:
- the current FreeBSD path now has a working local Shepherd build based on:
- local fixed Guile
- locally installed Guile Fibers `1.4.2`
- Shepherd `1.0.9`
- Shepherd build/install required one concrete FreeBSD-specific toolchain adaptation:
- `SED=/usr/local/bin/gsed`
because the install phase edits wrapper scripts using GNU `sed -i` syntax that base FreeBSD `sed` does not accept
- at runtime, Shepherd reports:
- `System lacks support for 'signalfd'; using fallback mechanism.`
but the fallback path works correctly for supervision on this host
- the prototype successfully validated all requested regular-service capabilities:
- start/stop via `herd`
- dependency handling
- status monitoring
- crash/respawn behavior
- privilege-aware execution
- the concrete service set used for validation included:
- an unprivileged heartbeat logger
- a loopback HTTP service
- a dependent file-monitor service
- a crash-once respawn test service
- observed metadata confirmed:
- `logger_running=yes`
- `web_running=yes`
- `monitor_running=yes`
- `crashy_running=yes`
- `logger_uid=65534` (`nobody`)
- `http_response=shepherd-freebsd-ok`
- `monitor_detected=detected`
- `crashy_counter=2`
Current assessment:
- Phase 4.1 is now satisfied on the current FreeBSD prototype track
- Shepherd is no longer just a theoretical later step; it now builds and supervises multiple services correctly on the host when paired with the fixed local Guile stack
- the next question is no longer “can Shepherd run on FreeBSD at all?” but “what is the best FreeBSD init-integration strategy for it on this prototype path?”
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`
Next recommended step:
1. complete Phase 4.2 by prototyping how Shepherd should be launched and stopped through FreeBSD init conventions while validating boot/shutdown dependency ordering for essential services
2. after that, bridge Shepherd to key FreeBSD service concepts such as rc.d management, loopback/network configuration, filesystem setup, and temporary user/group administration
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.2 completed: FreeBSD rc.d init-integration prototype validated for Shepherd
Completed work:
- added a runnable FreeBSD init-integration prototype harness:
- `tests/shepherd/run-freebsd-shepherd-init-prototype.sh`
- wrote the Phase 4.2 report:
- `docs/reports/phase4-freebsd-shepherd-init-integration.md`
- ran the init-integration prototype successfully and captured metadata under:
- `/tmp/freebsd-shepherd-init-metadata.txt`
Important findings:
- a real temporary FreeBSD `rc.d` script can successfully launch the locally built Shepherd daemon through the standard:
- `service <name> onestart`
path
- the same wrapper can stop it cleanly through:
- `service <name> onestop`
using `herd ... stop root` under the hood
- the prototype automatically started a minimal essential-service graph at daemon launch consisting of:
- `filesystems`
- `system-log`
- `networking`
- `login`
- observed startup order matched the declared dependency chain exactly:
- `start:filesystems`
- `start:system-log`
- `start:networking`
- `start:login`
- observed shutdown order matched the expected reverse dependency order exactly:
- `stop:login`
- `stop:networking`
- `stop:system-log`
- `stop:filesystems`
- the rc.d wrapper reported the Shepherd instance as running while active:
- `rc_status=running`
- the prototype again observed the expected FreeBSD runtime note:
- `System lacks support for 'signalfd'; using fallback mechanism.`
and confirmed that it does not prevent correct boot/shutdown ordering behavior
Current assessment:
- Phase 4.2 is now satisfied on the current prototype track as an init-integration prototype
- the key result is that Shepherd can already be launched and stopped through native FreeBSD service-management conventions while preserving dependency-based startup and shutdown semantics
- the remaining Phase 4 work is now specifically about bridging Shepherd services to concrete FreeBSD host-management concepts rather than basic daemon launch or service ordering
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`
Next recommended step:
1. complete Phase 4.3 by adding a small FreeBSD Shepherd bridge layer for rc.d-style services, loopback/network configuration, filesystem setup, and temporary user/group administration
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
## 2026-04-01 — Planning update: Fruix naming policy clarified for post-Phase-4 work
Completed work:
- added a new post-Phase-4 planning document:
- `docs/PLAN_2.md`
- updated that plan to clarify the naming policy for the fork going forward
Key planning decision:
- **Fruix** is now the intended user-facing product identity
- the user-facing CLI should become:
- `fruix`
- `/frx` remains the canonical store/state/config root on the FreeBSD path
- however, the plan explicitly avoids a blanket rename of all upstream-derived internal identifiers
- in particular:
- internal `guix` namespaces may remain temporarily where needed for compatibility and maintenance
- `gnu` names are preserved where they refer to real GNU concepts or components such as GNU packages, GNU Shepherd, or `gnu-build-system`
- new fork-specific modules and user-facing surfaces should prefer `fruix` naming
Current assessment:
- the naming direction is now clearer for the next integration batch
- Phase 5 and beyond should aim to:
- first make the upstream-derived checkout runnable on FreeBSD,
- then introduce a deliberate `fruix` command boundary,
- rather than destabilizing the codebase with a whole-tree `guix`/`gnu` rename too early
## 2026-04-01 — Phase 5.1 completed: checkout runtime unblocked and first `fruix` frontend boundary established
Completed work:
- added a reusable phase-5 checkout setup helper:
- `tests/guix/setup-phase5-checkout.sh`
- added a checkout runtime patch queue for the upstream-derived source tree:
- `tests/guix/patches/phase5-checkout-runtime.patch`
- added a FreeBSD daemon/build patch queue needed for later phase-5 work:
- `tests/guix/patches/phase5-guix-daemon-freebsd.patch`
- added a runtime validation harness:
- `tests/guix/run-phase5-checkout-runtime.sh`
- wrote the Phase 5.1 report:
- `docs/reports/phase5-checkout-runtime-freebsd.md`
- ran the runtime harness successfully and captured metadata under:
- `/tmp/phase5-runtime-metadata.txt`
Important findings:
- the earlier checkout blocker
- `./pre-inst-env guix --version`
- `Wrong type to apply: #<syntax-transformer leave-on-EPIPE>`
is now explained by top-level definition ordering in `guix/ui.scm`:
- `show-version-and-exit` called `leave-on-EPIPE` before the syntax transformer was defined later in the file
- on this FreeBSD path, that became a runtime application of a syntax-transformer object instead of a macro expansion site
- the phase-5 runtime patch fixes this by:
- making `(guix ui)` explicitly non-declarative
- rewriting `show-version-and-exit` to use direct `catch 'system-error` handling
- parameterizing `program-name` in `guix-main`
- deriving the top-level version banner name from `program-name`
- making `(guix scripts repl)` explicitly non-declarative as well
- the checkout now successfully runs the following commands on FreeBSD:
- `./pre-inst-env guix --version`
- `./pre-inst-env guix repl --help`
- `./pre-inst-env guix build --help`
- the first user-facing Fruix command boundary is now implemented in the checkout setup via:
- `scripts/fruix`
as a front-end alias next to `scripts/guix`
- observed runtime metadata confirmed:
- `first_guix_version_line=guix (GNU Guix) ...`
- `first_fruix_version_line=fruix (GNU Guix) ...`
- this matches the agreed naming policy:
- Fruix at the user-facing boundary
- stable upstream-derived internal `guix`/`gnu` names unless there is a concrete reason to rename them
Current assessment:
- Phase 5.1 is now satisfied on the current FreeBSD prototype track
- the key boundary has shifted from “the checkout still crashes immediately” to “the checkout runs, and can now be used as the basis for real derivation/store experiments”
- the next step is to prove that a real derivation can be emitted against `/frx/store` from the now-runnable checkout
## 2026-04-01 — Phase 5.2 completed: real derivation generation validated against `/frx/store`
Completed work:
- added a runnable derivation-generation harness:
- `tests/guix/run-phase5-derivation-generation.sh`
- wrote the Phase 5.2 report:
- `docs/reports/phase5-derivation-generation-freebsd.md`
- ran the derivation-generation harness successfully and captured metadata under:
- `/tmp/phase5-derivation-metadata.txt`
Important findings:
- the now-runnable checkout can successfully use a real daemon/store connection on FreeBSD to lower a package through:
- `package->bag`
- `bag->derivation`
- the emitted derivation is a real `/frx/store` derivation path rather than an ad hoc placeholder or shell metadata artifact
- the validation used a deliberately minimal custom package with a custom low-level build system so that this subphase isolates the real lowering/store boundary without being dominated by still-unresolved upstream bootstrap assumptions for full native FreeBSD package graphs
- observed metadata confirmed:
- `bag_name=phase5-freebsd-lowering-0`
- `bag_host_inputs=("source")`
- `drv_path=/frx/store/...-phase5-freebsd-lowering-0.drv`
- `out_path=/frx/store/...-phase5-freebsd-lowering-0`
- this means the key architectural step is now real and no longer hypothetical:
- a package object in the checkout can be lowered to a real derivation targeting `/frx/store` on FreeBSD
Current assessment:
- Phase 5.2 is now satisfied on the current FreeBSD prototype track
- the next step is no longer “can we emit a derivation at all?” but “can the same daemon/store path accept and execute a derivation-backed build request successfully?”
## 2026-04-01 — Phase 5.3 completed: minimal daemon/store RPC integration validated on FreeBSD
Completed work:
- added a runnable daemon/store RPC validation harness:
- `tests/guix/run-phase5-daemon-rpc.sh`
- wrote the Phase 5.3 report:
- `docs/reports/phase5-daemon-rpc-freebsd.md`
- ran the daemon/store RPC harness successfully and captured metadata under:
- `/tmp/phase5-daemon-rpc-metadata.txt`
Important findings:
- the patched checkout can now contact a real FreeBSD-aware daemon over a Unix socket and submit a derivation-backed build request successfully
- the resulting build path is a real `/frx/store` output rather than a simulated prototype artifact
- the successful metadata path now includes the full minimal chain needed for later system work:
- checkout command path
- daemon RPC
- derivation submission
- build execution
- store output materialization
- observed metadata confirmed:
- `drv_path=/frx/store/...-phase5-freebsd-daemon-build-0.drv`
- `out_path=/frx/store/...-phase5-freebsd-daemon-build-0`
- `payload=phase5-daemon-build-source`
- `source_path=/frx/store/...-phase5-source.txt`
- this step was exercised through the Fruix-facing checkout boundary:
- `./pre-inst-env fruix repl -- ...`
which means the user-facing transition is now connected to actual daemon/store activity, not just to help text or version banners
Current assessment:
- Phase 5.3 is now satisfied on the current FreeBSD prototype track
- the project now has a real but narrow end-to-end host-side execution path on FreeBSD:
- runnable checkout
- Fruix front-end boundary
- real derivation emission
- daemon/store RPC
- successful derivation-backed build into `/frx/store`
## 2026-04-01 — Phase 5 completed on the current FreeBSD prototype track
Phase 5 is now considered complete for the active FreeBSD amd64 prototype path.
Why this milestone is satisfied:
- **Phase 5.1** success criteria were met on the prototype track:
- the checkout runtime blocker around `leave-on-EPIPE` was root-caused and fixed in the patch queue
- the uninstalled checkout command path now runs on FreeBSD
- a first user-facing `fruix` command boundary was established
- **Phase 5.2** success criteria were met on the prototype track:
- a real package object was lowered through `package->bag` and `bag->derivation`
- a real derivation was emitted targeting `/frx/store`
- **Phase 5.3** success criteria were met on the prototype track:
- a real checkout command path contacted a FreeBSD-aware daemon/store path
- that path accepted and executed a derivation-backed build request
- the resulting output was materialized successfully in `/frx/store`
Important scope note:
- this completes the **real checkout and host runtime unblocking milestone** on the current prototype track, not full upstream-package-graph support for arbitrary native FreeBSD package builds yet
- the successful derivation/build path currently uses a deliberately minimal custom package/build-system path to isolate real daemon/store viability from still-unresolved upstream bootstrap and package-graph assumptions for native FreeBSD
- nevertheless, the core Phase 5 question has now been answered positively:
- the checkout runs
- real derivations can be emitted
- the daemon can be built far enough to serve store RPC
- and derivation-backed builds can succeed into `/frx/store`
Next recommended step:
1. begin Phase 6.1 by moving from the minimal custom derivation-backed package path to at least one real FreeBSD store-backed package build driven by Fruix/Guix mechanisms
2. then integrate the already validated jail/build-user model more directly into the live daemon build path
3. continue preserving the selective Fruix naming policy:
- Fruix at the product boundary
- `/frx` as the canonical store root
- stable upstream-derived internal names unless there is strong architectural value in renaming them

View File

@@ -1,4 +1,4 @@
Your task is described in ./docs/PLAN.md. Current progress is stored in ./docs/PROGRESS.md.
Your task is described in ./docs/PLAN_2.md. Current progress is stored in ./docs/PROGRESS.md.
Perform the next step towards the final goal. Update the progress file and `git commit` after each subphase (or even in between, if adequate).
@@ -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!

View File

@@ -0,0 +1,189 @@
# Phase 2.3: `/frx/store` prototype for FreeBSD
Date: 2026-04-01
## Summary
This step establishes a working FreeBSD prototype for the Guix store concept under the operator-requested `/frx` prefix.
Added file:
- `tests/store/run-freebsd-store-prototype.sh`
The prototype creates and validates:
- `/frx/store`
- `/frx/var`
- `/frx/etc`
- `/frx/var/fruix/gcroots`
It then installs a few content-addressed demo store items, verifies that an unprivileged user can read and execute them but cannot write to the store, and exercises a simple reference-following garbage-collection pass.
## Run command
```sh
METADATA_OUT=/tmp/freebsd-store-prototype-metadata.txt \
./tests/store/run-freebsd-store-prototype.sh
```
## Store layout and permission model
Observed store metadata:
- store root: `/frx/store`
- store state root: `/frx/var/fruix`
- gc roots: `/frx/var/fruix/gcroots`
- store group: `fruixbuild`
- observed store root mode: `drwxrwxr-t`
- observed store root ownership: `root:fruixbuild`
This matches the intended high-level Guix model closely enough for the prototype track:
- daemon/root can create and remove store items
- ordinary users can traverse and read store items
- ordinary users cannot write new store entries directly
## Content-addressed demo items
The prototype creates three demo store items using hashes derived from explicit manifests:
1. **greeting data**
- path shape: `/frx/store/<hash>-demo-greeting-data`
- contains `share/greeting.txt`
2. **hello app**
- path shape: `/frx/store/<hash>-demo-hello-app`
- contains `bin/demo-hello`
- references the greeting-data store path through an absolute store reference
3. **orphan data**
- path shape: `/frx/store/<hash>-demo-orphan-data`
- intentionally left unrooted so GC should collect it
Each store item also carries prototype metadata files:
- `.fruix-demo`
- `.references`
The `.references` file is used by the prototype GC pass to retain transitive dependencies.
## Readability and write protection checks
The prototype validates store access as the unprivileged `nobody` user.
Observed successful unprivileged reads/execution:
```text
hello-from-frx-store
hello-from-frx-store
```
Those two lines come from:
- directly reading the data file
- executing the app script, which reads the referenced data store path
Observed failed unprivileged write attempt:
```text
touch: /frx/store/should-not-write: Permission denied
```
This demonstrates the critical store property needed for later daemon work:
- store contents are consumable by unprivileged users
- store mutation remains daemon/root controlled
## Garbage-collection prototype
### Rooted GC pass
The prototype creates a GC root symlink:
- `/frx/var/fruix/gcroots/demo-root` -> `demo-hello-app`
The GC algorithm then:
1. walks GC roots
2. marks the rooted item reachable
3. follows `.references` transitively
4. removes demo items not in the reachable set
Observed store listing after the rooted GC pass:
```text
/frx/store/5aa68380f937120deaf9befe3390a563a3631672e2ab5d19b5498e2c288d7277-demo-hello-app
/frx/store/d182f3489191dae9d4b1768e0e7afbfae0143b6d08750a058589437ccd5e0af3-demo-greeting-data
```
Result:
- rooted app retained
- referenced greeting data retained
- orphan data removed
### Unrooted GC pass
The prototype then removes the GC root and reruns the same mark-and-sweep logic.
Observed store listing after the second GC pass:
```text
<empty>
```
Result:
- app removed
- transitive dependency removed
- demo store returned to an empty state
## What this demonstrates for the FreeBSD port
### 1. `/frx/store` works as a practical stand-in for `/gnu/store`
The operator-requested `/frx` prefix is now exercised as a real on-host store root with:
- correct directory creation
- permissions resembling Guix store expectations
- content-addressed path naming
- shared read access
- root-controlled mutation
### 2. Store references can be modeled directly on FreeBSD
The `demo-hello-app` item refers to the exact absolute path of its dependency under `/frx/store`, showing that the usual Guix model of immutable absolute store references maps cleanly onto FreeBSD path semantics.
### 3. Garbage collection can be expressed with ordinary FreeBSD filesystem primitives
No Linux-specific kernel mechanism was required for the prototype GC behavior. A root/daemon process can manage roots, references, and deletion using normal filesystem traversal and metadata files.
## Limitations versus real Guix
This is still a prototype, not full Guix store integration. It does **not** yet provide:
- real derivation registration
- SQLite-backed store metadata
- daemon RPC interfaces
- narinfo/substitute handling
- signature verification
- graft handling
- real package database integration
However, it does validate the central FreeBSD-side store assumptions needed before deeper integration:
- `/frx/store` can exist with an appropriate permission model
- absolute immutable store paths are viable
- root-managed GC roots and dependency retention are viable
- unprivileged consumers can read/execute store content without being able to mutate it
## Conclusion
Phase 2.3 is satisfied on the current prototype track:
- a working `/frx/store` equivalent now exists on the host
- content-addressed demo store items were created successfully
- unprivileged readability and write protection were validated
- reference-preserving garbage collection was demonstrated
With Phase 2.1, 2.2, and 2.3 now all satisfied on the prototype track, the project has completed the core FreeBSD Guix-daemon architecture adaptation milestone needed before moving on to build-system adaptation work.

View File

@@ -0,0 +1,137 @@
# Phase 3.1: Adapted GNU build-system prototype on FreeBSD
Date: 2026-04-01
## Summary
This step adds a reusable FreeBSD-oriented GNU build-system prototype and validates it across five representative GNU packages using real Guix builder-side phase code.
Added files:
- `tests/build-system/gnu-package-freebsd-phase-runner.scm`
- `tests/build-system/run-gnu-package-freebsd-phase-runner.sh`
- `tests/build-system/run-freebsd-gnu-package-matrix.sh`
The harness drives `(guix build gnu-build-system)` directly with a small FreeBSD adaptation layer instead of relying on Linux-default tool assumptions.
## FreeBSD build adaptations applied
The runner injects a dedicated `freebsd-setup-environment` phase before `configure` and consistently applies the following host adaptations:
- `make` in `PATH` is redirected to GNU Make via a tool shim:
- `make -> /usr/local/bin/gmake`
- compiler tool names are normalized through shims:
- `cc -> /usr/bin/cc`
- `gcc -> /usr/bin/cc`
- `c++ -> /usr/bin/c++`
- `g++ -> /usr/bin/c++`
- environment variables are set explicitly for FreeBSD host dependencies:
- `CC=/usr/bin/cc`
- `CXX=/usr/bin/c++`
- `CONFIG_SHELL=/bin/sh`
- `PKG_CONFIG=/usr/local/bin/pkg-config`
- `PKG_CONFIG_PATH=/usr/local/libdata/pkgconfig:/usr/local/lib/pkgconfig`
- `CPPFLAGS=-I/usr/local/include`
- `LDFLAGS=-L/usr/local/lib -Wl,-rpath,/usr/local/lib`
These adaptations are enough to make the selected `%standard-phases` subset work on the current FreeBSD amd64 host without modifying upstream Guix itself.
## Package matrix
Run command:
```sh
METADATA_OUT=/tmp/freebsd-gnu-package-matrix-summary.txt \
./tests/build-system/run-freebsd-gnu-package-matrix.sh
```
Validated package set:
1. `hello` `2.12.3`
2. `which` `2.21`
3. `time` `1.9`
4. `patch` `2.8`
5. `nano` `8.7.1`
These versions and source hashes were taken from current Guix package definitions.
## Results
### 1. GNU Hello
- built successfully
- runtime output:
- `Hello, world!`
- tests passed
- notable runtime deps:
- `libiconv.so.2`
- `libintl.so.8`
### 2. GNU Which
- built successfully
- runtime output:
- `/bin/sh`
- tests passed
- runtime deps remained minimal:
- `libc.so.7`
- `libsys.so.7`
### 3. GNU Time
- built successfully
- runtime output:
- `time (GNU Time) 1.9`
- build-system adaptation note:
- package verification used `RUN_TESTS=0`
- reason:
- the upstream `time-max-rss` check was not reliable on this host and produced a FreeBSD-specific failure boundary unrelated to basic build/install/runtime functionality
### 4. GNU Patch
- built successfully
- runtime output:
- `GNU patch 2.8`
- tests passed with expected upstream `XFAIL`/`SKIP` cases
- runtime deps remained minimal:
- `libc.so.7`
- `libsys.so.7`
### 5. GNU Nano
- built successfully
- runtime output:
- `GNU nano, version 8.7.1`
- this package provided the most useful runtime-dependency validation in the matrix
- observed runtime deps included:
- `libintl.so.8`
- `libmagic.so.4`
- `libncursesw.so.9`
- `libtinfow.so.9`
- `libz.so.6`
## Why this satisfies Phase 3.1
The Phase 3.1 goal was to adapt core build-system expectations to FreeBSD and demonstrate successful builds for five representative packages.
That goal is satisfied on the current prototype track because:
- the harness uses real Guix builder-side GNU phase code
- the FreeBSD-specific toolchain and library-path adaptations are explicit and reusable
- five GNU packages now build successfully through the adapted runner
- at least one package with meaningful runtime dependencies (`nano`) was validated
- the built programs executed correctly with deterministic checks appropriate to each package
## Important notes
- this is still a builder-side prototype, not full package lowering through a real Guix daemon
- however, it is materially beyond earlier ad hoc package-specific experiments because the adaptation is now centralized and reusable across a package matrix
- one package (`time`) required disabling the test phase for this host due a specific test failure boundary; that was recorded explicitly rather than hidden
## Conclusion
Phase 3.1 is satisfied on the current FreeBSD prototype track:
- a reusable FreeBSD adaptation layer for GNU build phases now exists
- five representative GNU packages build and run successfully through it
- the results show that the main remaining Phase 3 uncertainty is no longer “can GNU builder phases run on FreeBSD?” but “how should higher-level packaging and profile composition be modeled for FreeBSD system components?”

View File

@@ -0,0 +1,165 @@
# Phase 3.2: FreeBSD system package-definition prototype and profile validation
Date: 2026-04-01
## Summary
This step adds a minimal FreeBSD system package-definition prototype and validates that the resulting packages can be materialized into store-like outputs and installed into a profile that is usable for development tasks.
Added files:
- `modules/fruix/packages/freebsd.scm`
- `tests/packages/freebsd-package-profile-prototype.scm`
- `tests/packages/run-freebsd-package-profile-prototype.sh`
The package-definition module is intentionally a **Guix-style prototype layer** rather than a fully integrated Guix package collection, because full host-side package lowering and daemon integration are still blocked upstream on this FreeBSD path. Even so, it provides explicit package metadata, dependency relationships, build-system tags, install plans, and a profile-validation harness.
## Prototype package set
The module currently defines the following minimal core set:
- `freebsd-kernel`
- `freebsd-kernel-headers`
- `freebsd-libc`
- `freebsd-userland`
- `freebsd-clang-toolchain`
- `freebsd-gmake`
- `freebsd-autotools`
- `freebsd-openssl`
- `freebsd-zlib`
- `freebsd-sh`
- `freebsd-bash`
These cover the categories requested by Phase 3.2:
- FreeBSD kernel and kernel headers
- FreeBSD libc
- FreeBSD userland utilities
- development tools (`clang`, `make`, autotools)
- minimum system libraries (`openssl`, `zlib`)
- a basic shell (`sh`, `bash`)
## Definition style
The module uses a Guix-like package record shape with explicit fields for:
- name
- version
- build system
- inputs
- home page
- synopsis
- description
- license
- install plan
This keeps the structure close to Guix package-definition practice even though the current harness interprets the definitions itself rather than asking a working Guix daemon to lower them.
## Materialization and profile harness
Run command:
```sh
METADATA_OUT=/tmp/freebsd-package-profile-prototype-metadata.txt \
./tests/packages/run-freebsd-package-profile-prototype.sh
```
What the harness does:
1. loads the prototype package definitions
2. recursively materializes each package into a store-like directory under the harness work tree
3. records dependency references between the package outputs
4. assembles a merged profile from the selected package set
5. validates the resulting profile by checking for:
- shell availability
- compiler availability
- make availability
- autotools availability
- kernel image presence
- kernel-header presence
- core library presence
6. compiles and runs a small C program using tools from the generated profile
## Observed results
Observed metadata from the successful run included:
- `core_package_count=11`
- `profile_package_count=11`
- `bash_version=GNU bash, version 5.3.9...`
- `make_version=GNU Make 4.4.1`
- `autoconf_version=autoconf (GNU Autoconf) 2.72`
- `cc_version=FreeBSD clang version 19.1.7 ...`
- `hello_output=hello-from-freebsd-profile`
The validation program was compiled and run successfully from the generated profile, producing:
```text
hello-from-freebsd-profile
```
## Dependency relationships validated
The prototype explicitly records useful dependency edges, for example:
- `freebsd-libc` depends on `freebsd-kernel-headers`
- `freebsd-userland` depends on `freebsd-libc` and `freebsd-sh`
- `freebsd-clang-toolchain` depends on `freebsd-libc`, `freebsd-kernel-headers`, and `freebsd-sh`
- `freebsd-gmake` depends on `freebsd-sh` and `freebsd-libc`
- `freebsd-autotools` depends on `freebsd-gmake`, `freebsd-bash`, and `freebsd-libc`
- `freebsd-openssl` depends on `freebsd-libc`
- `freebsd-zlib` depends on `freebsd-libc`
- `freebsd-bash` depends on `freebsd-libc`
These dependencies are not merely documented; the harness resolves them recursively when materializing outputs and building the final profile.
## Important implementation notes
### 1. Executable entries are wrapped, not copied verbatim
For package entries installed under `bin/`, the harness creates small wrappers that `exec` the host tool by absolute path. This avoids breaking tools such as `autoconf` that expect their installed prefix layout or auxiliary data files to remain coherent.
### 2. Data trees are copied where profile-local structure matters
For items such as:
- kernel headers
- automake support files
- autoconf data
- libtool auxiliary data
whole directory trees are copied into the package outputs so that the generated profile has the expected on-disk structure.
### 3. This is a prototype package-definition layer, not final Guix integration
The current result demonstrates packaging shape and profile usability. It does **not** yet provide:
- real Guix `package` lowering on FreeBSD
- derivation generation from these package definitions
- daemon-backed builds into `/frx/store`
- real profile generation by `guix package`
Those remain later integration tasks.
## Why this satisfies Phase 3.2 on the prototype track
Phase 3.2 asked for a minimal FreeBSD package-definition set and validation that the packages build, relate correctly, and can be installed into profiles for practical use.
That goal is satisfied on the current prototype track because:
- the requested FreeBSD system-component categories are all represented
- explicit dependency relationships are encoded and resolved
- the package set materializes successfully into store-like outputs
- the outputs install into a merged profile successfully
- the resulting profile is practically usable for development validation, including compilation and execution of a test program using the staged toolchain
## Conclusion
Phase 3.2 is satisfied on the current FreeBSD prototype track:
- a minimal FreeBSD system package-definition layer now exists in-repo
- dependency relationships are explicit and validated
- profile-style installation works
- the generated profile is usable for at least a concrete C build/run validation
With both Phase 3.1 and Phase 3.2 complete, the project has now finished the build-system adaptation milestone on the current FreeBSD path.

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,126 @@
# Phase 4.2: FreeBSD init-integration prototype for Shepherd
Date: 2026-04-01
## Summary
This step prototypes how Shepherd can be launched and stopped through FreeBSD init conventions while validating boot-time dependency ordering and orderly shutdown sequencing.
Added file:
- `tests/shepherd/run-freebsd-shepherd-init-prototype.sh`
## Scope
The original plan for Phase 4.2 described booting a FreeBSD system with Shepherd as PID 1. That is not practical to perform directly on the current live host, so this step validates the next-best integration boundary on the current prototype track:
- Shepherd launched through a real FreeBSD `rc.d` script
- automatic startup of an essential-service graph at daemon launch
- dependency-ordered startup
- dependency-ordered shutdown
- clean stop through FreeBSD service-management entry points
This is an init-integration prototype rather than a full replacement of `/sbin/init` on the host.
## Prototype design
Run command:
```sh
METADATA_OUT=/tmp/freebsd-shepherd-init-metadata.txt \
./tests/shepherd/run-freebsd-shepherd-init-prototype.sh
```
The harness does the following:
1. reuses the local Shepherd build from Phase 4.1
2. writes a temporary Shepherd configuration that models a minimal boot graph:
- `filesystems`
- `system-log`
- `networking`
- `login`
3. writes a temporary `rc.d` script into `/usr/local/etc/rc.d/`
4. starts Shepherd through:
- `service <name> onestart`
5. verifies that the service graph comes up automatically in the expected order
6. stops Shepherd through:
- `service <name> onestop`
7. verifies orderly reverse-order shutdown and process exit
## FreeBSD integration details validated
### 1. Real rc.d entry point
The harness uses an actual temporary FreeBSD `rc.d` service script rather than a shell approximation. The script:
- defines `start_cmd`, `stop_cmd`, and `status_cmd`
- launches the local Shepherd binary with the required Guile environment
- uses `herd -s <socket> stop root` for orderly shutdown
- exposes the instance through standard FreeBSD `service` commands
### 2. Automatic boot graph startup
The temporary Shepherd configuration automatically starts the `login` target during initialization. That causes Shepherd to bring up the dependency chain:
- `filesystems`
- `system-log`
- `networking`
- `login`
### 3. Ordered shutdown
Stopping the rc.d service causes Shepherd to stop the service graph in reverse dependency order:
- `login`
- `networking`
- `system-log`
- `filesystems`
## Observed results
Observed metadata included:
- `rc_status=running`
- `start_sequence=start:filesystems,start:system-log,start:networking,start:login`
- `stop_sequence=stop:login,stop:networking,stop:system-log,stop:filesystems`
- `signalfd_fallback=yes`
This confirms:
- the rc.d wrapper launched Shepherd successfully
- Shepherd automatically started the boot graph in the expected dependency order
- stopping the wrapper triggered an orderly reverse shutdown
- the PID file was removed and the daemon exited cleanly
## Important findings
- the same FreeBSD runtime note from Phase 4.1 appears here as well:
- `System lacks support for 'signalfd'; using fallback mechanism.`
- despite that, the init-integration prototype behaved correctly
- the rc.d wrapper approach is a practical bridge for running Shepherd under FreeBSD system conventions while the broader port remains in prototype form
- the validated boot graph shows that Shepherd can already express the ordering logic needed for essential-system startup on FreeBSD even before a true PID 1 handoff is attempted
## Why this satisfies Phase 4.2 on the prototype track
The literal Phase 4.2 goal in the plan was a full PID 1 boot. On the active host, the meaningful and safely testable prototype equivalent is:
- run Shepherd through FreeBSD's real service-management entry points
- validate boot ordering for essential services
- validate orderly shutdown sequencing
- validate clean daemon lifecycle and status behavior
That prototype goal is satisfied because:
- a real FreeBSD `rc.d` wrapper now launches and stops Shepherd successfully
- a minimal essential-service graph starts automatically in correct order
- shutdown happens cleanly in correct reverse order
- the resulting behavior matches the service-ordering and shutdown expectations of an init integration path
## Conclusion
Phase 4.2 is satisfied on the current prototype track as an init-integration prototype:
- Shepherd can be integrated with FreeBSD `rc.d`
- a minimal boot graph can be started automatically through that path
- startup and shutdown dependency ordering work correctly
- the remaining gap is now not basic init integration mechanics, but broader bridging of Shepherd services to FreeBSD-specific service concepts and host-management tasks

View File

@@ -0,0 +1,171 @@
# Phase 4.1: Shepherd built and validated as a regular FreeBSD service manager
Date: 2026-04-01
## Summary
This step validates that GNU Shepherd can be built on the current FreeBSD amd64 host using the previously fixed local Guile stack and can successfully manage multiple services as a regular daemon.
Added files:
- `tests/shepherd/build-local-guile-fibers.sh`
- `tests/shepherd/build-local-shepherd.sh`
- `tests/shepherd/run-freebsd-shepherd-service-prototype.sh`
## Build inputs and versions
The validation used:
- fixed local Guile:
- `/tmp/guile-freebsd-validate-install/bin/guile`
- shared local Guile extension prefix:
- `/tmp/guile-gnutls-freebsd-validate-install`
- Guile Fibers from the current Guix source of truth:
- version `1.4.2`
- resolved commit `297359f0ad655378bcc3ff0d4e96101965ef39b4`
- Shepherd from the current Guix package definition:
- version `1.0.9`
- nix-base32 `1mh080060lnycys8yq6kkiy363wif8dsip3nyklgd3a1r22wb274`
- verified SHA256 `e488c585c8418df6e8f476dca81b72910f337c9cd3608fb467de5260004000d6`
## FreeBSD-specific build findings
### Guile Fibers
- building from the Guix-matching Git tag required autotools regeneration:
- `autoreconf -vfi`
- the resulting installation validated successfully with:
- `(use-modules (fibers))`
### Shepherd
- Shepherd `1.0.9` configured and built successfully against the fixed local Guile plus the locally installed Fibers module
- one FreeBSD-specific build adaptation was required during install:
- configure must use `SED=/usr/local/bin/gsed`
- reason:
- the Shepherd install phase edits installed wrapper scripts with GNU `sed -i` syntax
- base FreeBSD `/usr/bin/sed` rejects that invocation
- after using GNU `sed`, install completed successfully
## Runtime findings on FreeBSD
The installed Shepherd and herd commands run successfully on FreeBSD with the local Guile environment.
Observed runtime note:
- Shepherd prints:
- `System lacks support for 'signalfd'; using fallback mechanism.`
This is expected on FreeBSD and did not prevent service supervision from working.
## Service-management prototype
Run command:
```sh
METADATA_OUT=/tmp/freebsd-shepherd-service-metadata.txt \
./tests/shepherd/run-freebsd-shepherd-service-prototype.sh
```
The prototype starts a root-launched Shepherd instance and validates four services:
1. `logger`
- background service
- runs as user/group `nobody:nobody`
- writes heartbeat output to a log
2. `web`
- depends on `logger`
- serves a tiny HTTP response over loopback using `nc`
3. `file-monitor`
- depends on `web`
- watches for a flag file and records detection
4. `crashy`
- fails on first start
- then respawns and stays running
## Verified behaviors
### Start/stop through Shepherd command interface
The prototype successfully used `herd` to:
- start services
- inspect service status
- stop the entire service graph through `stop root`
### Dependency handling
Starting `file-monitor` automatically started its dependencies:
- `logger`
- `web`
All three were then reported as running by `herd status`.
### Service status monitoring
Recorded metadata confirmed:
- `logger_running=yes`
- `web_running=yes`
- `monitor_running=yes`
- `crashy_running=yes`
### Crash handling and restart
The `crashy` service was configured with respawn enabled.
Observed behavior:
- first launch exited with code `1`
- Shepherd logged a respawn
- second launch remained running
- observed metadata:
- `crashy_counter=2`
### Privilege handling
The `logger`, `web`, and `file-monitor` services were launched by a root-owned Shepherd instance but executed as `nobody`.
Observed metadata:
- `logger_uid=65534`
This matches FreeBSD `nobody` on the host.
### Concrete service execution checks
The loopback HTTP service returned the expected deterministic response:
- `http_response=shepherd-freebsd-ok`
The file-monitor service detected a watched-file event successfully:
- `monitor_detected=detected`
## Why this satisfies Phase 4.1
Phase 4.1 required that Shepherd compile and run on FreeBSD as a regular service manager and demonstrate:
- service start/stop
- dependency management
- service status monitoring
- crash/restart handling
- appropriate privilege execution
Those requirements are satisfied on the current prototype track because:
- Shepherd now builds reproducibly with the fixed local Guile stack
- a root-launched Shepherd instance successfully supervised multiple services on FreeBSD
- dependencies were honored
- statuses were queryable through `herd`
- a crashing service was respawned successfully
- services were executed under an unprivileged account where requested
## Conclusion
Phase 4.1 is satisfied on the current FreeBSD prototype track:
- Shepherd builds on FreeBSD with a small GNU `sed` install-time adjustment
- the lack of `signalfd` is handled by Shepherd's fallback path
- regular-daemon service supervision works correctly for multiple dependent services on the host

View File

@@ -0,0 +1,102 @@
# Phase 5.1: Unblocked the checkout command path on FreeBSD and established a first `fruix` frontend boundary
Date: 2026-04-01
## Summary
This step moves the FreeBSD port from “the checkout configures and builds generated scripts” to “the checkout actually runs useful commands”.
Added files:
- `tests/guix/patches/phase5-checkout-runtime.patch`
- `tests/guix/patches/phase5-guix-daemon-freebsd.patch`
- `tests/guix/setup-phase5-checkout.sh`
- `tests/guix/run-phase5-checkout-runtime.sh`
## Root cause of the `leave-on-EPIPE` failure
The earlier failure:
- `./pre-inst-env guix --version`
- `Wrong type to apply: #<syntax-transformer leave-on-EPIPE>`
was traced to the ordering of top-level definitions in `guix/ui.scm`.
More specifically:
- `show-version-and-exit` called `leave-on-EPIPE`
- `leave-on-EPIPE` was defined later in the file as a syntax transformer
- on this FreeBSD path, the earlier call site was compiled as a value application rather than as a macro expansion site
- when invoked at runtime, Guile attempted to apply the syntax transformer as a procedure
This produced the observed error:
- `Wrong type to apply: #<syntax-transformer leave-on-EPIPE>`
## Runtime patch strategy
The runtime patch takes a deliberately small approach:
1. make `(guix ui)` explicitly non-declarative for this command-path use case
2. rewrite `show-version-and-exit` to use a direct `catch 'system-error` block instead of relying on the later syntax binding
3. parameterize `program-name` in `guix-main`
4. derive the version-banner command name from `program-name`
5. make `(guix scripts repl)` explicitly non-declarative as well, since it also uses `load`
This resolves the FreeBSD checkout runtime failure without forcing a broad internal rename.
## FreeBSD/Fruix frontend policy implemented
The step also establishes the first user-facing `fruix` checkout boundary.
Current policy implemented by the harness:
- retain upstream-derived internal `guix` module and script structure
- generate a `scripts/fruix` symlink next to `scripts/guix`
- rely on the new `program-name` handling so:
- `./pre-inst-env guix --version` reports `guix ...`
- `./pre-inst-env fruix --version` reports `fruix ...`
This is intentionally a **front-end boundary**, not a whole-tree rename.
## Validation command
Run command:
```sh
METADATA_OUT=/tmp/phase5-runtime-metadata.txt \
./tests/guix/run-phase5-checkout-runtime.sh
```
## Observed results
The runtime harness validated all of the following successfully:
1. `./pre-inst-env guix --version`
2. `./pre-inst-env guix repl --help`
3. `./pre-inst-env guix build --help`
4. `./pre-inst-env fruix --version`
Observed metadata included:
- `first_guix_version_line=guix (GNU Guix) ...`
- `first_fruix_version_line=fruix (GNU Guix) ...`
- `repl_usage_line=Usage: guix repl [OPTIONS...] [-- FILE ARGS...]`
- `build_usage_line=Usage: guix build [OPTION]... PACKAGE-OR-DERIVATION...`
## Important notes
- the first-step `fruix` boundary currently changes the top-level version banner correctly
- subcommand help output still exposes upstream-derived `guix` wording internally
- this is acceptable for the current transition stage and matches the agreed naming policy:
- Fruix at the user-facing boundary
- stable upstream-derived internals unless there is strong reason to rename them
## Conclusion
Phase 5.1 is satisfied on the current FreeBSD prototype track:
- the checkout command path now runs on FreeBSD
- the `leave-on-EPIPE` blocker is resolved in the checkout patch queue
- at least one additional non-trivial checkout subcommand works
- a first user-facing `fruix` checkout frontend boundary now exists without a destabilizing blanket rename

View File

@@ -0,0 +1,71 @@
# Phase 5.3: Minimal daemon/store RPC integration validated on FreeBSD
Date: 2026-04-01
## Summary
This step validates that the FreeBSD-aware checkout can do more than emit a derivation: it can contact a real daemon/store path and submit an actual derivation-backed build request that succeeds into `/frx/store`.
Added file:
- `tests/guix/run-phase5-daemon-rpc.sh`
## Validation command
Run command:
```sh
METADATA_OUT=/tmp/phase5-daemon-rpc-metadata.txt \
./tests/guix/run-phase5-daemon-rpc.sh
```
## What the harness does
The harness:
1. reuses the patched checkout/runtime setup from Phase 5.1
2. starts the patched `guix-daemon` on a temporary Unix socket
3. invokes the checkout through the user-facing boundary:
- `./pre-inst-env fruix repl -- ...`
4. defines a minimal custom package with the same low-level FreeBSD build system style used in Phase 5.2
5. lowers that package to a derivation
6. submits the resulting derivation to the daemon through `build-derivations`
7. validates the built output content in `/frx/store`
## Observed results
Observed metadata included:
- `drv_path=/frx/store/...-phase5-freebsd-daemon-build-0.drv`
- `out_path=/frx/store/...-phase5-freebsd-daemon-build-0`
- `payload=phase5-daemon-build-source`
- `source_path=/frx/store/...-phase5-source.txt`
- `frontend_invocation=./pre-inst-env fruix repl -- ...`
This demonstrates that:
- the checkout can contact a real daemon over a Unix socket on FreeBSD
- the daemon accepts a derivation-backed build request
- the build succeeds into `/frx/store`
- the resulting output is a real store item
- the source was itself materialized as a store path and referenced by the built output path
## Important findings
- the FreeBSD daemon path is now operational enough to support a narrow but real end-to-end flow:
- checkout command path
- daemon RPC
- derivation submission
- build execution
- store output materialization
- this validation intentionally used a minimal custom derivation-backed package rather than a full upstream GNU package graph, so the result isolates actual daemon/store viability from unrelated unresolved bootstrap-package assumptions
- the command path was exercised through the Fruix-facing frontend boundary (`fruix repl`), which is important because Phase 5 is not just about making the upstream-derived checkout work internally, but about beginning the transition to Fruix as the operator-facing identity
## Conclusion
Phase 5.3 is satisfied on the current FreeBSD prototype track:
- a real checkout command path can contact a FreeBSD-aware daemon/store path
- that path accepts and executes a derivation-backed build request
- the build succeeds into `/frx/store`
- the Fruix-facing checkout frontend is now being exercised against the real daemon/store path rather than only against no-op or help-text commands

View File

@@ -0,0 +1,90 @@
# Phase 5.2: Real derivation generation validated on FreeBSD against `/frx/store`
Date: 2026-04-01
## Summary
This step validates that the now-runnable checkout can perform real package lowering and emit a real derivation targeting `/frx/store` on FreeBSD.
Added file:
- `tests/guix/run-phase5-derivation-generation.sh`
## Approach
Rather than jumping immediately to a full upstream package such as GNU Hello, this step uses a deliberately minimal custom package defined inside the harness. The goal is to validate the real Guix lowering layers first, while avoiding unrelated bootstrap baggage that is still not fully adapted to FreeBSD.
The custom package still exercises the layers that matter for this subphase:
- `package->bag`
- `bag->derivation`
- real store connection through the daemon socket
- derivation emission into `/frx/store`
- source lowering as a real store item
## Why a custom low-level package was used
Attempting to lower a representative upstream package now gets past the earlier `leave-on-EPIPE` command-path failure, but still runs into deeper platform/bootstrap issues on this FreeBSD path, such as missing bootstrap binaries for the native system string.
That means the meaningful next proof point was:
> can the real checkout lower a package and emit a real derivation at all on FreeBSD?
The answer is now yes.
## Validation command
Run command:
```sh
METADATA_OUT=/tmp/phase5-derivation-metadata.txt \
./tests/guix/run-phase5-derivation-generation.sh
```
## What the harness does
The harness:
1. reuses the patched checkout/runtime setup from Phase 5.1
2. builds the patched `guix-daemon` for FreeBSD
3. starts that daemon on a temporary Unix socket
4. runs the checkout through the user-facing frontend boundary:
- `./pre-inst-env fruix repl -- ...`
5. defines a minimal custom FreeBSD test package with a custom low-level build system
6. lowers that package through:
- `package->bag`
- `bag->derivation`
7. records the resulting derivation path and output path
## Observed results
Observed metadata included:
- `bag_name=phase5-freebsd-lowering-0`
- `bag_host_inputs=("source")`
- `drv_path=/frx/store/...-phase5-freebsd-lowering-0.drv`
- `out_path=/frx/store/...-phase5-freebsd-lowering-0`
This demonstrates that:
- the checkout can now talk to a real daemon socket on FreeBSD
- a real package object can be lowered to a bag
- that bag can be lowered to a real derivation
- the derivation is registered under `/frx/store`
- the output path is also a normal `/frx/store` path
## Important findings
- the FreeBSD daemon path does not need to be fully feature-complete before derivation generation becomes useful; a narrow but real lowering path already works
- the custom package approach avoided conflating Phase 5.2 with the still-unresolved upstream bootstrap-package assumptions for native FreeBSD package graphs
- the result is materially beyond the earlier builder-phase and profile prototypes because the derivation is now emitted by the real checkout and store machinery rather than by an ad hoc stand-in
## Conclusion
Phase 5.2 is satisfied on the current FreeBSD prototype track:
- a real derivation is now emitted by the checkout on FreeBSD
- it targets `/frx/store`
- it comes from a real `package->bag` and `bag->derivation` path rather than from a shell-only approximation
The next step is to go one layer deeper and submit an actual derivation-backed build request through the same FreeBSD-aware daemon/store path.

View File

@@ -0,0 +1,272 @@
(define-module (fruix packages freebsd)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-9)
#:export (freebsd-release
freebsd-package?
freebsd-package-name
freebsd-package-version
freebsd-package-build-system
freebsd-package-inputs
freebsd-package-home-page
freebsd-package-synopsis
freebsd-package-description
freebsd-package-license
freebsd-package-install-plan
freebsd-kernel
freebsd-kernel-headers
freebsd-libc
freebsd-userland
freebsd-clang-toolchain
freebsd-gmake
freebsd-autotools
freebsd-openssl
freebsd-zlib
freebsd-sh
freebsd-bash
%freebsd-core-packages
%freebsd-development-profile-packages))
(define-record-type <freebsd-package>
(make-freebsd-package name version build-system inputs home-page synopsis
description license install-plan)
freebsd-package?
(name freebsd-package-name)
(version freebsd-package-version)
(build-system freebsd-package-build-system)
(inputs freebsd-package-inputs)
(home-page freebsd-package-home-page)
(synopsis freebsd-package-synopsis)
(description freebsd-package-description)
(license freebsd-package-license)
(install-plan freebsd-package-install-plan))
(define* (freebsd-package #:key name version build-system (inputs '()) home-page
synopsis description license install-plan)
(make-freebsd-package name version build-system inputs home-page synopsis
description license install-plan))
(define freebsd-release "15.0-STABLE")
(define freebsd-kernel
(freebsd-package
#:name "freebsd-kernel"
#:version freebsd-release
#:build-system 'copy-build-system
#:home-page "https://www.freebsd.org/"
#:synopsis "Prototype package for the running FreeBSD kernel"
#:description
"Prototype package definition that stages the currently installed FreeBSD
kernel image into a store-like output for FreeBSD porting experiments."
#:license 'bsd-2
#:install-plan
'((file "/boot/kernel/kernel" "boot/kernel/kernel")
(file "/boot/kernel/linker.hints" "boot/kernel/linker.hints"))))
(define freebsd-kernel-headers
(freebsd-package
#:name "freebsd-kernel-headers"
#:version freebsd-release
#:build-system 'copy-build-system
#:home-page "https://www.freebsd.org/"
#:synopsis "Prototype package for FreeBSD kernel headers"
#:description
"Prototype package definition that stages a minimal set of FreeBSD kernel
header directories from /usr/src for Guix porting experiments."
#:license 'bsd-2
#:install-plan
'((directory "/usr/src/sys/sys" "include/sys"))))
(define freebsd-libc
(freebsd-package
#:name "freebsd-libc"
#:version freebsd-release
#:build-system 'copy-build-system
#:inputs (list freebsd-kernel-headers)
#:home-page "https://www.freebsd.org/"
#:synopsis "Prototype package for FreeBSD libc and userland headers"
#:description
"Prototype package definition that stages FreeBSD libc, the dynamic loader,
and the userland C headers needed for development profiles."
#:license 'bsd-2
#:install-plan
'((file "/lib/libc.so.7" "lib/libc.so.7")
(file "/lib/libsys.so.7" "lib/libsys.so.7")
(file "/libexec/ld-elf.so.1" "libexec/ld-elf.so.1"))))
(define freebsd-sh
(freebsd-package
#:name "freebsd-sh"
#:version freebsd-release
#:build-system 'copy-build-system
#:inputs (list freebsd-libc)
#:home-page "https://www.freebsd.org/"
#:synopsis "Prototype package for the FreeBSD POSIX shell"
#:description
"Prototype package definition that stages the base system POSIX shell from
FreeBSD."
#:license 'bsd-2
#:install-plan
'((file "/bin/sh" "bin/sh"))))
(define freebsd-userland
(freebsd-package
#:name "freebsd-userland"
#:version freebsd-release
#:build-system 'copy-build-system
#:inputs (list freebsd-libc freebsd-sh)
#:home-page "https://www.freebsd.org/"
#:synopsis "Prototype package for selected FreeBSD userland utilities"
#:description
"Prototype package definition that stages a small set of base FreeBSD
userland commands needed for development and build experiments."
#:license 'bsd-2
#:install-plan
'((file "/bin/cat" "bin/cat")
(file "/bin/cp" "bin/cp")
(file "/bin/echo" "bin/echo")
(file "/bin/ln" "bin/ln")
(file "/bin/ls" "bin/ls")
(file "/bin/mkdir" "bin/mkdir")
(file "/bin/mv" "bin/mv")
(file "/bin/pwd" "bin/pwd")
(file "/bin/rm" "bin/rm")
(file "/usr/bin/find" "bin/find")
(file "/usr/bin/tar" "bin/tar")
(file "/usr/bin/xargs" "bin/xargs"))))
(define freebsd-clang-toolchain
(freebsd-package
#:name "freebsd-clang-toolchain"
#:version freebsd-release
#:build-system 'copy-build-system
#:inputs (list freebsd-libc freebsd-kernel-headers freebsd-sh)
#:home-page "https://www.freebsd.org/"
#:synopsis "Prototype package for the FreeBSD Clang toolchain"
#:description
"Prototype package definition that stages the base FreeBSD Clang-based C
and C++ toolchain into a profile-friendly output."
#:license 'bsd-2
#:install-plan
'((file "/usr/bin/cc" "bin/cc")
(file "/usr/bin/c++" "bin/c++")
(file "/usr/bin/clang" "bin/clang")
(file "/usr/bin/clang++" "bin/clang++")
(file "/usr/bin/ar" "bin/ar")
(file "/usr/bin/ranlib" "bin/ranlib")
(file "/usr/bin/nm" "bin/nm")
(file "/usr/bin/ld" "bin/ld"))))
(define freebsd-gmake
(freebsd-package
#:name "freebsd-gmake"
#:version "4.4.1"
#:build-system 'copy-build-system
#:inputs (list freebsd-sh freebsd-libc)
#:home-page "https://www.gnu.org/software/make/"
#:synopsis "Prototype package for GNU Make on FreeBSD"
#:description
"Prototype package definition that stages the GNU Make binary from the
FreeBSD ports collection for use in build profiles."
#:license 'gpl3+
#:install-plan
'((file "/usr/local/bin/gmake" "bin/gmake")
(file "/usr/local/bin/gmake" "bin/make"))))
(define freebsd-bash
(freebsd-package
#:name "freebsd-bash"
#:version "5.3.9"
#:build-system 'copy-build-system
#:inputs (list freebsd-libc)
#:home-page "https://www.gnu.org/software/bash/"
#:synopsis "Prototype package for GNU Bash on FreeBSD"
#:description
"Prototype package definition that stages the Bash binary from the
FreeBSD ports collection for development profiles."
#:license 'gpl3+
#:install-plan
'((file "/usr/local/bin/bash" "bin/bash"))))
(define freebsd-autotools
(freebsd-package
#:name "freebsd-autotools"
#:version "2026-04"
#:build-system 'copy-build-system
#:inputs (list freebsd-gmake freebsd-bash freebsd-libc)
#:home-page "https://www.gnu.org/software/autoconf/"
#:synopsis "Prototype package for Autotools on FreeBSD"
#:description
"Prototype package definition that stages the Autoconf, Automake, Libtool,
pkg-config, and GNU m4 tools needed by FreeBSD Guix build experiments."
#:license 'gpl3+
#:install-plan
'((file "/usr/local/bin/autoconf" "bin/autoconf")
(file "/usr/local/bin/autoheader" "bin/autoheader")
(file "/usr/local/bin/autom4te" "bin/autom4te")
(file "/usr/local/bin/automake" "bin/automake")
(file "/usr/local/bin/aclocal" "bin/aclocal")
(file "/usr/local/bin/autoreconf" "bin/autoreconf")
(file "/usr/local/bin/libtoolize" "bin/libtoolize")
(file "/usr/local/bin/pkg-config" "bin/pkg-config")
(file "/usr/local/bin/gm4" "bin/m4")
(directory "/usr/local/share/autoconf2.72" "share/autoconf2.72")
(directory "/usr/local/share/automake-1.18" "share/automake-1.18")
(directory "/usr/local/share/aclocal" "share/aclocal")
(directory "/usr/local/share/libtool" "share/libtool"))))
(define freebsd-openssl
(freebsd-package
#:name "freebsd-openssl"
#:version freebsd-release
#:build-system 'copy-build-system
#:inputs (list freebsd-libc)
#:home-page "https://www.openssl.org/"
#:synopsis "Prototype package for OpenSSL libraries from FreeBSD base"
#:description
"Prototype package definition that stages the OpenSSL shared libraries
shipped with FreeBSD base."
#:license 'openssl
#:install-plan
'((file "/usr/lib/libcrypto.so" "lib/libcrypto.so")
(file "/usr/lib/libssl.so" "lib/libssl.so"))))
(define freebsd-zlib
(freebsd-package
#:name "freebsd-zlib"
#:version freebsd-release
#:build-system 'copy-build-system
#:inputs (list freebsd-libc)
#:home-page "https://zlib.net/"
#:synopsis "Prototype package for zlib from FreeBSD base"
#:description
"Prototype package definition that stages the base FreeBSD zlib shared
library for profile experiments."
#:license 'zlib
#:install-plan
'((file "/lib/libz.so.6" "lib/libz.so.6"))))
(define %freebsd-core-packages
(list freebsd-kernel
freebsd-kernel-headers
freebsd-libc
freebsd-userland
freebsd-clang-toolchain
freebsd-gmake
freebsd-autotools
freebsd-openssl
freebsd-zlib
freebsd-sh
freebsd-bash))
(define %freebsd-development-profile-packages
(list freebsd-kernel
freebsd-kernel-headers
freebsd-libc
freebsd-userland
freebsd-clang-toolchain
freebsd-gmake
freebsd-autotools
freebsd-openssl
freebsd-zlib
freebsd-sh
freebsd-bash))

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,300 @@
(use-modules (guix base32)
(guix build gnu-build-system)
(guix build utils)
(ice-9 format)
(ice-9 match)
(ice-9 popen)
(ice-9 regex)
(srfi srfi-1)
(srfi srfi-13)
(rnrs bytevectors)
(rnrs io ports))
(define (getenv* name default)
(or (getenv name) default))
(define (trim-trailing-newlines str)
(let loop ((len (string-length str)))
(if (and (> len 0)
(char=? (string-ref str (- len 1)) #\newline))
(loop (- len 1))
(substring str 0 len))))
(define (command-output program . args)
(let* ((port (apply open-pipe* OPEN_READ program args))
(output (get-string-all port))
(status (close-pipe port)))
(unless (zero? status)
(error (format #f "command failed: ~a ~s => ~a"
program args status)))
(trim-trailing-newlines output)))
(define (nix-base32->hex str)
(string-concatenate
(map (lambda (byte)
(format #f "~2,'0x" byte))
(bytevector->u8-list (nix-base32-string->bytevector str)))))
(define (phase-subset names)
(filter (match-lambda
((name . _)
(memq name names)))
%standard-phases))
(define (insert-phase-before phases target-name new-phase)
(let loop ((remaining phases) (result '()))
(match remaining
(()
(reverse (cons new-phase result)))
(((name . proc) . rest)
(if (eq? name target-name)
(append (reverse result)
(list new-phase (cons name proc))
rest)
(loop rest (cons (cons name proc) result)))))))
(define (maybe-read-test-log source-dir)
(match (find-files source-dir "(^|/)(test-suite\\.log|testsuite\\.log)$")
((file . _)
(call-with-input-file file get-string-all))
(()
"<no test-suite.log or testsuite.log found>")))
(define (host-triplet-from-source source-dir)
(with-directory-excursion source-dir
(cond
((file-exists? "build-aux/config.guess")
(command-output "sh" "build-aux/config.guess"))
((file-exists? "config.guess")
(command-output "sh" "config.guess"))
(else
(command-output "cc" "-dumpmachine")))))
(define (make-tool-link directory name target)
(let ((link (string-append directory "/" name)))
(when (file-exists? link)
(delete-file link))
(symlink target link)))
(define (prepend-path directory)
(let ((original (getenv "PATH")))
(if original
(setenv "PATH" (string-append directory ":" original))
(setenv "PATH" directory))))
(define (freebsd-setup-environment workdir)
(let* ((tools-dir (string-append workdir "/freebsd-tools"))
(gmake (if (file-exists? "/usr/local/bin/gmake")
"/usr/local/bin/gmake"
"/usr/bin/make"))
(cc (if (file-exists? "/usr/bin/cc")
"/usr/bin/cc"
(command-output "sh" "-c" "command -v cc")))
(cxx (cond
((file-exists? "/usr/bin/c++") "/usr/bin/c++")
((file-exists? "/usr/bin/clang++") "/usr/bin/clang++")
(else (command-output "sh" "-c" "command -v c++")))))
(mkdir-p tools-dir)
(make-tool-link tools-dir "make" gmake)
(make-tool-link tools-dir "gmake" gmake)
(make-tool-link tools-dir "cc" cc)
(make-tool-link tools-dir "gcc" cc)
(make-tool-link tools-dir "c++" cxx)
(make-tool-link tools-dir "g++" cxx)
(prepend-path tools-dir)
(setenv "CC" cc)
(setenv "CXX" cxx)
(setenv "CONFIG_SHELL" "/bin/sh")
(setenv "PKG_CONFIG" "/usr/local/bin/pkg-config")
(setenv "PKG_CONFIG_PATH"
"/usr/local/libdata/pkgconfig:/usr/local/lib/pkgconfig")
(setenv "CPPFLAGS" "-I/usr/local/include")
(setenv "LDFLAGS" "-L/usr/local/lib -Wl,-rpath,/usr/local/lib")
tools-dir))
(define workdir
(or (getenv "WORKDIR")
(error "WORKDIR environment variable is required")))
(define package-name
(or (getenv "PACKAGE_NAME")
(error "PACKAGE_NAME environment variable is required")))
(define package-version
(or (getenv "PACKAGE_VERSION")
(error "PACKAGE_VERSION environment variable is required")))
(define source-url
(or (getenv "PACKAGE_SOURCE_URL")
(error "PACKAGE_SOURCE_URL environment variable is required")))
(define expected-nix-base32
(or (getenv "PACKAGE_NIX_BASE32")
(error "PACKAGE_NIX_BASE32 environment variable is required")))
(define verify-kind
(or (getenv "VERIFY_KIND")
(error "VERIFY_KIND environment variable is required")))
(define binary-relative-path
(or (getenv "BINARY_RELATIVE_PATH")
(error "BINARY_RELATIVE_PATH environment variable is required")))
(define version-prefix
(getenv* "VERSION_PREFIX" ""))
(define run-tests?
(not (string=? (getenv* "RUN_TESTS" "1") "0")))
(define guix-source-dir
(getenv* "GUIX_SOURCE_DIR"
(string-append (getenv "HOME") "/repos/guix")))
(define output-dir
(string-append workdir "/0000000000000000-" package-name "-" package-version))
(define source-basename
(basename source-url))
(define source-tarball
(string-append workdir "/" source-basename))
(define source-dir
(string-append workdir "/" package-name "-" package-version))
(define metadata-file
(string-append workdir "/" package-name "-freebsd-phase-runner-metadata.txt"))
(define expected-sha256-hex
(nix-base32->hex expected-nix-base32))
(define selected-phase-names
'(set-SOURCE-DATE-EPOCH unpack configure build check install))
(define adapted-tools-dir
(string-append workdir "/freebsd-tools"))
(define selected-phases
(insert-phase-before (phase-subset selected-phase-names)
'configure
(cons 'freebsd-setup-environment
(lambda* (#:key #:allow-other-keys)
(freebsd-setup-environment workdir)
#t))))
(define (write-file path content)
(call-with-output-file path
(lambda (port)
(display content port))))
(define (verify-installed-output installed-binary output-dir verify-kind)
(match verify-kind
("hello"
(let ((hello-output (command-output installed-binary)))
(unless (string=? hello-output "Hello, world!")
(error (format #f "unexpected hello output: ~s" hello-output)))
hello-output))
("which"
(let ((which-output (command-output "env" "PATH=/bin:/usr/bin" installed-binary "sh")))
(unless (string=? which-output "/bin/sh")
(error (format #f "unexpected which output: ~s" which-output)))
which-output))
("sed-substitute"
(let* ((input-file (string-append output-dir "/share/freebsd-phase-runner-sed-input.txt"))
(sed-output (begin
(write-file input-file "alpha beta\n")
(command-output installed-binary "s/beta/gamma/" input-file))))
(unless (string=? sed-output "alpha gamma")
(error (format #f "unexpected sed output: ~s" sed-output)))
sed-output))
("diff-unified"
(let* ((left-file (string-append output-dir "/share/freebsd-phase-runner-left.txt"))
(right-file (string-append output-dir "/share/freebsd-phase-runner-right.txt"))
(expected "--- left\n+++ right\n@@ -1 +1 @@\n-apple\n+orange")
(diff-output (begin
(write-file left-file "apple\n")
(write-file right-file "orange\n")
(command-output installed-binary "-u" "--label" "left"
"--label" "right"
left-file right-file))))
(unless (string=? diff-output expected)
(error (format #f "unexpected diff output: ~s" diff-output)))
diff-output))
("version-prefix"
(let* ((version-output (command-output installed-binary "--version"))
(first-line (string-trim-both (car (string-split version-output #\newline)))))
(unless (and (not (string-null? version-prefix))
(string-prefix? version-prefix first-line))
(error (format #f "unexpected version line: ~s" first-line)))
first-line))
(else
(error (format #f "unknown VERIFY_KIND: ~a" verify-kind)))))
(setenv "NIX_BUILD_TOP" workdir)
(mkdir-p workdir)
(format #t "Using workdir: ~a~%" workdir)
(format #t "Using Guix source: ~a~%" guix-source-dir)
(format #t "Fetching source: ~a~%" source-url)
(invoke "fetch" "-o" source-tarball source-url)
(let ((actual-sha256-hex (command-output "sha256" "-q" source-tarball)))
(unless (string=? actual-sha256-hex expected-sha256-hex)
(error (format #f "sha256 mismatch: expected ~a but got ~a"
expected-sha256-hex actual-sha256-hex)))
(format #t "Verified SHA256: ~a~%" actual-sha256-hex)
(format #t "Running adapted Guix builder phases: ~s~%" selected-phase-names)
(with-directory-excursion workdir
(gnu-build #:source source-tarball
#:outputs `(("out" . ,output-dir))
#:phases selected-phases
#:tests? run-tests?))
(let* ((installed-binary (string-append output-dir "/" binary-relative-path))
(verification-output
(verify-installed-output installed-binary output-dir verify-kind))
(host-triplet (host-triplet-from-source source-dir))
(binary-file (command-output "file" installed-binary))
(runtime-deps (command-output "ldd" installed-binary))
(check-summary (maybe-read-test-log source-dir))
(guile-bin (or (getenv "GUILE_BIN") "<unset>"))
(uname-string (command-output "uname" "-a"))
(make-path (command-output "sh" "-c" "command -v make"))
(make-version (command-output "make" "--version"))
(cc-version (command-output (getenv "CC") "--version")))
(call-with-output-file metadata-file
(lambda (port)
(format port "package_name=~a~%" package-name)
(format port "package_version=~a~%" package-version)
(format port "source_url=~a~%" source-url)
(format port "expected_nix_base32=~a~%" expected-nix-base32)
(format port "expected_sha256_hex=~a~%" expected-sha256-hex)
(format port "actual_sha256_hex=~a~%" actual-sha256-hex)
(format port "verify_kind=~a~%" verify-kind)
(format port "version_prefix=~a~%" version-prefix)
(format port "run_tests=~a~%" (if run-tests? "1" "0"))
(format port "binary_relative_path=~a~%" binary-relative-path)
(format port "guix_source_dir=~a~%" guix-source-dir)
(format port "guile_bin=~a~%" guile-bin)
(format port "workdir=~a~%" workdir)
(format port "source_tarball=~a~%" source-tarball)
(format port "source_dir=~a~%" source-dir)
(format port "output_dir=~a~%" output-dir)
(format port "installed_binary=~a~%" installed-binary)
(format port "verification_output=~a~%" verification-output)
(format port "host_triplet=~a~%" host-triplet)
(format port "selected_phases=~s~%" selected-phase-names)
(format port "adapted_tools_dir=~a~%" adapted-tools-dir)
(format port "make_path=~a~%" make-path)
(format port "make_version_begin~%~a~%make_version_end~%" make-version)
(format port "cc=~a~%" (getenv "CC"))
(format port "cxx=~a~%" (getenv "CXX"))
(format port "cc_version_begin~%~a~%cc_version_end~%" cc-version)
(format port "cppflags=~a~%" (getenv "CPPFLAGS"))
(format port "ldflags=~a~%" (getenv "LDFLAGS"))
(format port "pkg_config=~a~%" (getenv "PKG_CONFIG"))
(format port "pkg_config_path=~a~%" (getenv "PKG_CONFIG_PATH"))
(format port "binary_file=~a~%" binary-file)
(format port "uname=~a~%" uname-string)
(format port "check_summary_begin~%~a~%check_summary_end~%"
check-summary)
(format port "runtime_deps_begin~%~a~%runtime_deps_end~%"
runtime-deps)))
(when (getenv "METADATA_OUT")
(mkdir-p (dirname (getenv "METADATA_OUT")))
(copy-file metadata-file (getenv "METADATA_OUT")))
(format #t "PASS ~a-freebsd-phase-runner~%" package-name)
(format #t "Installed binary: ~a~%" installed-binary)
(format #t "Verification output: ~a~%" verification-output)
(format #t "Metadata file: ~a~%" metadata-file)
(when (getenv "METADATA_OUT")
(format #t "Copied metadata to: ~a~%" (getenv "METADATA_OUT")))
(display "--- metadata ---\n")
(display (call-with-input-file metadata-file get-string-all))))

View File

@@ -0,0 +1,123 @@
#!/bin/sh
set -eu
script_dir=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
runner_sh=$script_dir/run-gnu-package-freebsd-phase-runner.sh
cleanup=0
if [ -n "${WORKDIR:-}" ]; then
workdir=$WORKDIR
mkdir -p "$workdir"
else
workdir=$(mktemp -d /tmp/fruix-gnu-package-matrix.XXXXXX)
cleanup=1
fi
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
cleanup=0
fi
cleanup_workdir() {
if [ "$cleanup" -eq 1 ]; then
rm -rf "$workdir"
fi
}
trap cleanup_workdir EXIT INT TERM
metadata_dir=$workdir/metadata
summary_file=$workdir/freebsd-gnu-package-matrix-summary.txt
mkdir -p "$metadata_dir"
run_package() {
name=$1
version=$2
url=$3
hash=$4
verify_kind=$5
binary=$6
version_prefix=${7:-}
run_tests=${8:-1}
package_workdir=$workdir/$name
metadata_out=$metadata_dir/$name.txt
printf '=== building %s %s ===\n' "$name" "$version"
PACKAGE_NAME=$name \
PACKAGE_VERSION=$version \
PACKAGE_SOURCE_URL=$url \
PACKAGE_NIX_BASE32=$hash \
VERIFY_KIND=$verify_kind \
BINARY_RELATIVE_PATH=$binary \
VERSION_PREFIX=$version_prefix \
RUN_TESTS=$run_tests \
WORKDIR=$package_workdir \
METADATA_OUT=$metadata_out \
KEEP_WORKDIR=1 \
"$runner_sh"
}
run_package hello 2.12.3 \
https://ftp.gnu.org/gnu/hello/hello-2.12.3.tar.gz \
183a6rxnhixiyykd7qis0y9g9cfqhpkk872a245y3zl28can0pqd \
hello \
bin/hello
run_package which 2.21 \
https://ftp.gnu.org/gnu/which/which-2.21.tar.gz \
1bgafvy3ypbhhfznwjv1lxmd6mci3x1byilnnkc7gcr486wlb8pl \
which \
bin/which
run_package time 1.9 \
https://ftp.gnu.org/gnu/time/time-1.9.tar.gz \
07jj7cz6lc13iqrpgn81ivqh8rkm73p4rnivwgrrshk23v4g1b7v \
version-prefix \
bin/time \
"time (GNU Time) 1.9" \
0
run_package patch 2.8 \
https://ftp.gnu.org/gnu/patch/patch-2.8.tar.xz \
1qssgwgy3mfahkpgg99a35gl38vamlqb15m3c2zzrd62xrlywz7q \
version-prefix \
bin/patch \
"GNU patch 2.8"
run_package nano 8.7.1 \
https://ftp.gnu.org/gnu/nano/nano-8.7.1.tar.xz \
1pyy3hnjr9g0831wcdrs18v0lh7v63yj1kaf3ljz3qpj92rdrw3n \
version-prefix \
bin/nano \
"GNU nano, version 8.7.1"
cat > "$summary_file" <<EOF
workdir=$workdir
metadata_dir=$metadata_dir
packages=hello which time patch nano
hello_metadata=$metadata_dir/hello.txt
which_metadata=$metadata_dir/which.txt
time_metadata=$metadata_dir/time.txt
patch_metadata=$metadata_dir/patch.txt
nano_metadata=$metadata_dir/nano.txt
EOF
for package in hello which time patch nano; do
printf '%s_verification=%s\n' "$package" "$(awk -F= '/^verification_output=/{print substr($0, index($0, "=") + 1)}' "$metadata_dir/$package.txt")" >> "$summary_file"
printf '%s_runtime_deps_begin\n' "$package" >> "$summary_file"
awk '/^runtime_deps_begin$/{flag=1;next}/^runtime_deps_end$/{flag=0}flag' "$metadata_dir/$package.txt" >> "$summary_file"
printf '%s_runtime_deps_end\n' "$package" >> "$summary_file"
printf '%s_make_path=%s\n' "$package" "$(awk -F= '/^make_path=/{print substr($0, index($0, "=") + 1)}' "$metadata_dir/$package.txt")" >> "$summary_file"
printf '%s_run_tests=%s\n' "$package" "$(awk -F= '/^run_tests=/{print substr($0, index($0, "=") + 1)}' "$metadata_dir/$package.txt")" >> "$summary_file"
done
if [ -n "${METADATA_OUT:-}" ]; then
mkdir -p "$(dirname "$METADATA_OUT")"
cp "$summary_file" "$METADATA_OUT"
fi
printf 'PASS freebsd-gnu-package-matrix\n'
printf 'Working directory: %s\n' "$workdir"
printf 'Summary file: %s\n' "$summary_file"
if [ -n "${METADATA_OUT:-}" ]; then
printf 'Copied summary to: %s\n' "$METADATA_OUT"
fi
printf '%s\n' '--- summary ---'
cat "$summary_file"

View File

@@ -0,0 +1,85 @@
#!/bin/sh
set -eu
guix_source_dir=${GUIX_SOURCE_DIR:-"$HOME/repos/guix"}
script_dir=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
runner_scm=$script_dir/gnu-package-freebsd-phase-runner.scm
if [ ! -d "$guix_source_dir/guix" ]; then
echo "Guix source tree not found at $guix_source_dir" >&2
exit 1
fi
if [ -n "${GUILE_BIN:-}" ]; then
guile_bin=$GUILE_BIN
elif [ -x /tmp/guile-freebsd-validate-install/bin/guile ]; then
guile_bin=/tmp/guile-freebsd-validate-install/bin/guile
else
cat >&2 <<'EOF'
A fixed local Guile build is required for this harness.
The packaged FreeBSD guile3 binary still crashes in system*/spawn/open-pipe*.
Set GUILE_BIN to a locally built fixed Guile, for example:
GUILE_BIN=/tmp/guile-freebsd-validate-install/bin/guile
EOF
exit 1
fi
if [ ! -x "$guile_bin" ]; then
echo "Guile binary is not executable: $guile_bin" >&2
exit 1
fi
for required_var in PACKAGE_NAME PACKAGE_VERSION PACKAGE_SOURCE_URL PACKAGE_NIX_BASE32 VERIFY_KIND BINARY_RELATIVE_PATH; do
eval "value=\${$required_var:-}"
if [ -z "$value" ]; then
echo "Required environment variable is missing: $required_var" >&2
exit 1
fi
done
guile_prefix=$(CDPATH= cd -- "$(dirname "$guile_bin")/.." && pwd)
guile_lib_dir=$guile_prefix/lib
if [ -e "$guile_lib_dir/libguile-3.0.so.1" ]; then
if [ -n "${LD_LIBRARY_PATH:-}" ]; then
export LD_LIBRARY_PATH="$guile_lib_dir:$LD_LIBRARY_PATH"
else
export LD_LIBRARY_PATH="$guile_lib_dir"
fi
fi
cleanup=0
if [ -n "${WORKDIR:-}" ]; then
workdir=$WORKDIR
mkdir -p "$workdir"
else
workdir=$(mktemp -d /tmp/fruix-gnu-package-freebsd.XXXXXX)
cleanup=1
fi
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
cleanup=0
fi
cleanup_workdir() {
if [ "$cleanup" -eq 1 ]; then
rm -rf "$workdir"
fi
}
trap cleanup_workdir EXIT INT TERM
export GUILE_BIN="$guile_bin"
export GUIX_SOURCE_DIR="$guix_source_dir"
export WORKDIR="$workdir"
export GUILE_AUTO_COMPILE=0
if [ -n "${GUILE_LOAD_PATH:-}" ]; then
export GUILE_LOAD_PATH="$guix_source_dir:$GUILE_LOAD_PATH"
else
export GUILE_LOAD_PATH="$guix_source_dir"
fi
printf 'Using package: %s %s\n' "$PACKAGE_NAME" "$PACKAGE_VERSION"
printf 'Using Guile: %s\n' "$guile_bin"
printf 'Using LD_LIBRARY_PATH: %s\n' "${LD_LIBRARY_PATH:-<unset>}"
printf 'Working directory: %s\n' "$workdir"
"$guile_bin" -s "$runner_scm"

View File

@@ -0,0 +1,78 @@
--- a/guix/ui.scm
+++ b/guix/ui.scm
@@ -37,6 +37,7 @@
;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
(define-module (guix ui) ;import in user interfaces only
+ #:declarative? #f
#:use-module (guix i18n)
#:use-module (guix colors)
#:use-module (guix diagnostics)
@@ -562,20 +563,25 @@
(define* (show-version-and-exit #:optional (command (car (command-line))))
"Display version information for COMMAND and `(exit 0)'."
- (leave-on-EPIPE
- (simple-format #t "~a (~a) ~a~%"
- command %guix-package-name %guix-version)
- (format #t "Copyright ~a 2026 ~a"
- ;; TRANSLATORS: Translate "(C)" to the copyright symbol
- ;; (C-in-a-circle), if this symbol is available in the user's
- ;; locale. Otherwise, do not translate "(C)"; leave it as-is. */
- (G_ "(C)")
- (G_ "the Guix authors\n"))
- (display (G_"\
+ (catch 'system-error
+ (lambda ()
+ (simple-format #t "~a (~a) ~a~%"
+ command %guix-package-name %guix-version)
+ (format #t "Copyright ~a 2026 ~a"
+ ;; TRANSLATORS: Translate "(C)" to the copyright symbol
+ ;; (C-in-a-circle), if this symbol is available in the user's
+ ;; locale. Otherwise, do not translate "(C)"; leave it as-is. */
+ (G_ "(C)")
+ (G_ "the Guix authors\n"))
+ (display (G_"\
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
-")))
+")))
+ (lambda args
+ (if (= EPIPE (system-error-errno args))
+ (primitive-_exit 0)
+ (apply throw args))))
(exit 0))
(define (show-bug-report-information)
@@ -2388,7 +2394,7 @@
((or ("-h") ("--help"))
(leave-on-EPIPE (show-guix-help)))
((or ("-V") ("--version"))
- (show-version-and-exit "guix"))
+ (show-version-and-exit (basename (or (program-name) "guix"))))
(((? option? o) args ...)
(format (current-error-port)
(G_ "guix: unrecognized option '~a'~%") o)
@@ -2404,8 +2410,9 @@
args)))))
(define (guix-main arg0 . args)
- (initialize-guix)
- (apply run-guix args))
+ (parameterize ((program-name (basename arg0)))
+ (initialize-guix)
+ (apply run-guix args)))
;;; Local Variables:
;;; eval: (put 'guard* 'scheme-indent-function 2)
--- a/guix/scripts/repl.scm
+++ b/guix/scripts/repl.scm
@@ -19,6 +19,7 @@
;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
(define-module (guix scripts repl)
+ #:declarative? #f
#:use-module (guix ui)
#:use-module (guix scripts)
#:use-module (guix repl)

View File

@@ -0,0 +1,58 @@
--- a/nix/nix-daemon/nix-daemon.cc
+++ b/nix/nix-daemon/nix-daemon.cc
@@ -169,7 +169,11 @@
static void handleSignal(int signum)
{
+#if defined(__FreeBSD__)
+ string name = getprogname();
+#else
string name = program_invocation_short_name;
+#endif
auto message = name + ": PID " + std::to_string(getpid())
+ " caught signal " + std::to_string(signum) + "\n";
writeFull(STDERR_FILENO, (unsigned char *) message.c_str(), message.length());
@@ -940,12 +944,12 @@
/* If we're on a TCP connection, disable Nagle's algorithm so that
data is sent as soon as possible. */
- (void) setsockopt(remote, SOL_TCP, TCP_NODELAY,
+ (void) setsockopt(remote, IPPROTO_TCP, TCP_NODELAY,
&enabled, sizeof enabled);
#if defined(TCP_QUICKACK)
/* Enable TCP quick-ack if applicable; this might help a little. */
- (void) setsockopt(remote, SOL_TCP, TCP_QUICKACK,
+ (void) setsockopt(remote, IPPROTO_TCP, TCP_QUICKACK,
&enabled, sizeof enabled);
#endif
}
--- a/nix/libutil/spawn.cc
+++ b/nix/libutil/spawn.cc
@@ -31,6 +31,10 @@
#include <cstdlib>
#include <cassert>
#include <format>
+
+#if defined(__FreeBSD__)
+extern char **environ;
+#endif
#if HAVE_SYS_MOUNT_H
#include <sys/mount.h>
--- a/nix/libutil/archive.cc
+++ b/nix/libutil/archive.cc
@@ -298,9 +298,10 @@
errno = posix_fallocate(fd, 0, len);
/* Note that EINVAL may indicate that the underlying
filesystem doesn't support preallocation (e.g. on
- OpenSolaris). Since preallocation is just an
- optimisation, ignore it. */
- if (errno && errno != EINVAL)
+ OpenSolaris). On FreeBSD, EOPNOTSUPP/ENOTSUP can be
+ returned for the same reason. Since preallocation is
+ just an optimisation, ignore those cases. */
+ if (errno && errno != EINVAL && errno != EOPNOTSUPP && errno != ENOTSUP)
throw SysError(std::format("preallocating file of {} bytes", len));
}
#endif

View File

@@ -0,0 +1,98 @@
#!/bin/sh
set -eu
repo_root=$(CDPATH= cd -- "$(dirname "$0")/../.." && pwd)
metadata_target=${METADATA_OUT:-}
cleanup=0
if [ -n "${WORKDIR:-}" ]; then
workdir=$WORKDIR
mkdir -p "$workdir"
else
workdir=$(mktemp -d /tmp/fruix-phase5-runtime.XXXXXX)
cleanup=1
fi
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
cleanup=0
fi
cleanup_workdir() {
if [ "$cleanup" -eq 1 ]; then
rm -rf "$workdir"
fi
}
trap cleanup_workdir EXIT INT TERM
setup_metadata=$workdir/setup-metadata.txt
setup_env=$workdir/setup-env.sh
runtime_log=$workdir/runtime-command.log
metadata_file=$workdir/phase5-runtime-metadata.txt
WORKDIR=$workdir/setup KEEP_WORKDIR=1 \
METADATA_OUT=$setup_metadata ENV_OUT=$setup_env \
"$repo_root/tests/guix/setup-phase5-checkout.sh" >"$workdir/setup.log" 2>&1
# shellcheck disable=SC1090
. "$setup_env"
cd "$PHASE5_BUILDDIR"
export LD_LIBRARY_PATH GUILE_LOAD_PATH GUILE_LOAD_COMPILED_PATH GUILE_EXTENSIONS_PATH
export GUILE_AUTO_COMPILE=0
./pre-inst-env guix --version >"$workdir/guix-version.log" 2>&1
./pre-inst-env guix repl --help >"$workdir/guix-repl-help.log" 2>&1
./pre-inst-env guix build --help >"$workdir/guix-build-help.log" 2>&1
./pre-inst-env fruix --version >"$workdir/fruix-version.log" 2>&1
first_guix_version_line=$(sed -n '1p' "$workdir/guix-version.log")
first_fruix_version_line=$(sed -n '1p' "$workdir/fruix-version.log")
repl_usage_line=$(grep -m1 '^Usage:' "$workdir/guix-repl-help.log" || true)
build_usage_line=$(grep -m1 '^Usage:' "$workdir/guix-build-help.log" || true)
printf '%s\n' "$first_guix_version_line" | grep -q '^guix (GNU Guix) ' || {
echo "unexpected guix version output: $first_guix_version_line" >&2
exit 1
}
printf '%s\n' "$first_fruix_version_line" | grep -q '^fruix (GNU Guix) ' || {
echo "unexpected fruix version output: $first_fruix_version_line" >&2
exit 1
}
printf '%s\n' "$repl_usage_line" | grep -q '^Usage: guix repl ' || {
echo "unexpected guix repl help output: $repl_usage_line" >&2
exit 1
}
printf '%s\n' "$build_usage_line" | grep -q '^Usage: guix build' || {
echo "unexpected guix build help output: $build_usage_line" >&2
exit 1
}
cat >"$metadata_file" <<EOF
workdir=$workdir
setup_metadata=$setup_metadata
setup_env=$setup_env
phase5_builddir=$PHASE5_BUILDDIR
phase5_pre_inst_env=$PHASE5_PRE_INST_ENV
guix_version_log=$workdir/guix-version.log
fruix_version_log=$workdir/fruix-version.log
guix_repl_help_log=$workdir/guix-repl-help.log
guix_build_help_log=$workdir/guix-build-help.log
first_guix_version_line=$first_guix_version_line
first_fruix_version_line=$first_fruix_version_line
repl_usage_line=$repl_usage_line
build_usage_line=$build_usage_line
fruix_frontend_policy=symlinked scripts/fruix frontend over upstream-derived guix internals
EOF
if [ -n "$metadata_target" ]; then
mkdir -p "$(dirname "$metadata_target")"
cp "$metadata_file" "$metadata_target"
fi
printf 'PASS phase5-checkout-runtime\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"

View File

@@ -0,0 +1,215 @@
#!/bin/sh
set -eu
repo_root=$(CDPATH= cd -- "$(dirname "$0")/../.." && pwd)
metadata_target=${METADATA_OUT:-}
cleanup=0
if [ -n "${WORKDIR:-}" ]; then
workdir=$WORKDIR
mkdir -p "$workdir"
else
workdir=$(mktemp -d /tmp/fruix-phase5-daemon.XXXXXX)
cleanup=1
fi
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
cleanup=0
fi
setup_metadata=$workdir/setup-metadata.txt
setup_env=$workdir/setup-env.sh
metadata_file=$workdir/phase5-daemon-rpc-metadata.txt
daemon_socket=$workdir/guix-daemon.sock
daemon_log=$workdir/guix-daemon.log
scheme_log=$workdir/phase5-build.log
scheme_template=$workdir/phase5-build.scm.in
scheme_file=$workdir/phase5-build.scm
source_file=$workdir/phase5-source.txt
daemon_pid=
cleanup_workdir() {
if [ -n "$daemon_pid" ]; then
sudo kill "$daemon_pid" >/dev/null 2>&1 || true
wait "$daemon_pid" 2>/dev/null || true
fi
rm -f "$daemon_socket"
if [ "$cleanup" -eq 1 ]; then
rm -rf "$workdir"
fi
}
trap cleanup_workdir EXIT INT TERM
WORKDIR=$workdir/setup KEEP_WORKDIR=1 \
METADATA_OUT=$setup_metadata ENV_OUT=$setup_env \
"$repo_root/tests/guix/setup-phase5-checkout.sh" >"$workdir/setup.log" 2>&1
# shellcheck disable=SC1090
. "$setup_env"
cd "$PHASE5_BUILDDIR"
export LD_LIBRARY_PATH GUILE_LOAD_PATH GUILE_LOAD_COMPILED_PATH GUILE_EXTENSIONS_PATH
export GUILE_AUTO_COMPILE=0
printf 'phase5-daemon-build-source\n' > "$source_file"
cat >"$scheme_template" <<'EOF'
(use-modules (guix store)
(guix monads)
(guix gexp)
(guix packages)
(guix build-system)
(guix derivations)
(ice-9 match)
(srfi srfi-1)
(rnrs io ports))
(define source-file "__SOURCE_FILE__")
(call-with-output-file source-file
(lambda (port)
(display "phase5-daemon-build-source\n" port)))
(define source-item
(local-file source-file "__SOURCE_NAME__"))
(define* (phase5-lower name #:key source inputs native-inputs outputs system target #:allow-other-keys)
(bag
(name name)
(system system)
(target target)
(host-inputs `(,@(if source `(("source" ,source)) '()) ,@inputs))
(build-inputs native-inputs)
(outputs outputs)
(build phase5-build)
(arguments `(#:source ,source))))
(define* (phase5-build name inputs #:key source (outputs '("out")) (system (%current-system)) #:allow-other-keys)
(mlet* %store-monad ((shell (interned-file "/bin/sh" "sh" #:recursive? #t))
(source* (lower-object source system))
(builder (text-file "phase5-builder.sh"
"#!/bin/sh\nset -eu\n/bin/mkdir -p \"$out\"\n/bin/cp \"$1\" \"$out/payload.txt\"\nprintf '%s\\n' \"$1\" > \"$out/source-path.txt\"\n"
(list (if (derivation? source*)
(derivation->output-path source*)
source*)))))
(lambda (store)
(values (derivation store name shell
(list "-e" builder
(if (derivation? source*)
(derivation->output-path source*)
source*))
#:env-vars '(("HOME" . "/homeless"))
#:inputs `(,@(if (derivation? source*)
`((,source*))
'())
(,shell) (,builder))
#:sources `(,shell ,builder)
#:system system
#:outputs outputs)
store))))
(define phase5-build-system
(build-system
(name 'phase5-freebsd)
(description "Phase 5 FreeBSD daemon test build system")
(lower phase5-lower)))
(define phase5-package
(package
(name "phase5-freebsd-daemon-build")
(version "0")
(source source-item)
(build-system phase5-build-system)
(synopsis "Phase 5 daemon build test package")
(description "Minimal package used to validate daemon-backed builds on FreeBSD.")
(home-page "https://example.invalid/fruix")
(license #f)))
(let* ((store (open-connection))
(drv (run-with-store store
(mlet* %store-monad ((bag -> (package->bag phase5-package (%current-system) #f))
(drv (bag->derivation bag phase5-package)))
(return drv)))))
(build-derivations store (list drv))
(let* ((out (derivation->output-path drv))
(payload (call-with-input-file (string-append out "/payload.txt") get-string-all))
(source-path (call-with-input-file (string-append out "/source-path.txt") get-string-all)))
(format #t "drv-path=~a~%" (derivation-file-name drv))
(format #t "out-path=~a~%" out)
(format #t "payload=~a" payload)
(format #t "source-path=~a" source-path)))
EOF
sed \
-e "s|__SOURCE_FILE__|$source_file|g" \
-e "s|__SOURCE_NAME__|$(basename "$source_file")|g" \
"$scheme_template" > "$scheme_file"
sudo env GUIX_DAEMON_SOCKET=unix://$daemon_socket \
"$PHASE5_GUIX_DAEMON" --disable-chroot --listen "$daemon_socket" --no-substitutes \
>"$daemon_log" 2>&1 &
daemon_pid=$!
for _ in 1 2 3 4 5 6 7 8 9 10; do
[ -S "$daemon_socket" ] && break
sleep 1
done
[ -S "$daemon_socket" ] || {
echo "daemon socket was not created: $daemon_socket" >&2
exit 1
}
GUIX_DAEMON_SOCKET=unix://$daemon_socket \
./pre-inst-env fruix repl -- "$scheme_file" >"$scheme_log" 2>&1
drv_path=$(sed -n 's/^drv-path=//p' "$scheme_log")
out_path=$(sed -n 's/^out-path=//p' "$scheme_log")
payload=$(sed -n 's/^payload=//p' "$scheme_log")
source_path=$(sed -n 's/^source-path=//p' "$scheme_log")
[ -n "$drv_path" ] || { echo "missing drv-path in $scheme_log" >&2; exit 1; }
[ -n "$out_path" ] || { echo "missing out-path in $scheme_log" >&2; exit 1; }
[ -n "$payload" ] || { echo "missing payload in $scheme_log" >&2; exit 1; }
[ -n "$source_path" ] || { echo "missing source-path in $scheme_log" >&2; exit 1; }
case "$drv_path" in
/frx/store/*.drv) : ;;
*) echo "unexpected derivation path: $drv_path" >&2; exit 1 ;;
esac
case "$out_path" in
/frx/store/*) : ;;
*) echo "unexpected output path: $out_path" >&2; exit 1 ;;
esac
[ "$payload" = 'phase5-daemon-build-source' ] || {
echo "unexpected payload content: $payload" >&2
exit 1
}
case "$source_path" in
/frx/store/*) : ;;
*) echo "unexpected source store path: $source_path" >&2; exit 1 ;;
esac
cat >"$metadata_file" <<EOF
workdir=$workdir
setup_metadata=$setup_metadata
setup_env=$setup_env
phase5_builddir=$PHASE5_BUILDDIR
daemon_socket=$daemon_socket
daemon_log=$daemon_log
scheme_log=$scheme_log
source_file=$source_file
scheme_file=$scheme_file
drv_path=$drv_path
out_path=$out_path
payload=$payload
source_path=$source_path
frontend_invocation=./pre-inst-env fruix repl -- $scheme_file
EOF
if [ -n "$metadata_target" ]; then
mkdir -p "$(dirname "$metadata_target")"
cp "$metadata_file" "$metadata_target"
fi
printf 'PASS phase5-daemon-rpc\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"

View File

@@ -0,0 +1,203 @@
#!/bin/sh
set -eu
repo_root=$(CDPATH= cd -- "$(dirname "$0")/../.." && pwd)
metadata_target=${METADATA_OUT:-}
cleanup=0
if [ -n "${WORKDIR:-}" ]; then
workdir=$WORKDIR
mkdir -p "$workdir"
else
workdir=$(mktemp -d /tmp/fruix-phase5-derivation.XXXXXX)
cleanup=1
fi
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
cleanup=0
fi
setup_metadata=$workdir/setup-metadata.txt
setup_env=$workdir/setup-env.sh
metadata_file=$workdir/phase5-derivation-metadata.txt
daemon_socket=$workdir/guix-daemon.sock
daemon_log=$workdir/guix-daemon.log
scheme_log=$workdir/phase5-derivation.log
scheme_template=$workdir/phase5-derivation.scm.in
scheme_file=$workdir/phase5-derivation.scm
source_file=$workdir/phase5-source.txt
daemon_pid=
cleanup_workdir() {
if [ -n "$daemon_pid" ]; then
sudo kill "$daemon_pid" >/dev/null 2>&1 || true
wait "$daemon_pid" 2>/dev/null || true
fi
rm -f "$daemon_socket"
if [ "$cleanup" -eq 1 ]; then
rm -rf "$workdir"
fi
}
trap cleanup_workdir EXIT INT TERM
WORKDIR=$workdir/setup KEEP_WORKDIR=1 \
METADATA_OUT=$setup_metadata ENV_OUT=$setup_env \
"$repo_root/tests/guix/setup-phase5-checkout.sh" >"$workdir/setup.log" 2>&1
# shellcheck disable=SC1090
. "$setup_env"
cd "$PHASE5_BUILDDIR"
export LD_LIBRARY_PATH GUILE_LOAD_PATH GUILE_LOAD_COMPILED_PATH GUILE_EXTENSIONS_PATH
export GUILE_AUTO_COMPILE=0
printf 'phase5-derivation-source\n' > "$source_file"
cat >"$scheme_template" <<'EOF'
(use-modules (guix store)
(guix monads)
(guix gexp)
(guix packages)
(guix build-system)
(guix derivations)
(ice-9 match)
(srfi srfi-1))
(define source-file "__SOURCE_FILE__")
(call-with-output-file source-file
(lambda (port)
(display "phase5-derivation-source\n" port)))
(define source-item
(local-file source-file "__SOURCE_NAME__"))
(define* (phase5-lower name #:key source inputs native-inputs outputs system target #:allow-other-keys)
(bag
(name name)
(system system)
(target target)
(host-inputs `(,@(if source `(("source" ,source)) '()) ,@inputs))
(build-inputs native-inputs)
(outputs outputs)
(build phase5-build)
(arguments `(#:source ,source))))
(define* (phase5-build name inputs #:key source (outputs '("out")) (system (%current-system)) #:allow-other-keys)
(mlet* %store-monad ((shell (interned-file "/bin/sh" "sh" #:recursive? #t))
(source* (lower-object source system))
(builder (text-file "phase5-builder.sh"
"#!/bin/sh\nset -eu\n/bin/mkdir -p \"$out\"\n/bin/cp \"$1\" \"$out/payload.txt\"\nprintf '%s\\n' \"$1\" > \"$out/source-path.txt\"\n"
(list (if (derivation? source*)
(derivation->output-path source*)
source*)))))
(lambda (store)
(values (derivation store name shell
(list "-e" builder
(if (derivation? source*)
(derivation->output-path source*)
source*))
#:env-vars '(("HOME" . "/homeless"))
#:inputs `(,@(if (derivation? source*)
`((,source*))
'())
(,shell) (,builder))
#:sources `(,shell ,builder)
#:system system
#:outputs outputs)
store))))
(define phase5-build-system
(build-system
(name 'phase5-freebsd)
(description "Phase 5 FreeBSD lowering test build system")
(lower phase5-lower)))
(define phase5-package
(package
(name "phase5-freebsd-lowering")
(version "0")
(source source-item)
(build-system phase5-build-system)
(synopsis "Phase 5 lowering test package")
(description "Minimal package used to validate lowering on FreeBSD.")
(home-page "https://example.invalid/fruix")
(license #f)))
(run-with-store (open-connection)
(mlet* %store-monad ((bag -> (package->bag phase5-package (%current-system) #f))
(drv (bag->derivation bag phase5-package)))
(return (begin
(format #t "bag-name=~a~%" (bag-name bag))
(format #t "bag-host-inputs=~s~%" (map car (bag-host-inputs bag)))
(format #t "drv-path=~a~%" (derivation-file-name drv))
(format #t "out-path=~a~%" (derivation->output-path drv))))))
EOF
sed \
-e "s|__SOURCE_FILE__|$source_file|g" \
-e "s|__SOURCE_NAME__|$(basename "$source_file")|g" \
"$scheme_template" > "$scheme_file"
sudo env GUIX_DAEMON_SOCKET=unix://$daemon_socket \
"$PHASE5_GUIX_DAEMON" --disable-chroot --listen "$daemon_socket" --no-substitutes \
>"$daemon_log" 2>&1 &
daemon_pid=$!
for _ in 1 2 3 4 5 6 7 8 9 10; do
[ -S "$daemon_socket" ] && break
sleep 1
done
[ -S "$daemon_socket" ] || {
echo "daemon socket was not created: $daemon_socket" >&2
exit 1
}
GUIX_DAEMON_SOCKET=unix://$daemon_socket \
./pre-inst-env fruix repl -- "$scheme_file" >"$scheme_log" 2>&1
bag_name=$(sed -n 's/^bag-name=//p' "$scheme_log")
bag_inputs=$(sed -n 's/^bag-host-inputs=//p' "$scheme_log")
drv_path=$(sed -n 's/^drv-path=//p' "$scheme_log")
out_path=$(sed -n 's/^out-path=//p' "$scheme_log")
[ -n "$bag_name" ] || { echo "missing bag-name in $scheme_log" >&2; exit 1; }
[ -n "$drv_path" ] || { echo "missing drv-path in $scheme_log" >&2; exit 1; }
[ -n "$out_path" ] || { echo "missing out-path in $scheme_log" >&2; exit 1; }
case "$drv_path" in
/frx/store/*.drv) : ;;
*) echo "unexpected derivation path: $drv_path" >&2; exit 1 ;;
esac
case "$out_path" in
/frx/store/*) : ;;
*) echo "unexpected output path: $out_path" >&2; exit 1 ;;
esac
printf '%s' "$bag_inputs" | grep -q 'source' || {
echo "expected source input in bag-host-inputs: $bag_inputs" >&2
exit 1
}
cat >"$metadata_file" <<EOF
workdir=$workdir
setup_metadata=$setup_metadata
setup_env=$setup_env
phase5_builddir=$PHASE5_BUILDDIR
daemon_socket=$daemon_socket
daemon_log=$daemon_log
scheme_log=$scheme_log
source_file=$source_file
scheme_file=$scheme_file
bag_name=$bag_name
bag_host_inputs=$bag_inputs
drv_path=$drv_path
out_path=$out_path
EOF
if [ -n "$metadata_target" ]; then
mkdir -p "$(dirname "$metadata_target")"
cp "$metadata_file" "$metadata_target"
fi
printf 'PASS phase5-derivation-generation\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"

View File

@@ -0,0 +1,199 @@
#!/bin/sh
set -eu
repo_root=$(CDPATH= cd -- "$(dirname "$0")/../.." && pwd)
source_repo=${GUIX_SOURCE_REPO:-"$HOME/repos/guix"}
guile_bin=${GUILE_BIN:-/tmp/guile-freebsd-validate-install/bin/guile}
guile_extra_prefix=${GUILE_EXTRA_PREFIX:-/tmp/guile-gnutls-freebsd-validate-install}
store_dir=${STORE_DIR:-/frx/store}
localstatedir=${LOCALSTATEDIR:-/frx/var}
sysconfdir=${SYSCONFDIR:-/frx/etc}
make_bin=${MAKE_BIN:-gmake}
gsed_bin=${GSED_BIN:-/usr/local/bin/gsed}
metadata_target=${METADATA_OUT:-}
env_target=${ENV_OUT:-}
if [ ! -x "$guile_bin" ]; then
echo "Guile binary is not executable: $guile_bin" >&2
exit 1
fi
if [ ! -d "$source_repo/guix" ]; then
echo "Guix source tree not found at $source_repo" >&2
exit 1
fi
for tool in git patch "$make_bin" gm4 "$gsed_bin"; do
if ! command -v "$tool" >/dev/null 2>&1; then
echo "Required tool not found: $tool" >&2
exit 1
fi
done
if [ ! -f /usr/local/include/argp.h ]; then
echo "argp-standalone headers not found at /usr/local/include/argp.h" >&2
exit 1
fi
cleanup=0
if [ -n "${WORKDIR:-}" ]; then
workdir=$WORKDIR
mkdir -p "$workdir"
else
workdir=$(mktemp -d /tmp/fruix-phase5-checkout.XXXXXX)
cleanup=1
fi
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
cleanup=0
fi
cleanup_workdir() {
if [ "$cleanup" -eq 1 ]; then
rm -rf "$workdir"
fi
}
trap cleanup_workdir EXIT INT TERM
srcclone=$workdir/guix-src
builddir=$workdir/build
metadata_file=$workdir/phase5-checkout-setup-metadata.txt
env_file=$workdir/phase5-checkout-env.sh
bootstrap_log=$workdir/bootstrap.log
configure_log=$workdir/configure.log
make_scripts_log=$workdir/make-scripts.log
daemon_build_log=$workdir/make-guix-daemon.log
runtime_patch=$repo_root/tests/guix/patches/phase5-checkout-runtime.patch
daemon_patch=$repo_root/tests/guix/patches/phase5-guix-daemon-freebsd.patch
guile_bindir=$(CDPATH= cd -- "$(dirname "$guile_bin")" && pwd)
guile_prefix=$(CDPATH= cd -- "$guile_bindir/.." && pwd)
guile_lib_dir=$guile_prefix/lib
guile_version=$(LD_LIBRARY_PATH="$guile_lib_dir${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" "$guile_bin" -c '(display (effective-version))')
extra_site_dir=$guile_extra_prefix/share/guile/site/$guile_version
extra_site_ccache_dir=$guile_extra_prefix/lib/guile/$guile_version/site-ccache
extra_extensions_dir=$guile_extra_prefix/lib/guile/$guile_version/extensions
tool_bindir=$workdir/guile-tools-bin
mkdir -p "$tool_bindir"
ln -sf "$guile_bin" "$tool_bindir/guile"
ln -sf "$guile_bin" "$tool_bindir/guile-3.0"
ln -sf "$guile_bindir/guild" "$tool_bindir/guild"
ln -sf "$guile_bindir/guild" "$tool_bindir/guild-3.0"
ln -sf "$guile_bindir/guile-config" "$tool_bindir/guile-config"
ln -sf "$guile_bindir/guile-config" "$tool_bindir/guile-config-3.0"
ln -sf "$guile_bindir/guile-snarf" "$tool_bindir/guile-snarf"
export PATH="$tool_bindir:$guile_bindir:/usr/local/bin:$PATH"
export ACLOCAL_PATH=/usr/local/share/aclocal${ACLOCAL_PATH:+:$ACLOCAL_PATH}
export PKG_CONFIG_PATH=$guile_extra_prefix/lib/pkgconfig:$guile_extra_prefix/libdata/pkgconfig:$guile_prefix/lib/pkgconfig:/usr/local/libdata/pkgconfig:/usr/local/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}
export CPPFLAGS="-I$guile_prefix/include -I$guile_extra_prefix/include -I/usr/local/include"
export LDFLAGS="-L$guile_lib_dir -L$guile_extra_prefix/lib -L/usr/local/lib -Wl,-rpath,$guile_lib_dir -Wl,-rpath,$guile_extra_prefix/lib -Wl,-rpath,/usr/local/lib"
export LD_LIBRARY_PATH="$guile_extra_prefix/lib:$guile_lib_dir:/usr/local/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
if [ -d "$extra_site_dir" ]; then
export GUILE_LOAD_PATH="$extra_site_dir${GUILE_LOAD_PATH:+:$GUILE_LOAD_PATH}"
fi
if [ -d "$extra_site_ccache_dir" ]; then
export GUILE_LOAD_COMPILED_PATH="$extra_site_ccache_dir${GUILE_LOAD_COMPILED_PATH:+:$GUILE_LOAD_COMPILED_PATH}"
fi
if [ -d "$extra_extensions_dir" ]; then
export GUILE_EXTENSIONS_PATH="$extra_extensions_dir${GUILE_EXTENSIONS_PATH:+:$GUILE_EXTENSIONS_PATH}"
fi
printf 'Working directory: %s\n' "$workdir"
printf 'Cloning source from: %s\n' "$source_repo"
rm -rf "$srcclone" "$builddir"
git clone --shared "$source_repo" "$srcclone" >/dev/null 2>&1
(
cd "$srcclone"
M4=gm4 ./bootstrap
) >"$bootstrap_log" 2>&1
(
cd "$srcclone"
patch -p1 < "$runtime_patch"
patch -p1 < "$daemon_patch"
) >/dev/null
mkdir -p "$builddir"
(
cd "$builddir"
MAKE="$make_bin" \
GUILE="$guile_bin" \
GUILE_EFFECTIVE_VERSION=3.0 \
SED="$gsed_bin" \
"$srcclone/configure" \
--with-courage \
--with-store-dir="$store_dir" \
--localstatedir="$localstatedir" \
--sysconfdir="$sysconfdir"
) >"$configure_log" 2>&1
(
cd "$builddir"
"$make_bin" -j1 scripts/guix
) >"$make_scripts_log" 2>&1
(
cd "$builddir"
"$make_bin" -j1 LIBS='-L/usr/local/lib -largp -lintl' nix/libstore/schema.sql.hh guix-daemon
) >"$daemon_build_log" 2>&1
ln -sf guix "$builddir/scripts/fruix"
cat >"$env_file" <<EOF
export PHASE5_WORKDIR='$workdir'
export PHASE5_SRCCLONE='$srcclone'
export PHASE5_BUILDDIR='$builddir'
export PHASE5_PRE_INST_ENV='$builddir/pre-inst-env'
export PHASE5_GUIX_BIN='$builddir/scripts/guix'
export PHASE5_FRUIX_BIN='$builddir/scripts/fruix'
export PHASE5_GUIX_DAEMON='$builddir/guix-daemon'
export LD_LIBRARY_PATH='$guile_extra_prefix/lib:$guile_lib_dir:/usr/local/lib'
export GUILE_LOAD_PATH='$extra_site_dir'
export GUILE_LOAD_COMPILED_PATH='$extra_site_ccache_dir'
export GUILE_EXTENSIONS_PATH='$extra_extensions_dir'
EOF
cat >"$metadata_file" <<EOF
workdir=$workdir
source_repo=$source_repo
srcclone=$srcclone
builddir=$builddir
guile_bin=$guile_bin
guile_extra_prefix=$guile_extra_prefix
guile_version=$guile_version
store_dir=$store_dir
localstatedir=$localstatedir
sysconfdir=$sysconfdir
runtime_patch=$runtime_patch
daemon_patch=$daemon_patch
bootstrap_log=$bootstrap_log
configure_log=$configure_log
make_scripts_log=$make_scripts_log
daemon_build_log=$daemon_build_log
env_file=$env_file
pre_inst_env=$builddir/pre-inst-env
guix_bin=$builddir/scripts/guix
fruix_bin=$builddir/scripts/fruix
guix_daemon=$builddir/guix-daemon
EOF
if [ -n "$metadata_target" ]; then
mkdir -p "$(dirname "$metadata_target")"
cp "$metadata_file" "$metadata_target"
fi
if [ -n "$env_target" ]; then
mkdir -p "$(dirname "$env_target")"
cp "$env_file" "$env_target"
fi
printf 'PASS phase5-checkout-setup\n'
printf 'Environment file: %s\n' "$env_file"
printf 'Metadata file: %s\n' "$metadata_file"
if [ -n "$env_target" ]; then
printf 'Copied environment file to: %s\n' "$env_target"
fi
if [ -n "$metadata_target" ]; then
printf 'Copied metadata file to: %s\n' "$metadata_target"
fi
printf '%s\n' '--- metadata ---'
cat "$metadata_file"

View File

@@ -0,0 +1,258 @@
(use-modules (fruix packages freebsd)
(guix build utils)
(ice-9 format)
(ice-9 ftw)
(ice-9 match)
(ice-9 popen)
(srfi srfi-1)
(srfi srfi-13)
(rnrs io ports))
(define (getenv* name default)
(or (getenv name) default))
(define (trim-trailing-newlines str)
(let loop ((len (string-length str)))
(if (and (> len 0)
(char=? (string-ref str (- len 1)) #\newline))
(loop (- len 1))
(substring str 0 len))))
(define (command-output program . args)
(let* ((port (apply open-pipe* OPEN_READ program args))
(output (get-string-all port))
(status (close-pipe port)))
(unless (zero? status)
(error (format #f "command failed: ~a ~s => ~a"
program args status)))
(trim-trailing-newlines output)))
(define workdir
(or (getenv "WORKDIR")
(error "WORKDIR environment variable is required")))
(define store-dir
(string-append workdir "/store"))
(define profile-dir
(string-append workdir "/profiles/freebsd-development"))
(define build-dir
(string-append workdir "/profile-build"))
(define metadata-file
(string-append workdir "/freebsd-package-profile-prototype-metadata.txt"))
(define built-package-paths '())
(define (sha256-string text)
(let ((temp-file (string-append workdir "/hash-input.txt")))
(call-with-output-file temp-file
(lambda (port)
(display text port)))
(command-output "sha256" "-q" temp-file)))
(define (package-manifest-string package)
(string-append
"name=" (freebsd-package-name package) "\n"
"version=" (freebsd-package-version package) "\n"
"build-system=" (symbol->string (freebsd-package-build-system package)) "\n"
"inputs=" (string-join (map freebsd-package-name
(freebsd-package-inputs package)) ",") "\n"
"install-plan=" (object->string (freebsd-package-install-plan package))))
(define (package-output-path package)
(let ((cached (assoc-ref built-package-paths (freebsd-package-name package))))
(if cached
cached
#f)))
(define (remember-package-output! package path)
(set! built-package-paths
(acons (freebsd-package-name package) path built-package-paths)))
(define (write-file path content)
(call-with-output-file path
(lambda (port)
(display content port))))
(define (install-wrapper source destination)
(write-file destination
(string-append "#!/bin/sh\nexec " source " \"$@\"\n"))
(chmod destination #o555))
(define (materialize-plan-entry output-path entry)
(match entry
(('file source target)
(let ((destination (string-append output-path "/" target)))
(mkdir-p (dirname destination))
(if (string-prefix? "bin/" target)
(install-wrapper source destination)
(symlink source destination))))
(('directory source target)
(let ((destination (string-append output-path "/" target)))
(mkdir-p (dirname destination))
(copy-recursively source destination)))
(_
(error (format #f "unsupported install plan entry: ~s" entry)))))
(define (materialize-package package)
(or (package-output-path package)
(let* ((input-paths (map materialize-package
(freebsd-package-inputs package)))
(hash (sha256-string (package-manifest-string package)))
(output-path (string-append store-dir "/" hash "-"
(freebsd-package-name package)
"-" (freebsd-package-version package))))
(unless (file-exists? output-path)
(mkdir-p output-path)
(for-each (lambda (entry)
(materialize-plan-entry output-path entry))
(freebsd-package-install-plan package))
(write-file (string-append output-path "/.references")
(string-join input-paths "\n"))
(write-file (string-append output-path "/.fruix-package")
(package-manifest-string package)))
(remember-package-output! package output-path)
output-path)))
(define (directory-entries path)
(filter (lambda (entry)
(not (member entry '("." ".."))))
(scandir path)))
(define (merge-output-into-profile output-path profile-root)
(define (walk relative)
(let ((source (if (string-null? relative)
output-path
(string-append output-path "/" relative))))
(for-each
(lambda (entry)
(unless (member entry '(".references" ".fruix-package"))
(let* ((entry-relative (if (string-null? relative)
entry
(string-append relative "/" entry)))
(source-entry (string-append output-path "/" entry-relative))
(target-entry (string-append profile-root "/" entry-relative))
(st (lstat source-entry)))
(if (eq? 'directory (stat:type st))
(begin
(mkdir-p target-entry)
(walk entry-relative))
(begin
(mkdir-p (dirname target-entry))
(if (file-exists? target-entry)
(let ((existing (false-if-exception (readlink target-entry))))
(unless (or (and existing
(string=? existing source-entry))
(and existing
(file-exists? existing)
(same-file-contents? existing source-entry)))
(error (format #f "profile collision for ~a" target-entry))))
(symlink source-entry target-entry)))))))
(directory-entries source))))
(mkdir-p profile-root)
(walk ""))
(define (profile-file path)
(string-append profile-dir "/" path))
(define (assert-file-exists path)
(unless (file-exists? path)
(error (format #f "required file missing: ~a" path))))
(define (same-file-contents? a b)
(zero? (system* "cmp" "-s" a b)))
(define (validate-profile)
(let* ((bash-version (command-output (profile-file "bin/bash") "--version"))
(make-version (command-output (profile-file "bin/make") "--version"))
(autoconf-version (command-output (profile-file "bin/autoconf") "--version"))
(cc-version (command-output (profile-file "bin/cc") "--version"))
(hello-source (string-append build-dir "/hello.c"))
(hello-binary (string-append build-dir "/hello"))
(hello-output
(begin
(mkdir-p build-dir)
(write-file hello-source
"#include <stdio.h>\nint main(void){puts(\"hello-from-freebsd-profile\");return 0;}\n")
(command-output (profile-file "bin/sh") "-c"
(string-append
"PATH=" profile-dir "/bin:/usr/bin:/bin "
"CPPFLAGS=-I" profile-dir "/include "
"LDFLAGS=-L" profile-dir "/lib "
(profile-file "bin/cc") " " hello-source
" -o " hello-binary
" && " hello-binary)))))
(for-each assert-file-exists
(list (profile-file "bin/sh")
(profile-file "bin/bash")
(profile-file "bin/cc")
(profile-file "bin/make")
(profile-file "bin/autoreconf")
(profile-file "bin/pkg-config")
(profile-file "include/sys/param.h")
(profile-file "lib/libc.so.7")
(profile-file "lib/libcrypto.so")
(profile-file "lib/libz.so.6")
(profile-file "boot/kernel/kernel")))
(unless (string-prefix? "GNU bash, version" (car (string-split bash-version #\newline)))
(error "unexpected bash version output"))
(unless (string-prefix? "GNU Make" (car (string-split make-version #\newline)))
(error "unexpected make version output"))
(unless (string-prefix? "autoconf" (car (string-split autoconf-version #\newline)))
(error "unexpected autoconf version output"))
(unless (string-contains cc-version "FreeBSD clang version")
(error "unexpected cc version output"))
(unless (string=? hello-output "hello-from-freebsd-profile")
(error (format #f "unexpected hello output: ~s" hello-output)))
`((bash-version . ,(car (string-split bash-version #\newline)))
(make-version . ,(car (string-split make-version #\newline)))
(autoconf-version . ,(car (string-split autoconf-version #\newline)))
(cc-version . ,(car (string-split cc-version #\newline)))
(hello-output . ,hello-output))))
(mkdir-p workdir)
(mkdir-p store-dir)
(mkdir-p profile-dir)
(let* ((core-paths (map materialize-package %freebsd-core-packages))
(profile-package-paths (map materialize-package
%freebsd-development-profile-packages))
(validation (begin
(for-each (lambda (path)
(merge-output-into-profile path profile-dir))
profile-package-paths)
(validate-profile))))
(call-with-output-file metadata-file
(lambda (port)
(format port "workdir=~a~%" workdir)
(format port "store_dir=~a~%" store-dir)
(format port "profile_dir=~a~%" profile-dir)
(format port "core_package_count=~a~%" (length %freebsd-core-packages))
(format port "profile_package_count=~a~%"
(length %freebsd-development-profile-packages))
(for-each
(lambda (package)
(format port "package=~a version=~a build_system=~a inputs=~a~%"
(freebsd-package-name package)
(freebsd-package-version package)
(freebsd-package-build-system package)
(map freebsd-package-name (freebsd-package-inputs package))))
%freebsd-core-packages)
(for-each
(lambda (path)
(format port "store_item=~a~%" path))
core-paths)
(format port "bash_version=~a~%" (assoc-ref validation 'bash-version))
(format port "make_version=~a~%" (assoc-ref validation 'make-version))
(format port "autoconf_version=~a~%" (assoc-ref validation 'autoconf-version))
(format port "cc_version=~a~%" (assoc-ref validation 'cc-version))
(format port "hello_output=~a~%" (assoc-ref validation 'hello-output))))
(when (getenv "METADATA_OUT")
(mkdir-p (dirname (getenv "METADATA_OUT")))
(copy-file metadata-file (getenv "METADATA_OUT")))
(format #t "PASS freebsd-package-profile-prototype~%")
(format #t "Profile directory: ~a~%" profile-dir)
(format #t "Metadata file: ~a~%" metadata-file)
(when (getenv "METADATA_OUT")
(format #t "Copied metadata to: ~a~%" (getenv "METADATA_OUT")))
(display "--- metadata ---\n")
(display (call-with-input-file metadata-file get-string-all)))

View File

@@ -0,0 +1,71 @@
#!/bin/sh
set -eu
project_root=${PROJECT_ROOT:-$(pwd)}
guix_source_dir=${GUIX_SOURCE_DIR:-"$HOME/repos/guix"}
script_dir=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
runner_scm=$script_dir/freebsd-package-profile-prototype.scm
if [ -n "${GUILE_BIN:-}" ]; then
guile_bin=$GUILE_BIN
elif [ -x /tmp/guile-freebsd-validate-install/bin/guile ]; then
guile_bin=/tmp/guile-freebsd-validate-install/bin/guile
else
cat >&2 <<'EOF'
A fixed local Guile build is required for this harness.
Set GUILE_BIN to a locally built fixed Guile, for example:
GUILE_BIN=/tmp/guile-freebsd-validate-install/bin/guile
EOF
exit 1
fi
if [ ! -x "$guile_bin" ]; then
echo "Guile binary is not executable: $guile_bin" >&2
exit 1
fi
guile_prefix=$(CDPATH= cd -- "$(dirname "$guile_bin")/.." && pwd)
guile_lib_dir=$guile_prefix/lib
if [ -e "$guile_lib_dir/libguile-3.0.so.1" ]; then
if [ -n "${LD_LIBRARY_PATH:-}" ]; then
export LD_LIBRARY_PATH="$guile_lib_dir:$LD_LIBRARY_PATH"
else
export LD_LIBRARY_PATH="$guile_lib_dir"
fi
fi
cleanup=0
if [ -n "${WORKDIR:-}" ]; then
workdir=$WORKDIR
mkdir -p "$workdir"
else
workdir=$(mktemp -d /tmp/fruix-freebsd-package-profile.XXXXXX)
cleanup=1
fi
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
cleanup=0
fi
cleanup_workdir() {
if [ "$cleanup" -eq 1 ]; then
rm -rf "$workdir"
fi
}
trap cleanup_workdir EXIT INT TERM
export GUILE_BIN="$guile_bin"
export GUILE_AUTO_COMPILE=0
export WORKDIR="$workdir"
export PROJECT_ROOT="$project_root"
if [ -n "${GUILE_LOAD_PATH:-}" ]; then
export GUILE_LOAD_PATH="$project_root/modules:$guix_source_dir:$GUILE_LOAD_PATH"
else
export GUILE_LOAD_PATH="$project_root/modules:$guix_source_dir"
fi
printf 'Using Guile: %s\n' "$guile_bin"
printf 'Using LD_LIBRARY_PATH: %s\n' "${LD_LIBRARY_PATH:-<unset>}"
printf 'Working directory: %s\n' "$workdir"
"$guile_bin" -s "$runner_scm"

View File

@@ -0,0 +1,174 @@
#!/bin/sh
set -eu
fibers_repo=${GUILE_FIBERS_REPO:-"https://codeberg.org/guile/fibers"}
fibers_version=${GUILE_FIBERS_VERSION:-1.4.2}
install_prefix=${INSTALL_PREFIX:-/tmp/guile-gnutls-freebsd-validate-install}
guile_bin=${GUILE_BIN:-/tmp/guile-freebsd-validate-install/bin/guile}
make_bin=${MAKE_BIN:-gmake}
if [ ! -x "$guile_bin" ]; then
echo "Guile binary is not executable: $guile_bin" >&2
exit 1
fi
for tool in git autoreconf pkg-config "$make_bin"; do
if ! command -v "$tool" >/dev/null 2>&1; then
echo "Required tool not found: $tool" >&2
exit 1
fi
done
cleanup=0
if [ -n "${WORKDIR:-}" ]; then
workdir=$WORKDIR
mkdir -p "$workdir"
else
workdir=$(mktemp -d /tmp/fruix-guile-fibers.XXXXXX)
cleanup=1
fi
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
cleanup=0
fi
cleanup_workdir() {
if [ "$cleanup" -eq 1 ]; then
rm -rf "$workdir"
fi
}
trap cleanup_workdir EXIT INT TERM
guile_bindir=$(CDPATH= cd -- "$(dirname "$guile_bin")" && pwd)
guile_prefix=$(CDPATH= cd -- "$guile_bindir/.." && pwd)
guile_lib_dir=$guile_prefix/lib
guile_version=$(LD_LIBRARY_PATH="$guile_lib_dir${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" "$guile_bin" -c '(display (effective-version))')
site_dir=$install_prefix/share/guile/site/$guile_version
site_ccache_dir=$install_prefix/lib/guile/$guile_version/site-ccache
extensions_dir=$install_prefix/lib/guile/$guile_version/extensions
tool_bindir=$workdir/guile-tools-bin
mkdir -p "$tool_bindir"
ln -sf "$guile_bin" "$tool_bindir/guile-3.0"
ln -sf "$guile_bin" "$tool_bindir/guile"
ln -sf "$guile_bindir/guild" "$tool_bindir/guild-3.0"
ln -sf "$guile_bindir/guild" "$tool_bindir/guild"
ln -sf "$guile_bindir/guile-config" "$tool_bindir/guile-config-3.0"
ln -sf "$guile_bindir/guile-config" "$tool_bindir/guile-config"
ln -sf "$guile_bindir/guile-snarf" "$tool_bindir/guile-snarf"
export PATH="$tool_bindir:$guile_bindir:/usr/local/bin:$PATH"
export ACLOCAL_PATH=/usr/local/share/aclocal${ACLOCAL_PATH:+:$ACLOCAL_PATH}
export PKG_CONFIG_PATH=$install_prefix/lib/pkgconfig:$install_prefix/libdata/pkgconfig:/usr/local/libdata/pkgconfig:/usr/local/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}
export CPPFLAGS="-I$guile_prefix/include -I$install_prefix/include -I/usr/local/include"
export LDFLAGS="-L$guile_lib_dir -L$install_prefix/lib -L/usr/local/lib -Wl,-rpath,$guile_lib_dir -Wl,-rpath,$install_prefix/lib -Wl,-rpath,/usr/local/lib"
if [ -n "${LD_LIBRARY_PATH:-}" ]; then
export LD_LIBRARY_PATH="$install_prefix/lib:$guile_lib_dir:/usr/local/lib:$LD_LIBRARY_PATH"
else
export LD_LIBRARY_PATH="$install_prefix/lib:$guile_lib_dir:/usr/local/lib"
fi
if [ -d "$site_dir" ]; then
export GUILE_LOAD_PATH="$site_dir${GUILE_LOAD_PATH:+:$GUILE_LOAD_PATH}"
fi
if [ -d "$site_ccache_dir" ]; then
export GUILE_LOAD_COMPILED_PATH="$site_ccache_dir${GUILE_LOAD_COMPILED_PATH:+:$GUILE_LOAD_COMPILED_PATH}"
fi
if [ -d "$extensions_dir" ]; then
export GUILE_EXTENSIONS_PATH="$extensions_dir${GUILE_EXTENSIONS_PATH:+:$GUILE_EXTENSIONS_PATH}"
fi
src_dir=$workdir/guile-fibers
build_dir=$workdir/build
metadata_file=$workdir/guile-fibers-build-metadata.txt
env_file=$workdir/guile-fibers-env.sh
clone_log=$workdir/clone.log
autoreconf_log=$workdir/autoreconf.log
configure_log=$workdir/configure.log
build_log=$workdir/build.log
install_log=$workdir/install.log
printf 'Using Guile: %s\n' "$guile_bin"
printf 'Installing into existing prefix: %s\n' "$install_prefix"
printf 'Working directory: %s\n' "$workdir"
rm -rf \
"$site_dir/fibers" \
"$site_dir/fibers.scm" \
"$site_ccache_dir/fibers" \
"$site_ccache_dir/fibers.go"
mkdir -p "$install_prefix"
git clone --depth 1 --branch "v$fibers_version" "$fibers_repo" "$src_dir" >"$clone_log" 2>&1
fibers_commit=$(git -C "$src_dir" rev-parse HEAD)
(
cd "$src_dir"
autoreconf -vfi
) >"$autoreconf_log" 2>&1
mkdir -p "$build_dir"
(
cd "$build_dir"
"$src_dir/configure" --prefix="$install_prefix"
) >"$configure_log" 2>&1
(
cd "$build_dir"
"$make_bin" GUILE_AUTO_COMPILE=0 -j"${JOBS:-$(sysctl -n hw.ncpu 2>/dev/null || echo 1)}"
) >"$build_log" 2>&1
(
cd "$build_dir"
"$make_bin" GUILE_AUTO_COMPILE=0 install
) >"$install_log" 2>&1
export GUILE_LOAD_PATH="$site_dir${GUILE_LOAD_PATH:+:$GUILE_LOAD_PATH}"
export GUILE_LOAD_COMPILED_PATH="$site_ccache_dir${GUILE_LOAD_COMPILED_PATH:+:$GUILE_LOAD_COMPILED_PATH}"
module_check=$($guile_bin -c '(use-modules (fibers)) (display "ok") (newline)')
if [ "$module_check" != "ok" ]; then
echo "(fibers) module validation failed" >&2
exit 1
fi
cat >"$env_file" <<EOF
export GUILE_EXTRA_PREFIX='$install_prefix'
export GUILE_LOAD_PATH='$site_dir'
export GUILE_LOAD_COMPILED_PATH='$site_ccache_dir'
export GUILE_EXTENSIONS_PATH='$extensions_dir'
export LD_LIBRARY_PATH='$install_prefix/lib:$guile_lib_dir:/usr/local/lib'
EOF
cat >"$metadata_file" <<EOF
fibers_repo=$fibers_repo
fibers_version=$fibers_version
fibers_commit=$fibers_commit
guile_bin=$guile_bin
guile_version=$guile_version
install_prefix=$install_prefix
site_dir=$site_dir
site_ccache_dir=$site_ccache_dir
extensions_dir=$extensions_dir
clone_log=$clone_log
autoreconf_log=$autoreconf_log
configure_log=$configure_log
build_log=$build_log
install_log=$install_log
module_check=$module_check
env_file=$env_file
EOF
if [ -n "${METADATA_OUT:-}" ]; then
mkdir -p "$(dirname "$METADATA_OUT")"
cp "$metadata_file" "$METADATA_OUT"
fi
if [ -n "${ENV_OUT:-}" ]; then
mkdir -p "$(dirname "$ENV_OUT")"
cp "$env_file" "$ENV_OUT"
fi
printf 'PASS local-guile-fibers-build\n'
printf 'Module check: %s\n' "$module_check"
printf 'Environment file: %s\n' "$env_file"
printf 'Metadata file: %s\n' "$metadata_file"
if [ -n "${ENV_OUT:-}" ]; then
printf 'Copied environment file to: %s\n' "$ENV_OUT"
fi
if [ -n "${METADATA_OUT:-}" ]; then
printf 'Copied metadata file to: %s\n' "$METADATA_OUT"
fi
printf '%s\n' '--- metadata ---'
cat "$metadata_file"

View File

@@ -0,0 +1,220 @@
#!/bin/sh
set -eu
source_url=${SHEPHERD_SOURCE_URL:-"https://ftp.gnu.org/gnu/shepherd/shepherd-1.0.9.tar.gz"}
version=${SHEPHERD_VERSION:-1.0.9}
expected_nix_base32=${SHEPHERD_NIX_BASE32:-1mh080060lnycys8yq6kkiy363wif8dsip3nyklgd3a1r22wb274}
install_prefix=${INSTALL_PREFIX:-/tmp/shepherd-freebsd-validate-install}
guile_bin=${GUILE_BIN:-/tmp/guile-freebsd-validate-install/bin/guile}
guile_extra_prefix=${GUILE_EXTRA_PREFIX:-/tmp/guile-gnutls-freebsd-validate-install}
guix_source_dir=${GUIX_SOURCE_DIR:-"$HOME/repos/guix"}
make_bin=${MAKE_BIN:-gmake}
gsed_bin=${GSED_BIN:-/usr/local/bin/gsed}
if [ ! -x "$guile_bin" ]; then
echo "Guile binary is not executable: $guile_bin" >&2
exit 1
fi
if [ ! -d "$guix_source_dir/guix" ]; then
echo "Guix source tree not found at $guix_source_dir" >&2
exit 1
fi
for tool in fetch sha256 bsdtar "$make_bin" "$gsed_bin"; do
if ! command -v "$tool" >/dev/null 2>&1; then
echo "Required tool not found: $tool" >&2
exit 1
fi
done
cleanup=0
if [ -n "${WORKDIR:-}" ]; then
workdir=$WORKDIR
mkdir -p "$workdir"
else
workdir=$(mktemp -d /tmp/fruix-shepherd-build.XXXXXX)
cleanup=1
fi
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
cleanup=0
fi
cleanup_workdir() {
if [ "$cleanup" -eq 1 ]; then
rm -rf "$workdir"
fi
}
trap cleanup_workdir EXIT INT TERM
guile_bindir=$(CDPATH= cd -- "$(dirname "$guile_bin")" && pwd)
guile_prefix=$(CDPATH= cd -- "$guile_bindir/.." && pwd)
guile_lib_dir=$guile_prefix/lib
guile_version=$(LD_LIBRARY_PATH="$guile_lib_dir${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" "$guile_bin" -c '(display (effective-version))')
guile_extra_site_dir=$guile_extra_prefix/share/guile/site/$guile_version
guile_extra_site_ccache_dir=$guile_extra_prefix/lib/guile/$guile_version/site-ccache
guile_extra_extensions_dir=$guile_extra_prefix/lib/guile/$guile_version/extensions
tool_bindir=$workdir/guile-tools-bin
mkdir -p "$tool_bindir"
ln -sf "$guile_bin" "$tool_bindir/guile-3.0"
ln -sf "$guile_bin" "$tool_bindir/guile"
ln -sf "$guile_bindir/guild" "$tool_bindir/guild-3.0"
ln -sf "$guile_bindir/guild" "$tool_bindir/guild"
ln -sf "$guile_bindir/guile-config" "$tool_bindir/guile-config-3.0"
ln -sf "$guile_bindir/guile-config" "$tool_bindir/guile-config"
ln -sf "$guile_bindir/guile-snarf" "$tool_bindir/guile-snarf"
export PATH="$tool_bindir:$guile_bindir:/usr/local/bin:$PATH"
export ACLOCAL_PATH=/usr/local/share/aclocal${ACLOCAL_PATH:+:$ACLOCAL_PATH}
export PKG_CONFIG_PATH=$guile_extra_prefix/lib/pkgconfig:$guile_extra_prefix/libdata/pkgconfig:$guile_prefix/lib/pkgconfig:/usr/local/libdata/pkgconfig:/usr/local/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}
export CPPFLAGS="-I$guile_prefix/include -I$guile_extra_prefix/include -I/usr/local/include"
export LDFLAGS="-L$guile_lib_dir -L$guile_extra_prefix/lib -L/usr/local/lib -Wl,-rpath,$guile_lib_dir -Wl,-rpath,$guile_extra_prefix/lib -Wl,-rpath,/usr/local/lib"
if [ -n "${LD_LIBRARY_PATH:-}" ]; then
export LD_LIBRARY_PATH="$guile_extra_prefix/lib:$guile_lib_dir:/usr/local/lib:$LD_LIBRARY_PATH"
else
export LD_LIBRARY_PATH="$guile_extra_prefix/lib:$guile_lib_dir:/usr/local/lib"
fi
if [ -d "$guile_extra_site_dir" ]; then
export GUILE_LOAD_PATH="$guile_extra_site_dir${GUILE_LOAD_PATH:+:$GUILE_LOAD_PATH}"
fi
if [ -d "$guile_extra_site_ccache_dir" ]; then
export GUILE_LOAD_COMPILED_PATH="$guile_extra_site_ccache_dir${GUILE_LOAD_COMPILED_PATH:+:$GUILE_LOAD_COMPILED_PATH}"
fi
if [ -d "$guile_extra_extensions_dir" ]; then
export GUILE_EXTENSIONS_PATH="$guile_extra_extensions_dir${GUILE_EXTENSIONS_PATH:+:$GUILE_EXTENSIONS_PATH}"
fi
metadata_file=$workdir/shepherd-build-metadata.txt
env_file=$workdir/shepherd-env.sh
src_tarball=$workdir/shepherd-$version.tar.gz
src_dir=$workdir/shepherd-$version
build_dir=$workdir/build
configure_log=$workdir/configure.log
build_log=$workdir/build.log
install_log=$workdir/install.log
version_log=$workdir/version.log
expected_sha256_hex=$(GUILE_AUTO_COMPILE=0 \
GUILE_LOAD_PATH="$guix_source_dir${GUILE_LOAD_PATH:+:$GUILE_LOAD_PATH}" \
"$guile_bin" -c "(use-modules (guix base32) (rnrs bytevectors) (ice-9 format)) (for-each (lambda (b) (format #t \"~2,'0x\" b)) (bytevector->u8-list (nix-base32-string->bytevector (cadr (command-line))))) (newline)" \
"$expected_nix_base32")
printf 'Using Guile: %s\n' "$guile_bin"
printf 'Installing to: %s\n' "$install_prefix"
printf 'Working directory: %s\n' "$workdir"
printf 'Fetching: %s\n' "$source_url"
fetch -o "$src_tarball" "$source_url"
actual_sha256_hex=$(sha256 -q "$src_tarball")
if [ "$actual_sha256_hex" != "$expected_sha256_hex" ]; then
echo "sha256 mismatch for $src_tarball" >&2
echo "expected: $expected_sha256_hex" >&2
echo "actual: $actual_sha256_hex" >&2
exit 1
fi
rm -rf "$src_dir" "$build_dir" "$install_prefix"
mkdir -p "$src_dir" "$build_dir"
bsdtar -xf "$src_tarball" -C "$src_dir" --strip-components=1
(
cd "$build_dir"
"$src_dir/configure" \
--prefix="$install_prefix" \
--localstatedir="$install_prefix/var" \
SED="$gsed_bin" \
GUILE="$guile_bin" \
GUILD="$guile_bindir/guild" \
GUILE_SNARF="$guile_bindir/guile-snarf"
) >"$configure_log" 2>&1
(
cd "$build_dir"
"$make_bin" GUILE_AUTO_COMPILE=0 -j"${JOBS:-$(sysctl -n hw.ncpu 2>/dev/null || echo 1)}"
) >"$build_log" 2>&1
(
cd "$build_dir"
"$make_bin" GUILE_AUTO_COMPILE=0 install
) >"$install_log" 2>&1
shepherd_version_output=$(GUILE_LOAD_PATH="$guile_extra_site_dir${GUILE_LOAD_PATH:+:$GUILE_LOAD_PATH}" \
GUILE_LOAD_COMPILED_PATH="$guile_extra_site_ccache_dir${GUILE_LOAD_COMPILED_PATH:+:$GUILE_LOAD_COMPILED_PATH}" \
GUILE_EXTENSIONS_PATH="$guile_extra_extensions_dir${GUILE_EXTENSIONS_PATH:+:$GUILE_EXTENSIONS_PATH}" \
LD_LIBRARY_PATH="$guile_extra_prefix/lib:$guile_lib_dir:/usr/local/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \
"$install_prefix/bin/shepherd" --version 2>&1 | tee "$version_log")
herd_version_output=$(GUILE_LOAD_PATH="$guile_extra_site_dir${GUILE_LOAD_PATH:+:$GUILE_LOAD_PATH}" \
GUILE_LOAD_COMPILED_PATH="$guile_extra_site_ccache_dir${GUILE_LOAD_COMPILED_PATH:+:$GUILE_LOAD_COMPILED_PATH}" \
GUILE_EXTENSIONS_PATH="$guile_extra_extensions_dir${GUILE_EXTENSIONS_PATH:+:$GUILE_EXTENSIONS_PATH}" \
LD_LIBRARY_PATH="$guile_extra_prefix/lib:$guile_lib_dir:/usr/local/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \
"$install_prefix/bin/herd" --version 2>&1 | tail -n 1)
case "$shepherd_version_output" in
*"shepherd (GNU Shepherd) $version"*) : ;;
*)
echo "shepherd version validation failed" >&2
exit 1
;;
esac
case "$shepherd_version_output" in
*"System lacks support for 'signalfd'; using fallback mechanism."*)
signalfd_fallback=yes
;;
*)
signalfd_fallback=no
;;
esac
cat >"$env_file" <<EOF
export SHEPHERD_PREFIX='$install_prefix'
export SHEPHERD_BIN='$install_prefix/bin/shepherd'
export HERD_BIN='$install_prefix/bin/herd'
export GUILE_EXTRA_PREFIX='$guile_extra_prefix'
export GUILE_LOAD_PATH='$guile_extra_site_dir'
export GUILE_LOAD_COMPILED_PATH='$guile_extra_site_ccache_dir'
export GUILE_EXTENSIONS_PATH='$guile_extra_extensions_dir'
export LD_LIBRARY_PATH='$guile_extra_prefix/lib:$guile_lib_dir:/usr/local/lib'
EOF
cat >"$metadata_file" <<EOF
version=$version
source_url=$source_url
expected_nix_base32=$expected_nix_base32
expected_sha256_hex=$expected_sha256_hex
actual_sha256_hex=$actual_sha256_hex
guile_bin=$guile_bin
guile_version=$guile_version
guile_extra_prefix=$guile_extra_prefix
install_prefix=$install_prefix
configure_log=$configure_log
build_log=$build_log
install_log=$install_log
version_log=$version_log
gsed_bin=$gsed_bin
signalfd_fallback=$signalfd_fallback
shepherd_version_output=$(printf '%s' "$shepherd_version_output" | tr '\n' ' ')
herd_version_output=$(printf '%s' "$herd_version_output" | tr '\n' ' ')
env_file=$env_file
EOF
if [ -n "${METADATA_OUT:-}" ]; then
mkdir -p "$(dirname "$METADATA_OUT")"
cp "$metadata_file" "$METADATA_OUT"
fi
if [ -n "${ENV_OUT:-}" ]; then
mkdir -p "$(dirname "$ENV_OUT")"
cp "$env_file" "$ENV_OUT"
fi
printf 'PASS local-shepherd-build\n'
printf 'Version output validated for GNU Shepherd %s\n' "$version"
printf 'Environment file: %s\n' "$env_file"
printf 'Metadata file: %s\n' "$metadata_file"
if [ -n "${ENV_OUT:-}" ]; then
printf 'Copied environment file to: %s\n' "$ENV_OUT"
fi
if [ -n "${METADATA_OUT:-}" ]; then
printf 'Copied metadata file to: %s\n' "$METADATA_OUT"
fi
printf '%s\n' '--- metadata ---'
cat "$metadata_file"

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"

View File

@@ -0,0 +1,296 @@
#!/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
}
cleanup=0
if [ -n "${WORKDIR:-}" ]; then
workdir=$WORKDIR
mkdir -p "$workdir"
else
workdir=$(mktemp -d /tmp/freebsd-shepherd-init.XXXXXX)
cleanup=1
fi
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
cleanup=0
fi
chmod 0755 "$workdir"
config_file=$workdir/init-config.scm
boot_log=$workdir/boot-order.log
pid_file=$workdir/shepherd.pid
socket_file=$workdir/shepherd.sock
shepherd_log=$workdir/shepherd.log
shepherd_stdout=$workdir/shepherd.out
metadata_file=$workdir/freebsd-shepherd-init-metadata.txt
rc_name=fruix_shepherd_boot_$$
rc_script=/usr/local/etc/rc.d/$rc_name
rc_template=$workdir/rc-template.in
cleanup_workdir() {
run_root service "$rc_name" onestop >/dev/null 2>&1 || true
run_root rm -f "$rc_script"
if [ "$cleanup" -eq 1 ]; then
run_root rm -rf "$workdir"
fi
}
trap cleanup_workdir EXIT INT TERM
cat >"$config_file" <<EOF
(use-modules (shepherd service)
(shepherd support))
(define (append-line text)
(let ((port (open-file "$boot_log" "a")))
(display text port)
(newline port)
(close-port port)))
(register-services
(list
(service '(filesystems)
#:documentation "Prototype filesystem initialization target."
#:start (lambda _
(append-line "start:filesystems")
(call-with-output-file "$workdir/filesystems.ready"
(lambda (port) (display "ok" port)))
#t)
#:stop (lambda _
(append-line "stop:filesystems")
#f)
#:respawn? #f)
(service '(system-log)
#:documentation "Prototype logging target."
#:requirement '(filesystems)
#:start (lambda _
(append-line "start:system-log")
(call-with-output-file "$workdir/system-log.ready"
(lambda (port) (display "ok" port)))
#t)
#:stop (lambda _
(append-line "stop:system-log")
#f)
#:respawn? #f)
(service '(networking)
#:documentation "Prototype networking target."
#:requirement '(system-log)
#:start (lambda _
(append-line "start:networking")
(call-with-output-file "$workdir/networking.ready"
(lambda (port) (display "ok" port)))
#t)
#:stop (lambda _
(append-line "stop:networking")
#f)
#:respawn? #f)
(service '(login)
#:documentation "Prototype login target."
#:requirement '(networking)
#:start (lambda _
(append-line "start:login")
(call-with-output-file "$workdir/login.ready"
(lambda (port) (display "ok" port)))
#t)
#:stop (lambda _
(append-line "stop:login")
#f)
#:respawn? #f)))
(start-service (lookup-service 'login))
EOF
cat >"$rc_template" <<'EOF'
#!/bin/sh
# PROVIDE: __RC_NAME__
# REQUIRE: LOGIN
# KEYWORD: shutdown
. /etc/rc.subr
name=__RC_NAME__
rcvar="${name}_enable"
: ${__RC_ENABLE_VAR__:=YES}
pidfile=__PIDFILE__
socket=__SOCKET__
config=__CONFIG__
logfile=__LOGFILE__
command=__SHEPHERD_BIN__
start_cmd="__RC_START_FN__"
stop_cmd="__RC_STOP_FN__"
status_cmd="__RC_STATUS_FN__"
__RC_START_FN__()
{
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__ \
__SHEPHERD_BIN__ -I -s "$socket" -c "$config" --pid="$pidfile" -l "$logfile" > "__STDOUT__" 2>&1 &
for _try in 1 2 3 4 5 6 7 8 9 10; do
[ -f "$pidfile" ] && [ -S "$socket" ] && return 0
sleep 1
done
return 1
}
__RC_STOP_FN__()
{
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__ \
__HERD_BIN__ -s "$socket" stop root >/dev/null 2>&1 || true
for _try in 1 2 3 4 5 6 7 8 9 10; do
[ ! -f "$pidfile" ] && return 0
sleep 1
done
kill "$(cat "$pidfile")" >/dev/null 2>&1 || true
rm -f "$pidfile"
return 0
}
__RC_STATUS_FN__()
{
[ -f "$pidfile" ] && kill -0 "$(cat "$pidfile")" >/dev/null 2>&1
}
load_rc_config $name
run_rc_command "$1"
EOF
rc_start_fn=${rc_name}_start
rc_stop_fn=${rc_name}_stop
rc_status_fn=${rc_name}_status
rc_enable_var=${rc_name}_enable
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=$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
sed \
-e "s|__RC_NAME__|$rc_name|g" \
-e "s|__RC_ENABLE_VAR__|$rc_enable_var|g" \
-e "s|__RC_START_FN__|$rc_start_fn|g" \
-e "s|__RC_STOP_FN__|$rc_stop_fn|g" \
-e "s|__RC_STATUS_FN__|$rc_status_fn|g" \
-e "s|__PIDFILE__|$pid_file|g" \
-e "s|__SOCKET__|$socket_file|g" \
-e "s|__CONFIG__|$config_file|g" \
-e "s|__LOGFILE__|$shepherd_log|g" \
-e "s|__STDOUT__|$shepherd_stdout|g" \
-e "s|__SHEPHERD_BIN__|$shepherd_bin|g" \
-e "s|__HERD_BIN__|$herd_bin|g" \
-e "s|__LD_LIBRARY_PATH__|$ld_library_path|g" \
-e "s|__GUILE_LOAD_PATH__|$guile_load_path|g" \
-e "s|__GUILE_LOAD_COMPILED_PATH__|$guile_load_compiled_path|g" \
-e "s|__GUILE_EXTENSIONS_PATH__|$guile_extensions_path|g" \
"$rc_template" | run_root tee "$rc_script" >/dev/null
run_root chmod +x "$rc_script"
run_root service "$rc_name" onestart
for ready in filesystems.ready system-log.ready networking.ready login.ready; do
[ -f "$workdir/$ready" ] || {
echo "Expected boot marker missing: $workdir/$ready" >&2
exit 1
}
done
if run_root service "$rc_name" onestatus >/dev/null 2>&1; then
rc_status=running
else
rc_status=stopped
fi
start_sequence=$(paste -sd, "$boot_log")
expected_start_sequence=start:filesystems,start:system-log,start:networking,start:login
if [ "$start_sequence" != "$expected_start_sequence" ]; then
echo "Unexpected boot sequence: $start_sequence" >&2
exit 1
fi
run_root service "$rc_name" onestop
stop_sequence=$(tail -n 4 "$boot_log" | paste -sd, -)
expected_stop_sequence=stop:login,stop:networking,stop:system-log,stop:filesystems
if [ "$stop_sequence" != "$expected_stop_sequence" ]; then
echo "Unexpected shutdown sequence: $stop_sequence" >&2
exit 1
fi
if [ -f "$pid_file" ]; then
echo "PID file still present after stop: $pid_file" >&2
exit 1
fi
case $(cat "$shepherd_stdout") in
*"System lacks support for 'signalfd'; using fallback mechanism."*) signalfd_fallback=yes ;;
*) signalfd_fallback=no ;;
esac
cat >"$metadata_file" <<EOF
workdir=$workdir
rc_name=$rc_name
rc_script=$rc_script
config_file=$config_file
boot_log=$boot_log
pid_file=$pid_file
socket_file=$socket_file
shepherd_log=$shepherd_log
shepherd_stdout=$shepherd_stdout
rc_status=$rc_status
start_sequence=$start_sequence
expected_start_sequence=$expected_start_sequence
stop_sequence=$stop_sequence
expected_stop_sequence=$expected_stop_sequence
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-init-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"

View File

@@ -0,0 +1,355 @@
#!/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
guile_bindir=$(CDPATH= cd -- "$(dirname "$guile_bin")" && pwd)
guile_prefix=$(CDPATH= cd -- "$guile_bindir/.." && pwd)
guile_lib_dir=$guile_prefix/lib
guile_version=$(LD_LIBRARY_PATH="$guile_lib_dir${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" "$guile_bin" -c '(display (effective-version))')
extra_site_dir=$guile_extra_prefix/share/guile/site/$guile_version
extra_site_ccache_dir=$guile_extra_prefix/lib/guile/$guile_version/site-ccache
extra_extensions_dir=$guile_extra_prefix/lib/guile/$guile_version/extensions
run_root_env() {
if [ "$(id -u)" -eq 0 ]; then
env \
LD_LIBRARY_PATH="$guile_extra_prefix/lib:$guile_lib_dir:/usr/local/lib" \
GUILE_LOAD_PATH="$extra_site_dir" \
GUILE_LOAD_COMPILED_PATH="$extra_site_ccache_dir" \
GUILE_EXTENSIONS_PATH="$extra_extensions_dir" \
"$@"
else
sudo env \
LD_LIBRARY_PATH="$guile_extra_prefix/lib:$guile_lib_dir:/usr/local/lib" \
GUILE_LOAD_PATH="$extra_site_dir" \
GUILE_LOAD_COMPILED_PATH="$extra_site_ccache_dir" \
GUILE_EXTENSIONS_PATH="$extra_extensions_dir" \
"$@"
fi
}
cleanup=0
if [ -n "${WORKDIR:-}" ]; then
workdir=$WORKDIR
mkdir -p "$workdir"
else
workdir=$(mktemp -d /tmp/freebsd-shepherd-service.XXXXXX)
cleanup=1
fi
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
cleanup=0
fi
port=$((19000 + ($$ % 1000)))
state_dir=$workdir/state
config_file=$workdir/service-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-service-metadata.txt
logger_status=$workdir/logger.status
web_status=$workdir/web.status
monitor_status=$workdir/file-monitor.status
crashy_status=$workdir/crashy.status
service_command_log=$state_dir/service-events.log
http_response_file=$workdir/http-response.txt
cleanup_workdir() {
run_root_env "$herd_bin" -s "$socket_file" stop root >/dev/null 2>&1 || true
if [ -f "$pid_file" ]; then
if [ "$(id -u)" -eq 0 ]; then
kill "$(cat "$pid_file")" >/dev/null 2>&1 || true
else
sudo sh -c 'kill "$(cat "$1")" >/dev/null 2>&1 || true' sh "$pid_file"
fi
fi
if [ "$cleanup" -eq 1 ]; then
if [ "$(id -u)" -eq 0 ]; then
rm -rf "$workdir"
else
sudo rm -rf "$workdir"
fi
fi
}
trap cleanup_workdir EXIT INT TERM
mkdir -p "$state_dir"
chmod 0755 "$workdir"
chmod 0777 "$state_dir"
cat >"$workdir/logger.sh" <<'EOF'
#!/bin/sh
log=$1
ready=$2
uidfile=$3
printf 'logger-start uid=%s gid=%s\n' "$(id -u)" "$(id -g)" >> "$log"
id -u > "$uidfile"
touch "$ready"
trap 'echo logger-stop >> "$log"; exit 0' TERM INT
while :; do
echo heartbeat >> "$log"
sleep 1
done
EOF
chmod +x "$workdir/logger.sh"
cat >"$workdir/web.sh" <<'EOF'
#!/bin/sh
port=$1
ready=$2
log=$3
echo web-start >> "$log"
touch "$ready"
trap 'echo web-stop >> "$log"; exit 0' TERM INT
while :; do
printf 'HTTP/1.0 200 OK\r\nContent-Length: 20\r\n\r\nshepherd-freebsd-ok\n' | nc -l 127.0.0.1 "$port"
done
EOF
chmod +x "$workdir/web.sh"
cat >"$workdir/monitor.sh" <<'EOF'
#!/bin/sh
watch=$1
event=$2
ready=$3
log=$4
echo monitor-start >> "$log"
touch "$ready"
trap 'echo monitor-stop >> "$log"; exit 0' TERM INT
while :; do
if [ -f "$watch" ] && [ ! -f "$event" ]; then
echo detected > "$event"
echo monitor-detected >> "$log"
fi
sleep 1
done
EOF
chmod +x "$workdir/monitor.sh"
cat >"$workdir/crashy.sh" <<'EOF'
#!/bin/sh
counter=$1
ready=$2
log=$3
n=0
if [ -f "$counter" ]; then
n=$(cat "$counter")
fi
n=$((n + 1))
echo "$n" > "$counter"
echo "crashy-attempt-$n" >> "$log"
if [ "$n" -eq 1 ]; then
exit 1
fi
touch "$ready"
trap 'echo crashy-stop >> "$log"; exit 0' TERM INT
while :; do
sleep 10
done
EOF
chmod +x "$workdir/crashy.sh"
cat >"$config_file" <<EOF
(use-modules (shepherd service)
(shepherd support))
(register-services
(list
(service '(logger)
#:documentation "Write a heartbeat log as an unprivileged service."
#:start (make-forkexec-constructor (list "$workdir/logger.sh" "$service_command_log" "$state_dir/logger.ready" "$state_dir/logger.uid")
#:user "nobody" #:group "nobody"
#:directory "$state_dir"
#:log-file "$state_dir/logger.stdout")
#:stop (make-kill-destructor)
#:respawn? #f)
(service '(web)
#:documentation "Serve a tiny HTTP response over loopback."
#:requirement '(logger)
#:start (make-forkexec-constructor (list "$workdir/web.sh" "$port" "$state_dir/web.ready" "$service_command_log")
#:user "nobody" #:group "nobody"
#:directory "$state_dir"
#:log-file "$state_dir/web.stdout")
#:stop (make-kill-destructor)
#:respawn? #f)
(service '(file-monitor)
#:documentation "Watch for a flag file and record detection."
#:requirement '(web)
#:start (make-forkexec-constructor (list "$workdir/monitor.sh" "$state_dir/watch.flag" "$state_dir/event.flag" "$state_dir/monitor.ready" "$service_command_log")
#:user "nobody" #:group "nobody"
#:directory "$state_dir"
#:log-file "$state_dir/monitor.stdout")
#:stop (make-kill-destructor)
#:respawn? #f)
(service '(crashy)
#:documentation "Fail once and then stay running to validate respawn."
#:start (make-forkexec-constructor (list "$workdir/crashy.sh" "$state_dir/crashy.counter" "$state_dir/crashy.ready" "$service_command_log")
#:directory "$state_dir"
#:log-file "$state_dir/crashy.stdout")
#:stop (make-kill-destructor)
#:respawn? #t)))
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 did not become ready" >&2
exit 1
fi
run_root_env "$herd_bin" -s "$socket_file" start file-monitor >/dev/null
for ready in "$state_dir/logger.ready" "$state_dir/web.ready" "$state_dir/monitor.ready"; do
for _ in 1 2 3 4 5 6 7 8 9 10; do
[ -f "$ready" ] && break
sleep 1
done
[ -f "$ready" ] || {
echo "Expected readiness file missing: $ready" >&2
exit 1
}
done
run_root_env "$herd_bin" -s "$socket_file" status logger >"$logger_status"
run_root_env "$herd_bin" -s "$socket_file" status web >"$web_status"
run_root_env "$herd_bin" -s "$socket_file" status file-monitor >"$monitor_status"
fetch -T 5 -qo "$http_response_file" "http://127.0.0.1:$port/"
http_response=$(tr -d '\r' <"$http_response_file")
if [ "$http_response" != "shepherd-freebsd-ok" ]; then
echo "Unexpected HTTP response: $http_response" >&2
exit 1
fi
touch "$state_dir/watch.flag"
for _ in 1 2 3 4 5 6 7 8 9 10; do
[ -f "$state_dir/event.flag" ] && break
sleep 1
done
[ -f "$state_dir/event.flag" ] || {
echo "Monitor did not detect watched file" >&2
exit 1
}
run_root_env "$herd_bin" -s "$socket_file" start crashy >/dev/null || true
for _ in 1 2 3 4 5 6 7 8 9 10; do
[ -f "$state_dir/crashy.ready" ] && break
sleep 1
done
[ -f "$state_dir/crashy.ready" ] || {
echo "Respawn test service did not reach steady state" >&2
exit 1
}
run_root_env "$herd_bin" -s "$socket_file" status crashy >"$crashy_status"
logger_uid=$(cat "$state_dir/logger.uid")
crashy_counter=$(cat "$state_dir/crashy.counter")
if [ "$logger_uid" != "65534" ]; then
echo "Expected logger to run as nobody (65534), got $logger_uid" >&2
exit 1
fi
if [ "$crashy_counter" -lt 2 ]; then
echo "Expected respawn counter >= 2, got $crashy_counter" >&2
exit 1
fi
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
case $(cat "$logger_status") in
*"It is running"*) logger_running=yes ;;
*) logger_running=no ;;
esac
case $(cat "$web_status") in
*"It is running"*) web_running=yes ;;
*) web_running=no ;;
esac
case $(cat "$monitor_status") in
*"It is running"*) monitor_running=yes ;;
*) monitor_running=no ;;
esac
case $(cat "$crashy_status") in
*"It is running"*) crashy_running=yes ;;
*) crashy_running=no ;;
esac
case $(cat "$shepherd_stdout") in
*"System lacks support for 'signalfd'; using fallback mechanism."*) signalfd_fallback=yes ;;
*) signalfd_fallback=no ;;
esac
cat >"$metadata_file" <<EOF
workdir=$workdir
shepherd_prefix=$shepherd_prefix
guile_bin=$guile_bin
guile_extra_prefix=$guile_extra_prefix
port=$port
socket_file=$socket_file
pid_file=$pid_file
shepherd_log=$shepherd_log
shepherd_stdout=$shepherd_stdout
service_command_log=$service_command_log
logger_status_file=$logger_status
web_status_file=$web_status
monitor_status_file=$monitor_status
crashy_status_file=$crashy_status
logger_running=$logger_running
web_running=$web_running
monitor_running=$monitor_running
crashy_running=$crashy_running
logger_uid=$logger_uid
http_response=$http_response
monitor_detected=$(cat "$state_dir/event.flag")
crashy_counter=$crashy_counter
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-service-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"

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"