system: record explicit generation layout
This commit is contained in:
225
docs/GUIX_DIFFERENCES.md
Normal file
225
docs/GUIX_DIFFERENCES.md
Normal file
@@ -0,0 +1,225 @@
|
||||
# Fruix differences for Guix sysadmins
|
||||
|
||||
Date: 2026-04-04
|
||||
|
||||
This document is aimed at operators who already know Guix well and want a quick map of where Fruix behaves similarly and where it intentionally differs.
|
||||
|
||||
The short version is:
|
||||
|
||||
- Fruix is strongly **Guix-inspired**
|
||||
- it tries to preserve the important semantic properties
|
||||
- but it does **not** copy Guix mechanically where FreeBSD or Fruix-specific concerns make a different representation clearer
|
||||
|
||||
## Big picture
|
||||
|
||||
Fruix keeps the core Guix ideas you would expect:
|
||||
|
||||
- declarative inputs
|
||||
- content-addressed store paths
|
||||
- immutable build outputs
|
||||
- rollback-friendly deployment identity
|
||||
- provenance tied to the deployed closure rather than mutable in-place state
|
||||
|
||||
But Fruix differs in at least four major ways today:
|
||||
|
||||
1. it targets **FreeBSD**, not GNU/Linux
|
||||
2. its system frontend is currently smaller:
|
||||
- `fruix system build|rootfs|image|install|installer|installer-iso`
|
||||
3. it treats **FreeBSD source provenance** as an explicit deployment concern
|
||||
4. its installed-system generation model is still earlier-stage than Guix's mature system-generation workflow
|
||||
|
||||
## Similarities to Guix
|
||||
|
||||
If you know Guix System, these Fruix properties should feel familiar.
|
||||
|
||||
### Immutable deployment identity
|
||||
|
||||
A deployed Fruix system is identified primarily by its closure path in `/frx/store`, not by mutable files under `/etc` or `/usr/local`.
|
||||
|
||||
### `/run/current-system`
|
||||
|
||||
Fruix keeps the Guix-like runtime convention:
|
||||
|
||||
- `/run/current-system`
|
||||
|
||||
That path remains the active runtime boundary used by activation and service wiring.
|
||||
|
||||
### Rollback-friendly semantics
|
||||
|
||||
Fruix avoids in-place mutation of an older deployed closure.
|
||||
|
||||
The validated rollback story today is:
|
||||
|
||||
- keep the earlier declaration
|
||||
- rebuild or rematerialize it
|
||||
- boot or redeploy that earlier closure again
|
||||
|
||||
That is Guix-like in spirit even though Fruix does not yet expose the same installed-system rollback command surface.
|
||||
|
||||
### Generation-style metadata and roots
|
||||
|
||||
Fruix now records explicit installed-system generation state under:
|
||||
|
||||
- `/var/lib/fruix/system`
|
||||
|
||||
and explicit retention roots under:
|
||||
|
||||
- `/frx/var/fruix/gcroots`
|
||||
|
||||
This preserves the basic Guix idea that deployment state and reachability should be represented explicitly rather than inferred from whatever happens to be on disk.
|
||||
|
||||
## Important differences from Guix
|
||||
|
||||
## 1. Fruix does not mirror Guix's on-disk generation layout 1:1
|
||||
|
||||
This is intentional.
|
||||
|
||||
Guix heavily reuses its profile-generation model and represents a lot of meaning through symlink structure such as profile links and system generation links.
|
||||
|
||||
Fruix keeps the **semantics** but uses a more explicit metadata-oriented layout for installed systems.
|
||||
|
||||
Current Fruix layout:
|
||||
|
||||
```text
|
||||
/var/lib/fruix/system/
|
||||
current -> generations/1
|
||||
current-generation
|
||||
generations/
|
||||
1/
|
||||
closure -> /frx/store/...-fruix-system-...
|
||||
metadata.scm
|
||||
provenance.scm
|
||||
install.scm
|
||||
```
|
||||
|
||||
Why Fruix does this:
|
||||
|
||||
- it makes deployment state easier to inspect directly
|
||||
- it gives FreeBSD-specific install and provenance details a clearer home
|
||||
- it keeps room for richer operator tooling later without losing the Guix properties
|
||||
|
||||
So the rule of thumb is:
|
||||
|
||||
- **same semantics as Guix where practical**
|
||||
- **not necessarily the same directory names or symlink vocabulary**
|
||||
|
||||
## 2. Fruix currently keeps `/run/current-system` simple
|
||||
|
||||
Even though Fruix now records explicit generation metadata under `/var/lib/fruix/system`, it still keeps:
|
||||
|
||||
- `/run/current-system -> /frx/store/...`
|
||||
|
||||
rather than making `/run/current-system` point through a generation directory first.
|
||||
|
||||
This was chosen to preserve compatibility with the already-validated activation and runtime model while adding explicit generation metadata separately.
|
||||
|
||||
## 3. Fruix treats source provenance more explicitly
|
||||
|
||||
Guix sysadmins are used to derivation/store provenance. Fruix adds an extra emphasis because its current FreeBSD story depends on explicit source selection and materialization.
|
||||
|
||||
Fruix routinely records:
|
||||
|
||||
- declared FreeBSD source object metadata
|
||||
- materialized source store paths
|
||||
- source materialization metadata files
|
||||
- closure-level store layout metadata
|
||||
- install metadata on the target system
|
||||
|
||||
This is more prominent in Fruix than most Guix system docs because FreeBSD base/source identity has been an active design concern for this project.
|
||||
|
||||
## 4. Fruix has installer-media workflows as first-class system actions
|
||||
|
||||
Guix has installation media and image workflows, but Fruix's current system frontend makes these especially explicit because they are still part of the active architectural bring-up story.
|
||||
|
||||
Current Fruix actions include:
|
||||
|
||||
- `fruix system install`
|
||||
- `fruix system installer`
|
||||
- `fruix system installer-iso`
|
||||
|
||||
That is a little more deployment-medium-oriented than the mental model many Guix users start with.
|
||||
|
||||
## 5. Device naming is more environment-sensitive than Guix users may expect
|
||||
|
||||
Because Fruix is on FreeBSD, the install target device naming is not the same as on Linux.
|
||||
|
||||
Validated examples:
|
||||
|
||||
- installer disk-image path under QEMU:
|
||||
- `/dev/vtbd1`
|
||||
- installer ISO path under QEMU:
|
||||
- `/dev/vtbd0`
|
||||
- installer ISO path under XCP-ng:
|
||||
- `/dev/ada0`
|
||||
|
||||
So compared with Guix-on-Linux intuition, Fruix operators should be more explicit about target-device selection during installation and installer-media validation.
|
||||
|
||||
## 6. Fruix does not yet have Guix-equivalent installed-system generation commands
|
||||
|
||||
This is the biggest current operational gap.
|
||||
|
||||
Fruix does **not** yet provide a mature equivalent of the familiar Guix System operator flow around in-place generation switching and rollback commands.
|
||||
|
||||
Today, Fruix rollback is mostly:
|
||||
|
||||
- declaration-driven
|
||||
- rebuild/redeploy based
|
||||
|
||||
rather than:
|
||||
|
||||
- switch current system generation in place through a dedicated command
|
||||
|
||||
So if you come from Guix, assume that Fruix currently has:
|
||||
|
||||
- strong closure/store semantics
|
||||
- explicit install artifacts
|
||||
- explicit generation metadata roots
|
||||
- but a less mature installed-system generation UX
|
||||
|
||||
## Where Fruix is intentionally trying to improve on Guix's representation
|
||||
|
||||
Fruix is not trying to improve on Guix's core semantics. Guix already got those right.
|
||||
|
||||
Where Fruix is intentionally experimenting is mostly the **representation layer**:
|
||||
|
||||
- make generation state more legible to operators
|
||||
- make provenance more visible without needing to reconstruct it mentally from symlink layout alone
|
||||
- separate:
|
||||
- runtime entry point (`/run/current-system`)
|
||||
- installed deployment state (`/var/lib/fruix/system`)
|
||||
- retention roots (`/frx/var/fruix/gcroots`)
|
||||
|
||||
That is why Fruix currently prefers a small per-generation metadata directory instead of only a bare generation link.
|
||||
|
||||
## Practical operator advice for Guix users
|
||||
|
||||
If you are already comfortable with Guix, the safest Fruix mental model today is:
|
||||
|
||||
1. think in terms of immutable closures and declarations first
|
||||
2. use `fruix system build` as the canonical starting point
|
||||
3. treat image/install/installer/installer-iso as deployment materializers built from the same declaration
|
||||
4. identify a deployment by:
|
||||
- closure path
|
||||
- source provenance metadata
|
||||
- install metadata
|
||||
5. think of rollback today as:
|
||||
- “redeploy the earlier declaration again”
|
||||
rather than:
|
||||
- “switch to an already-managed previous generation in place”
|
||||
|
||||
## Status summary
|
||||
|
||||
Today Fruix is closest to Guix in:
|
||||
|
||||
- store and closure semantics
|
||||
- declarative deployment identity
|
||||
- rollback-friendly immutability
|
||||
- `/run/current-system` runtime model
|
||||
|
||||
It differs most from Guix in:
|
||||
|
||||
- FreeBSD platform details
|
||||
- source-provenance emphasis
|
||||
- installer-medium-oriented workflows
|
||||
- generation-layout representation
|
||||
- and the still-maturing installed-system generation command surface
|
||||
@@ -26,6 +26,10 @@ Fruix currently has:
|
||||
- `fruix system installer`
|
||||
- a bootable Fruix-managed installer ISO:
|
||||
- `fruix system installer-iso`
|
||||
- an explicit installed-system generation layout under:
|
||||
- `/var/lib/fruix/system`
|
||||
- explicit installed-system retention roots under:
|
||||
- `/frx/var/fruix/gcroots`
|
||||
|
||||
Validated boot modes still are:
|
||||
|
||||
@@ -38,42 +42,38 @@ The validated Phase 18 installation work currently uses:
|
||||
|
||||
## Latest completed achievement
|
||||
|
||||
### 2026-04-04 — Phase 19.1 completed
|
||||
### 2026-04-04 — Phase 19.2 completed
|
||||
|
||||
Fruix now has a documented canonical deployment workflow for build, image generation, installation, roll-forward, and rollback.
|
||||
Fruix now records an explicit installed-system generation layout and retention-root model instead of relying mainly on harness knowledge.
|
||||
|
||||
Highlights:
|
||||
|
||||
- added canonical operator workflow documentation:
|
||||
- `docs/system-deployment-workflow.md`
|
||||
- added Phase 19.1 report:
|
||||
- `docs/reports/phase19-deployment-workflow-freebsd.md`
|
||||
- the documented command surface now explicitly centers on:
|
||||
- `fruix system build`
|
||||
- `fruix system rootfs`
|
||||
- `fruix system image`
|
||||
- `fruix system install`
|
||||
- `fruix system installer`
|
||||
- `fruix system installer-iso`
|
||||
- the deployment story now explicitly records:
|
||||
- declaration-driven roll-forward
|
||||
- rollback by rebuilding/redeploying an earlier declaration
|
||||
- current deployment identity via closure path and emitted provenance metadata
|
||||
- current environment-specific installer target-device conventions
|
||||
- added explicit installed-system generation layout under:
|
||||
- `/var/lib/fruix/system`
|
||||
- added explicit installed-system retention roots under:
|
||||
- `/frx/var/fruix/gcroots`
|
||||
- installed targets now record a first-generation deployment directory containing:
|
||||
- `closure`
|
||||
- `metadata.scm`
|
||||
- `provenance.scm`
|
||||
- `install.scm`
|
||||
- `/run/current-system` remains the runtime boundary and still points directly at the active closure path
|
||||
- added Guix-oriented operator notes in:
|
||||
- `docs/GUIX_DIFFERENCES.md`
|
||||
- updated deployment workflow documentation to reflect the new explicit generation model
|
||||
|
||||
Validation basis referenced by the workflow documentation:
|
||||
Validation:
|
||||
|
||||
- `PASS phase15-base-rollback-qemu`
|
||||
- `PASS phase15-base-rollback-xcpng`
|
||||
- `PASS phase18-system-install`
|
||||
- `PASS phase18-installer-environment`
|
||||
- `PASS phase18-installer-iso`
|
||||
- `PASS phase18-installer-iso-xcpng`
|
||||
- `PASS phase19-generation-layout-qemu`
|
||||
- regression re-check:
|
||||
- `PASS phase18-installer-iso`
|
||||
|
||||
Reports:
|
||||
|
||||
- `docs/system-deployment-workflow.md`
|
||||
- `docs/GUIX_DIFFERENCES.md`
|
||||
- `docs/reports/phase19-deployment-workflow-freebsd.md`
|
||||
- `docs/reports/phase19-generation-layout-freebsd.md`
|
||||
|
||||
## Recent major milestones
|
||||
|
||||
@@ -99,6 +99,6 @@ Reports:
|
||||
|
||||
Per `docs/PLAN_4.md`, the next planned step is:
|
||||
|
||||
- **Phase 19.2** — make the installed-system generation and rollback-root model more explicit
|
||||
- **Phase 19.3** — validate installed-system rollback through the intended operator-facing workflow
|
||||
|
||||
Phase 19.1 is now complete: Fruix documents a coherent operator-facing deployment workflow for build, installation, roll-forward, and rollback.
|
||||
Phase 19.2 is now complete: Fruix has an explicit installed-system generation layout and retention-root model on FreeBSD.
|
||||
|
||||
192
docs/reports/phase19-generation-layout-freebsd.md
Normal file
192
docs/reports/phase19-generation-layout-freebsd.md
Normal file
@@ -0,0 +1,192 @@
|
||||
# Phase 19.2: explicit installed-system generation layout on FreeBSD
|
||||
|
||||
Date: 2026-04-04
|
||||
|
||||
## Goal
|
||||
|
||||
Phase 19.2 is about making Fruix's installed-system generation model more explicit.
|
||||
|
||||
The target here is not yet a full Guix-equivalent in-place `switch-generation` workflow.
|
||||
|
||||
The immediate goal is to stop relying mainly on harness knowledge and implicit symlink expectations by recording installed deployment state more explicitly on disk.
|
||||
|
||||
## Decision
|
||||
|
||||
Fruix now follows this design direction:
|
||||
|
||||
- keep Guix-like **semantics**
|
||||
- do not mirror Guix's installed-system/profile layout **mechanically 1:1**
|
||||
|
||||
What Fruix preserves from Guix:
|
||||
|
||||
- immutable closure identity
|
||||
- rollback-friendly deployment semantics
|
||||
- explicit current deployment pointer
|
||||
- GC-root-style retention links
|
||||
- `/run/current-system` as the active runtime boundary
|
||||
|
||||
What Fruix intentionally changes:
|
||||
|
||||
- installed-system generation state is represented as a small metadata-bearing directory
|
||||
- the generation model is recorded under a Fruix-native path
|
||||
- deployment metadata and provenance are easier to inspect directly without reconstructing intent from symlink layout alone
|
||||
|
||||
## Implemented layout
|
||||
|
||||
Installed systems now record an explicit generation root under:
|
||||
|
||||
- `/var/lib/fruix/system`
|
||||
|
||||
Current validated initial layout:
|
||||
|
||||
```text
|
||||
/var/lib/fruix/system/
|
||||
current -> generations/1
|
||||
current-generation
|
||||
generations/
|
||||
1/
|
||||
closure -> /frx/store/...-fruix-system-...
|
||||
metadata.scm
|
||||
provenance.scm
|
||||
install.scm
|
||||
```
|
||||
|
||||
Installed systems now also create explicit retention roots under:
|
||||
|
||||
- `/frx/var/fruix/gcroots`
|
||||
|
||||
Current validated initial layout:
|
||||
|
||||
```text
|
||||
/frx/var/fruix/gcroots/
|
||||
current-system -> /frx/store/...-fruix-system-...
|
||||
system-1 -> /frx/store/...-fruix-system-...
|
||||
```
|
||||
|
||||
Important compatibility point:
|
||||
|
||||
- `/run/current-system` still points directly at the active closure in `/frx/store`
|
||||
|
||||
That means the new explicit generation model strengthens deployment metadata without changing the already-validated runtime contract used by activation, rc.d integration, and service startup.
|
||||
|
||||
## Code changes
|
||||
|
||||
### `modules/fruix/system/freebsd/media.scm`
|
||||
|
||||
Added explicit generation-layout helpers:
|
||||
|
||||
- generation metadata object writer
|
||||
- generation provenance object writer
|
||||
- generation layout population for staged rootfs trees
|
||||
|
||||
The system rootfs staging path now creates explicit generation state during rootfs population.
|
||||
|
||||
That affects:
|
||||
|
||||
- direct rootfs materialization
|
||||
- direct image materialization
|
||||
- direct installation targets
|
||||
- target rootfs payloads staged inside installer images
|
||||
- target rootfs payloads staged inside installer ISOs
|
||||
|
||||
The direct install path now also refreshes the generation layout after writing:
|
||||
|
||||
- `/var/lib/fruix/install.scm`
|
||||
|
||||
so the generation directory carries the same install metadata.
|
||||
|
||||
### Installer runtime path
|
||||
|
||||
The generated installer runtime script now also copies install metadata into:
|
||||
|
||||
- `/var/lib/fruix/system/generations/1/install.scm`
|
||||
|
||||
on the installed target.
|
||||
|
||||
This keeps direct-install and installer-mediated installs aligned.
|
||||
|
||||
## New validation harness
|
||||
|
||||
Added:
|
||||
|
||||
- `tests/system/run-phase19-generation-layout-qemu.sh`
|
||||
|
||||
This harness builds on the already-passing direct install validation from Phase 18.1 and then verifies the new explicit generation layout on the installed target image.
|
||||
|
||||
Passing validation:
|
||||
|
||||
- `PASS phase19-generation-layout-qemu`
|
||||
|
||||
Validated result summary:
|
||||
|
||||
```text
|
||||
closure_path=/frx/store/882fb4a9fbb05f08e77de29f70ca50f3c01dd29141e72688d32770a3172747e7-fruix-system-fruix-freebsd
|
||||
current_generation=1
|
||||
current_link=generations/1
|
||||
generation_closure=/frx/store/882fb4a9fbb05f08e77de29f70ca50f3c01dd29141e72688d32770a3172747e7-fruix-system-fruix-freebsd
|
||||
gcroot_current=/frx/store/882fb4a9fbb05f08e77de29f70ca50f3c01dd29141e72688d32770a3172747e7-fruix-system-fruix-freebsd
|
||||
gcroot_generation=/frx/store/882fb4a9fbb05f08e77de29f70ca50f3c01dd29141e72688d32770a3172747e7-fruix-system-fruix-freebsd
|
||||
run_current_system_target=/frx/store/882fb4a9fbb05f08e77de29f70ca50f3c01dd29141e72688d32770a3172747e7-fruix-system-fruix-freebsd
|
||||
generation_layout=explicit
|
||||
generation_layout_validation=ok
|
||||
```
|
||||
|
||||
The harness verified all of the following:
|
||||
|
||||
1. the installed target contains:
|
||||
- `/var/lib/fruix/system`
|
||||
2. the current generation pointer exists and resolves to:
|
||||
- `generations/1`
|
||||
3. the generation directory contains:
|
||||
- `closure`
|
||||
- `metadata.scm`
|
||||
- `provenance.scm`
|
||||
- `install.scm`
|
||||
4. the generation closure link points at the installed closure in `/frx/store`
|
||||
5. the generation metadata records:
|
||||
- closure path
|
||||
- install metadata path
|
||||
6. the generation install metadata records:
|
||||
- closure path
|
||||
- materialized source provenance
|
||||
7. explicit retention roots exist under:
|
||||
- `/frx/var/fruix/gcroots/current-system`
|
||||
- `/frx/var/fruix/gcroots/system-1`
|
||||
8. those GC-root-style links point at the same active closure
|
||||
9. `/run/current-system` still points directly at the active closure path
|
||||
10. existing install boot validation remains intact
|
||||
|
||||
## Relationship to Guix
|
||||
|
||||
Fruix now has an explicit installed-system generation model, but it is still intentionally not a byte-for-byte clone of Guix's on-disk conventions.
|
||||
|
||||
The design choice is:
|
||||
|
||||
- preserve Guix's deployment semantics
|
||||
- use a Fruix-native metadata-oriented representation where that improves clarity for operators and debugging
|
||||
|
||||
That decision is documented separately in:
|
||||
|
||||
- `docs/GUIX_DIFFERENCES.md`
|
||||
|
||||
## Current limitations
|
||||
|
||||
This phase does **not** yet add:
|
||||
|
||||
- multi-generation switching in place
|
||||
- a `switch`/`reconfigure` command
|
||||
- an operator-facing rollback command that flips from current to a previous installed generation without redeploy
|
||||
- explicit `rollback` link management beyond the initial current-generation layout
|
||||
|
||||
Those belong to later Phase 19 work.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Phase 19.2 is complete.
|
||||
|
||||
Fruix now has a clearer, explicit installed-system generation and retention-root model on FreeBSD:
|
||||
|
||||
- generation metadata is recorded under `/var/lib/fruix/system`
|
||||
- retention links are recorded under `/frx/var/fruix/gcroots`
|
||||
- `/run/current-system` remains stable as the runtime boundary
|
||||
- and the model is documented in Fruix-native terms for Guix-familiar operators
|
||||
@@ -255,6 +255,43 @@ Therefore the canonical workflow is:
|
||||
|
||||
Do not assume that a device name validated in one harness is portable to another.
|
||||
|
||||
## Installed-system generation layout
|
||||
|
||||
Installed Fruix systems now record an explicit first-generation deployment layout under:
|
||||
|
||||
- `/var/lib/fruix/system`
|
||||
|
||||
Current validated shape:
|
||||
|
||||
```text
|
||||
/var/lib/fruix/system/
|
||||
current -> generations/1
|
||||
current-generation
|
||||
generations/
|
||||
1/
|
||||
closure -> /frx/store/...-fruix-system-...
|
||||
metadata.scm
|
||||
provenance.scm
|
||||
install.scm # present on installed targets
|
||||
```
|
||||
|
||||
Installed systems also now create explicit GC-root-style deployment links under:
|
||||
|
||||
- `/frx/var/fruix/gcroots`
|
||||
|
||||
Current validated shape:
|
||||
|
||||
```text
|
||||
/frx/var/fruix/gcroots/
|
||||
current-system -> /frx/store/...-fruix-system-...
|
||||
system-1 -> /frx/store/...-fruix-system-...
|
||||
```
|
||||
|
||||
Important detail:
|
||||
|
||||
- `/run/current-system` still points directly at the active closure path in `/frx/store`
|
||||
- the explicit generation layout therefore adds deployment metadata and retention roots without changing the already-validated runtime contract used by activation, rc.d wiring, and tests
|
||||
|
||||
## Roll-forward workflow
|
||||
|
||||
The current Fruix roll-forward model is declaration-driven.
|
||||
@@ -310,13 +347,14 @@ Current rollback is:
|
||||
|
||||
- **redeploy the earlier declaration again**
|
||||
|
||||
Future Phase 19 work is expected to make these more explicit:
|
||||
What still remains for later Phase 19 work is making rollback itself operator-driven at the installed-system layer, rather than only declaration/redeploy driven.
|
||||
|
||||
- current generation
|
||||
- previous generation
|
||||
- rollback target
|
||||
- installed-system roots and generation links
|
||||
Still pending:
|
||||
|
||||
- previous-generation tracking beyond the initial explicit generation-1 layout
|
||||
- an explicit rollback target link distinct from `current`
|
||||
- an operator-facing installed-system rollback workflow
|
||||
- generation switching without full redeploy
|
||||
|
||||
## Provenance and deployment identity
|
||||
|
||||
@@ -341,12 +379,12 @@ The deployment workflow is now coherent, but it is not yet the final generation-
|
||||
|
||||
Not yet first-class:
|
||||
|
||||
- installed-system generation directories and symlink model
|
||||
- a dedicated `switch` or `reconfigure` command
|
||||
- an installed-system rollback command that moves between generations in place
|
||||
- explicit GC-root management for installed systems
|
||||
- multi-generation retention and previous-generation tracking beyond generation 1
|
||||
- generation switching policy independent of full redeploy
|
||||
|
||||
Those are the next logical steps after Phase 19.1.
|
||||
Those are the next logical steps after the current explicit-generation layout.
|
||||
|
||||
## Summary
|
||||
|
||||
@@ -359,4 +397,4 @@ The current canonical Fruix deployment model is:
|
||||
- **identify deployments by closure path and provenance metadata**
|
||||
- **roll back by rebuilding/redeploying the earlier declaration**, not by mutating the current closure in place
|
||||
|
||||
That is the operator-facing workflow Fruix should document and use until explicit installed-system generation management is added.
|
||||
That is the operator-facing workflow Fruix should document and use while installed-system generation switching remains more limited than Guix's mature in-place system-generation workflow.
|
||||
|
||||
Reference in New Issue
Block a user