Split top-level entrypoint into per-directory entrypoints #1182
No reviewers
fediversity/developers
Labels
No labels
0 points
0.5 points
1 point
13 points
2 points
21 points
3 points
34 points
5 points
55 points
8 points
ambition
application-offering
ambition
configure-applications
ambition
front-end
ambition/install-applications
ambition
security
ambition
switch-host
ambition
update-applications
ambition
user-management
api service
blocked
component: fediversity panel
component: nixops4
documentation
estimation high: >3d
estimation low: <2h
estimation mid: <8h
infinite points
productisation
project-management
question
role: application developer
role: application operator
role: hosting provider
role: maintainer
security
technical debt
testing
type unclear
type: bug
type: deliverable
type: key result
type: objective
type: task
type: user story
user experience
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
fediversity/fediversity!1182
Loading…
Reference in a new issue
No description provided.
Delete branch "kiara/fediversity:separate-entrypoints"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Splits the monolithic root
flake.nix+default.nixinto per-directory entrypoints so CI and consumers address each component directly rather than routing everything through the root.What moves where
src/gets the orchestration surface:nixosConfigurations, the<group>-<effect>-<machine>deployment packages/apps/devShells,legacyPackages,core-docs, thesrc-scopedformatter, and the nix/infradefaultdev shell. Addressed as./src#....api/,panel/,nix-module-form/each own theirpackages,checks,formatteranddevShells, including that leaf's owntreefmtcheck. Addressed as./api#..., etc.Why the root cannot be removed
Two concerns force a repo-root flake to survive (documented in
README.mdand inline indefault.nix):boundary. ItsdeploymentPlanresolvescallerviaimport (repoRoot + "/${caller}") args, andcallerstrings span both thesrc/tree and thechecks/tree, so./.must remain the repo root.test-*-service,contracts-*,deployment-*,apps-*,integrations-*,resources-*,nix-unit-*), which crosssrc/,checks/,nix/contractsand multiple leaves. They resist a clean per-leaf split for now, so the root retains them as itschecksoutput.The root also owns the one repo-wide concern that must scan the whole tree: the pre-commit hook. Per-leaf
treefmtchecks cover each leaf's own languages; the repo-wide hook is the backstop.Flake-mode / npins caveat
Each entrypoint reads its deps via
sources ? import ../npins, reaching the singlenpins/at the repo root. In flake mode Nix copies only each flake's own subtree, so../npinsresolves only becausenpins/is git-tracked at the root. New leafflake.nix/flake.lockmust be git-added before a flake-mode eval sees them. The CI generator enumerates subdir flakes via explicitgit+file://<repo>?dir=<sub>URLs, since a bare subdir path would copy only that subtree and lose../npins.Deviations from the plan
flake.nix(a subdir flake reports the subdir'snarHash, wrong for whole-repofetchTreeverification). The split deploy path reads it as.#selfModuleSourceand threads it via theSELF_MODULE_SOURCEenv var.tystays repo-wide in the root pre-commit hook (itsty.tomloverrides are inherently cross-tree); per-leaftreefmtconfigs scope the other formatters to each leaf's languages.Verification
src,panelandnix-module-formoutputs;api-http-stack's only divergence is the inherentdeployment_rootsource-root path, self-consistent within CI's flake mode.boundaryandcore-docsevaluate correctly.checkidempotency gate passes (writeandcheckboth exit 0); all workflow YAMLs parse.Heavy check closures (
apps-*, fullcore-docsrender,flake-show) are left for a real CI run on this branch.Disclaimer: I used a coding agent in the creation of this patch.
Five CI failures on the entrypoint split: - `deployment-{ssh,tf,tf-incus}-hosts` (runtime): the deployment's generated `configuration.nix` re-imports the operator root as `(import operatorRoot { operatorPin = null; }).boundary.<system>` (`src/effects/common/write-config.nix:146`, unchanged per the split plan). The root reshape dropped the `operatorPin` parameter along with the now-unused `versionSide`/`chosenLayers` machinery, so that runtime import failed with `function 'entrypoint' called with unexpected argument 'operatorPin'`. Eval-only parity checks never exercised this path, so it surfaced only when the deployer actually built the target config. Re-accept `operatorPin ? null` (ignored -- the thin root only ever exposes the provider-side `operatorPin == null` layers) to honor the call shape, with a `# deadnix: skip` so the intentionally-unused formal survives the hook. - `override`/`overrideDerivation`: the root `checks` output is built with `callPackage ./checks`, which attaches `override`/`overrideDerivation` passthru functions to the returned attrset. Those leaked into `checks.<system>` `attrNames`, so the generator emitted bogus `checks-override`/`checks-overrideDerivation` workflows that failed with `expected ... to be a derivation ... but found a function`. Restore the `deOverride` helper (dropped in the reshape) and apply it to the `callPackage ./checks` result. Delete the three orphaned workflow YAMLs (`checks-override`, `checks-overrideDerivation`, `checks-treefmt`); the generator no longer emits them and the idempotency gate confirms it. - `core-docs`: the flake-attributes enumeration imported the leaf entrypoints as `import ../../api { }` (etc.) with no `system`, so in pure flake eval they hit `builtins.currentSystem` and failed with `attribute 'currentSystem' missing`. Thread `system`/`sources` through explicitly (the leaves, unlike `src`/root, do not guard `currentSystem` with `or null`). - `pre-commit`: the committed tree was not treefmt/deadnix-clean (the repo-wide hook also runs deadnix, which the earlier per-file `nixfmt` pass skipped). deadnix removed the now-unused `self` param from the root `default.nix` entrypoint and the leaf flake `outputs` functions; `flake.nix` correspondingly stops passing `inherit self` (it only needs `self` locally to derive `selfModuleSource`). Reword the `README.md` list item whose code spans were split mid-phrase so treefmt's markdown reflow keeps them intact. Verified on fediversity-ci in flake mode: `override`/`treefmt` absent from check names; `core-docs`/`pre-commit` evaluate; `import root { operatorPin = null; }` resolves the boundary; the deployment closure differs from `main` only by source-hash rehashing (script logic byte-identical via nix-diff); the generator `check` gate stays green. Assisted-by: Claude:claude-opus-4-8@ -37,2 +37,3 @@- name: Deployrun: nix-shell --run 'eval "$(ssh-agent -s)" && ssh-add ~/.ssh/id_ed25519 && SHELL=$(which bash) nix --extra-experimental-features "nix-command flakes" run ".#dev-ssh-forgejo-runner"'run: |export SELF_MODULE_SOURCE=$(nix --extra-experimental-features "nix-command flakes" eval --json ".#selfModuleSource")why do CD jobs need
SELF_MODULE_SOURCE?@ -0,0 +14,4 @@# `git rev-parse --show-toplevel`, which is worktree-aware (a git worktree's# `.git` is a file, not a directory, so the default `.git/config` anchor# resolves to the wrong root). See the root `treefmt.nix` for the full story.projectRootFile = null;leaves' treefmt seem to lack
ty(then why did our root have it? some should need it then, right?), while multiple leaves also still lack nix stuff in treefmt (while all have nix)@ -3,8 +3,7 @@outputs ={ self, ... }:import ./default.nix {inherit self;did we not need this to propagate the repo root's nar hash?
this seems ditched in favor of the more granular
selfModuleSource, which is quite possibly for the better@ -0,0 +20,4 @@steps:- uses: actions/checkout@v4- run: |nix --extra-experimental-features 'nix-command flakes' build ".#checks.x86_64-linux.pre-commit" -Lwhy not leave formatting checks to the leaves'
treefmt?@ -17,3 +17,2 @@jobs:flake-show:runs-on: native-16gflake-show-root:just split these into their own jobs rather than having one overarching yaml for them
@ -60,0 +86,4 @@# `operatorPin == null` path), so there is no pin to resolve here; the# parameter exists solely to honor the call shape.# deadnix: skipoperatorPin ? null,should that import now point at the other entrypoint then? or is the parameter just no longer relevant somehow?
looks like something we could further consider addressing if we were to split up our mono-repo:
What that makes operatorPin (the actual subject of 16751)
Since the import stays at the root, the root must accept whatever args that call passes — including operatorPin = null. The reviewer's either/or resolves as:
unexpected argument 'operatorPin'. Hence the # deadnix: skip and the "exists solely to honor the call shape" note.
So: the imports don't switch entrypoints because .boundary is root-only and root-anchored — and that's exactly why operatorPin survives as an accepted-but-ignored param. Both halves of 16751 are answered by the inline comment block already; nothing to change in code.
Let me actually enumerate the import sites and see which point where.
The imports point at two different entrypoints because they're asking for two different things, and each thing lives in exactly one place:
Root default.nix (.# / import ../..) — for what only the root has
src/default.nix (./src# / import ./src / import ../.) — for what src owns
So why not "always root"?
Because the root deliberately no longer owns those src things — that's the entire point of the split. If you made the deploy family and nixosConfigurations resolve through the root again, you'd have un-done the reshape and put the monolith back. The two-entrypoint split is intended:
The imports aren't switching arbitrarily — each import site names the entrypoint that owns the attribute it wants. Boundary/infra-checks → root; configs/deploys/packages → src.
The catch — and it ties back to 16767
There's one genuinely awkward case: src/docs/default.nix imports both — srcFlake = import ../. and rootFlake = import ../.. (lines 305-306) — to build a single "Flake attributes" page. That's the docs page reaching sideways to root for the infra checks while reaching into src for packages. And srcFlake.packages is only "complete" because src re-aggregates the leaves — the 16767 layer inversion.
So the honest answer to "why not always root": the split's goal shape is "each thing addressed at its owner." Root owns boundary + infra checks (blocked from moving by #972 + the not-yet-split infra checks); src owns configs/deploys/packages. The places that still touch both (docs enumeration) are exactly the residue 16767 flags — the incomplete trim. It's not that the import "should always be root"; it's that we're mid-way between one entrypoint and clean per-leaf entrypoints, and the root retains only what's provably stuck there.
Does that reconcile it? The operatorPin-bearing import (16751) is firmly root — it can never be src, because .boundary is there. The other imports point at src because root gave those outputs away on purpose.
@ -0,0 +10,4 @@systems = [ "x86_64-linux" ];# Each `leaf.<key>.<system>` is already keyed by that one system, so a# plain attribute merge across systems suffices (distinct keys).perSystem =do flakes like this one even need to be explicit about the flake attributes like
packagesif they can just do theimport ./. { inherit system; }? (presumesdefault.nixotherwise follows a similar schema as the flake's system attributes)@ -322,20 +350,22 @@ letflakeGroups = [the flakes should probably be documented in their own (separate) docs, for in as far as we can meaningfully separate them
Fold four review threads on the split entrypoints: - CI formatting (16746): mirror `main`'s model -- the whole-repo `ty` type check and non-leaf files stay in the root whole-tree `treefmt` CHECK; each leaf (`src`/`api`/`panel`/`nix-module-form`) gets its own `treefmt` check scoped to its languages. Drop `pre-commit` as a CI check (it stays a commit-time git-hook for trailing-whitespace, which has no treefmt program). - flake-show (16747): split the single `flake-show.yaml` into one workflow file per leaf (`flake-show-{root,src,api,panel,nix-module-form}.yaml`). - leaf output allowlists (16749): leaf `default.nix` group their flake-schema outputs under one `flake` attr; `flake.nix` splats it via `recursiveUpdate`, so there is no hand-kept output allowlist. `nix flake {show,check}` rejects unknown top-level outputs, so the flat compat attrs stay OUT of `.flake`. - per-leaf docs (16750): each leaf renders its own flake-attributes page from its own attr names (shared `nix/docs.nix` machinery); `core-docs` documents only `src` + the root. Also guard the leaf `flake.nix` `lib` bootstrap: `default.nix`'s `system ? builtins.currentSystem` is unavailable in flake (pure) eval, so the argless `import ./. { }` is replaced with an explicit first-supported-system. Assisted-by: Claude:claude-opus-4-8SELF_MODULE_SOURCE@ -310,1 +363,4 @@# for introspection; it is baked directly into the re-exported deployment# attributes above (via `srcFor`), so consumers deploy `.#<group>-...` and# need not read it out-of-band. A flat compat attr, not a flake output.inherit selfModuleSource;let's stick to the flakes schema to retain feature parity
@ -27,2 +25,2 @@checks = perSystem "checks";};lib.foldl' (acc: system: lib.recursiveUpdate acc (import ./. { inherit system; }).flakeany feature parity for
flake.nix/default.nixpairs like this would seem entirely coincidental (given thedefault.nixcould just expose a bunch of other stuff not going to the flake) - consider instead an approach more like https://codeberg.org/kiara/poc-override-nix-deps/src/branch/main/flake.nix (sansinputs)i just realized our
default.nixs re-export their params, which should not conform to the flakes schema - not sure that works (or doesn't) for our root-flakeflake showright nowflake-show-roottonative-16gdd919f42069f3efed124dc79236262dc79236262a2dd5883892ffb7ec28403c7b68d7103c7b68d716fe3bae382@ -0,0 +464,4 @@openapi-client;api = (callPackage ../api { inherit sources selfModuleSource; }).packages;nix-module-form = (callPackage ../nix-module-form { inherit sources; }).packages;src/importing/exposing the other leaves feels like a layer violation, given those are built on top of this one - could those not just expose their stuff by themselves, rather than us still essentially failing to trim down from having some single over-arching entry-point?this seems to be because they're actually in the setup on behalf of group
hosting-provider, which we can address later by splittingsrc/further up, see #527WIP: Split top-level entrypoint into per-directory entrypointsto Split top-level entrypoint into per-directory entrypoints