Something went wrong. Try again.
the cms for static site generators on atproto
Something went wrong. Try again.
lichen.page flake.nix
28 kB · 490 lines
Nix
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491{ description = "r-lichen dev shell";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; inputs.fenix.url = "github:nix-community/fenix"; inputs.fenix.inputs.nixpkgs.follows = "nixpkgs"; # TODO: switch to pkgs.opencode once the nixpkgs package is fixed inputs.opencode-nix.url = "git+https://codeberg.org/notplants/opencode-nix.git"; inputs.opencode-nix.inputs.nixpkgs.follows = "nixpkgs";
outputs = { self, nixpkgs, fenix, opencode-nix }: let systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system);
# the one rust toolchain for the whole repo: fenix's latest stable, matching # rust-toolchain.toml (channel = "stable", components rustfmt + clippy). # nixpkgs' own rustc lags a release or two behind stable, and we need a # current one (yrs 0.27 uses `if let` match guards) — so every dev shell and # the `lichen` package take rust from fenix, never from pkgs. # `extraTargets` folds in cross rust-std sets (e.g. musl) for the shells that # link static binaries. Bump the toolchain with: nix flake update fenix rustFor = system: extraTargets: fenix.packages.${system}.combine ([ fenix.packages.${system}.stable.cargo fenix.packages.${system}.stable.rustc fenix.packages.${system}.stable.rustfmt fenix.packages.${system}.stable.clippy ] ++ extraTargets); in {
# environment exposed inside the sandboxed lichen-shell. # build with: nix build .#shell-env packages = forAllSystems (system: let pkgs = import nixpkgs { inherit system; config = { allowUnfree = true; }; }; rustToolchain = rustFor system [ ]; # build `lichen` with the same fenix stable rustc the dev shells use, not the # older one nixpkgs' rustPlatform carries rustPlatform = pkgs.makeRustPlatform { cargo = rustToolchain; rustc = rustToolchain; }; in { shell-env = pkgs.buildEnv { name = "lichen-shell-env"; paths = [ pkgs.bash pkgs.coreutils pkgs.git pkgs.ripgrep pkgs.fd pkgs.jq pkgs.curl pkgs.claude-code opencode-nix.packages.${system}.opencode ]; };
# lichen — install with: nix profile install .#lichen # run with: nix run .#lichen lichen = rustPlatform.buildRustPackage { pname = "lichen"; version = "0.3.0"; src = ./.; cargoLock = { lockFile = ./Cargo.lock; # git dependency — update this hash when bumping atproto-oauth. # to get the correct hash: run `nix build .#lichen` and copy from the error. outputHashes = { "atproto-oauth-0.14.5" = "sha256-XXPMq4we7LDC70R6jrOBVeeyEYwrtamTXvAV8OIqPBU="; }; }; cargoBuildFlags = [ "-p" "lichen-cli" "--bin" "lichen" ]; meta.mainProgram = "lichen"; # preserve the existing deployable Nix profile; Cargo defaults keep iroh optional buildFeatures = [ "iroh" "atproto" ]; nativeBuildInputs = [ pkgs.pkg-config pkgs.git pkgs.makeWrapper ]; # ssh-keygen is needed at runtime for deploy key generation postInstall = '' wrapProgram $out/bin/lichen \ --prefix PATH : ${pkgs.lib.makeBinPath [ pkgs.openssh pkgs.git pkgs.which pkgs.bash ]} ''; buildInputs = pkgs.lib.optionals pkgs.stdenv.isDarwin [ pkgs.darwin.apple_sdk.frameworks.Security pkgs.darwin.apple_sdk.frameworks.SystemConfiguration ]; # tests are run separately via `cargo test`; the nix sandbox # causes flaky failures in git-dependent tests doCheck = false; };
# one executable for the client, local manager, and hosted server default = self.packages.${system}.lichen; } );
# NixOS module exposing `services.lichen` — runs the unified lichen # package as a systemd service with typed, documented options. A # deployment imports this and passes its vars (port, bindAddress, # baseUrl, environmentFile for secrets, …). See nix/lichen-service.nix. nixosModules.lichen = import ./nix/lichen-service.nix { inherit self; }; nixosModules.default = self.nixosModules.lichen;
devShells = forAllSystems (system: let pkgs = import nixpkgs { inherit system; config = { allowUnfree = true; }; }; shell-env = self.packages.${system}.shell-env; rustStable = rustFor system [ ]; in { default = pkgs.mkShell { buildInputs = [ rustStable pkgs.git # fast frontend regressions run in the normal gate pkgs.nodejs_22 # local launchers use Python to locate Cargo's target directory pkgs.python3 # the SSG binaries lichen execs. the PR gate runs the unit tests with # lichen-cms/hugo enabled and those tests shell out to `hugo` by name — without it # they fail with "failed to execute hugo" and read as defects rather than a missing # binary. (the e2e shell carries these for the same reason.) pkgs.hugo pkgs.mdbook # local launchers use lsof to clear a stale development listener pkgs.lsof # LINECOV-1. e2e/linecov/run-linecov.sh:125 runs `nix develop -c build-instrumented.sh` # -- THE DEFAULT SHELL, no .#e2e -- and build-instrumented.sh:13 sources covenv.sh, # whose :63 is `source <(cargo llvm-cov show-env --sh)`. So the tool has to be HERE. # Putting cargo-llvm-cov in the workflow's dependencies.nixpkgs does NOT reach it: # `nix develop` replaces PATH with this shell's, so the container's nixery packages # are invisible to it. Measured in the 8ec3594b suite: `error: no such command: # llvm-cov`, then covenv.sh:77's unbound variable fourteen lines later, because # `source <(...)` on the EMPTY output of a failed command returns 0 and says nothing. # binutils comes with it because build-instrumented.sh:33 calls `nm` to prove the # binaries are instrumented, and nm is in neither shell today. pkgs.cargo-llvm-cov pkgs.binutils # `just gate` is the PR gate; jq derives the crate dependency graph it lints pkgs.just pkgs.jq pkgs.ripgrep # the concurrency-model gate extracts production source with awk pkgs.gawk # the PTY resolves its shell executable with which pkgs.which ]; shellHook = '' # PERF-8. Three runners (integration/run.sh:65, feature-crawl/run-e2e.sh:106, # linecov/run-linecov.sh) fall back to # nix eval --impure --expr "(builtins.getFlake (toString $REPO_ROOT)).inputs.nixpkgs.outPath" # when NIX_PATH is empty. getFlake on a PATH copies THE WHOLE WORKSPACE INTO THE STORE # first, and in CI that workspace now holds the restored target/ -- measured at 5.2 GB in # run 3, taking 4+ minutes before a single test ran. The cache restore makes it WORSE, # because the thing being copied is exactly what the restore just enlarged. # pkgs.path is the same nixpkgs, already realised, and costs nothing to name. # Each fallback is guarded by an is-NIX_PATH-empty test, so setting it here means # none of them ever runs. NOTE FOR EDITORS: this is a Nix indented string, so a bare # dollar-brace is interpolation and a bare pair of single quotes ENDS THE STRING. # Both of those broke this flake while this very comment was being written. Keep the # prose free of them. # ${nixpkgs}, NOT pkgs.path: pkgs.path is a SEPARATE 453 MB store path (nixpkgs # re-imported under its own hash), so naming it would make every container substitute # 453 MB it does not otherwise need -- reintroducing the cost this change removes. # ${nixpkgs} is the flake input, already fetched by the evaluation that builds this # shell, and it is byte-for-byte what the fallback computes today. export NIX_PATH=nixpkgs=${nixpkgs} # The build, PATH entry, and test use the same target; the fallback preserves the default. # This Nix indented string needs its two-apostrophe escape before shell dollar-brace expansions. _lichen_target="''${CARGO_TARGET_DIR:-$PWD/target}" export PATH="$_lichen_target/debug:$PATH" if [ ! -x "$_lichen_target/debug/lichen" ]; then echo "building lichen..." cargo build -p lichen-cli --quiet fi ''; };
# includes bubblewrap and the shell-env packages on PATH # usage: nix develop .#shell shell = pkgs.mkShell { buildInputs = [ rustStable pkgs.bubblewrap shell-env ]; };
# release automation shell # usage: nix develop .#release release = let # rust toolchain with musl targets for static linking rustToolchain = rustFor system [ fenix.packages.${system}.targets.x86_64-unknown-linux-musl.stable.rust-std fenix.packages.${system}.targets.aarch64-unknown-linux-musl.stable.rust-std ]; crossCC = pkgs.pkgsCross.aarch64-multiplatform.stdenv.cc; muslCC = pkgs.pkgsCross.musl64.stdenv.cc; in pkgs.mkShell { buildInputs = [ rustToolchain pkgs.git pkgs.docker crossCC muslCC (pkgs.python3.withPackages (ps: [ ps.requests ps.python-dotenv ps.click ps.fastapi ps.uvicorn ])) ]; env.CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER = "${crossCC}/bin/aarch64-unknown-linux-gnu-gcc"; env.CC_aarch64_unknown_linux_musl = "${crossCC}/bin/aarch64-unknown-linux-gnu-gcc"; env.CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER = "${muslCC}/bin/x86_64-unknown-linux-musl-gcc"; env.CC_x86_64_unknown_linux_musl = "${muslCC}/bin/x86_64-unknown-linux-musl-gcc"; shellHook = '' echo "r-lichen release shell" echo " cd release && python3 -m release --help" ''; };
# tauri desktop app development shell # usage: nix develop .#tauri tauri = let # rust toolchain with musl target for static git-remote-pds builds rustToolchain = rustFor system [ fenix.packages.${system}.targets.x86_64-unknown-linux-musl.stable.rust-std ]; # musl-wrapped gcc for linking musl-target binaries muslPkgs = pkgs.pkgsCross.musl64; muslCC = muslPkgs.stdenv.cc; in pkgs.mkShell { buildInputs = [ rustToolchain pkgs.pkg-config muslCC ] ++ pkgs.lib.optionals pkgs.stdenv.isLinux [ pkgs.gtk3 pkgs.webkitgtk_4_1 pkgs.libsoup_3 pkgs.glib pkgs.cairo pkgs.pango pkgs.gdk-pixbuf pkgs.atk pkgs.zlib pkgs.gsettings-desktop-schemas pkgs.shared-mime-info pkgs.glib-networking ] ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [ pkgs.darwin.apple_sdk.frameworks.WebKit pkgs.darwin.apple_sdk.frameworks.Cocoa ]; env.CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER = "${muslCC}/bin/x86_64-unknown-linux-musl-gcc"; env.CC_x86_64_unknown_linux_musl = "${muslCC}/bin/x86_64-unknown-linux-musl-gcc"; shellHook = '' # Runtime library path for GTK/WebKit shared objects export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath [ pkgs.gtk3 pkgs.webkitgtk_4_1 pkgs.libsoup_3 pkgs.glib pkgs.cairo pkgs.pango pkgs.gdk-pixbuf pkgs.atk pkgs.zlib ]}:$LD_LIBRARY_PATH" # GTK file chooser needs GSettings schemas at runtime export XDG_DATA_DIRS="${pkgs.gsettings-desktop-schemas}/share/gsettings-schemas/${pkgs.gsettings-desktop-schemas.name}:${pkgs.gtk3}/share/gsettings-schemas/${pkgs.gtk3.name}:${pkgs.shared-mime-info}/share:$XDG_DATA_DIRS" export GIO_MODULE_DIR="${pkgs.glib-networking}/lib/gio/modules" echo "r-lichen tauri dev shell" echo " cargo run --all-features -p lichen-tauri" ''; };
# server deploy shell: musl toolchain + ansible for lmdeploy.sh # usage: nix develop .#deploy deploy = let rustToolchain = rustFor system [ fenix.packages.${system}.targets.x86_64-unknown-linux-musl.stable.rust-std ]; muslPkgs = pkgs.pkgsCross.musl64; muslCC = muslPkgs.stdenv.cc; in pkgs.mkShell { buildInputs = [ rustToolchain pkgs.pkg-config muslCC pkgs.ansible pkgs.openssh pkgs.git ]; env.CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER = "${muslCC}/bin/x86_64-unknown-linux-musl-gcc"; env.CC_x86_64_unknown_linux_musl = "${muslCC}/bin/x86_64-unknown-linux-musl-gcc"; shellHook = '' echo "r-lichen deploy shell" echo " ./lmdeploy.sh build + deploy to inventory.yml" echo " ./lmdeploy.sh --revert revert to the image binary" ''; };
# e2e test shell with Python + Playwright # usage: nix develop .#e2e e2e = pkgs.mkShell { buildInputs = [ rustStable pkgs.git # ONE-BUILD-1 ruling 3 moved the gate's fmt-check and clippy into `e2e/run.sh standard`, # which runs under `nix develop .#e2e` -- and `nix develop` REPLACES PATH, so # anything the workflow image declares is invisible in here. Run 5 proved it: # lanes.sh: line 128: just: command not found rc=127, ran=0, twice # and because pr-gate.yml no longer matches standard-*, formatting and lint then ran # NOWHERE for a test- push. The checks moved and their invoker could not reach them. # # These four are exactly what pr-gate.yml's image declares for `just gate`, which is # the direct evidence of what the recipe needs: just itself, and jq/grep/sed because # `just clippy` derives the crate closure with them. jq was in the DEFAULT devshell # (flake.nix:172) and never here. gnugrep/gnused are NOT part of coreutils in # nixpkgs, so naming them is required rather than assumed. pkgs.just pkgs.jq pkgs.gnugrep pkgs.gnused
# GATE-ROOT-1, and it is in HERE for the same measured reason as the four above. # The suite drops to a non-root uid with `setpriv`, which is util-linux and NOT # coreutils. Declaring it in standard.yml's image would put it on the image PATH # and NOT in this devshell -- the exact shape that made `just` rc=127 twice while # looking declared. The suite runs inside `nix develop .#e2e`, so setpriv has to be # reachable from inside it. pkgs.util-linux
# THEIRS (42cc1996): `which`, and nodejs_22 for their gate's `test-js` # (`node --test crates/lichen-cms/tests/*.test.cjs`). Both go in .#e2e for the same # reason as the five above: `nix develop` REPLACES PATH, so a tool declared only on # the workflow image is invisible to `e2e/run.sh standard` and comes back as rc=127 -- # which is what JUST-1 was, twice. A gate step that cannot find node fails the same way. pkgs.which pkgs.nodejs_22
# DRIFT-1, and it is JUST-1 a third time. `e2e/lib/test_workflow_drift.py` is the # ONLY automated check that standard.yml and full.yml have not drifted apart # -- the workflow phase A runs under versus the one phase B runs under, which item 26 # requires to be one series on one sha. It parsed them with # `nix shell nixpkgs#yq-go --command yq` -- a RUNTIME NIX ENTRY, which failed in the # job container for a reason the artifact does not settle (the skip's diagnosis was # built from stderr and discarded by the non-verbose runner; the surviving candidates # are no registry/network, or nix un-re-enterable after the uid drop). Either way # `_load` raised SkipTest and BOTH of # that file's tests skipped on every CI run. Measured in A1r's artifact: the e2e/lib # unit tests printed `ss` and `OK (skipped=2)` while linecov's own skip banner said # 1 -- the banner reads the coverage harness's channel and cannot see unittest's, so # nothing in the run reported it. The guard was dead and looked declared, which is # the same shape as `just` at rc=127 for four pushes. # # Declared HERE rather than on the image because `nix develop` REPLACES PATH: on the # image it would be just as invisible. yq-go specifically, not python yaml, because # the comparison must be on the structure the CI ENGINE sees. pkgs.yq-go (pkgs.python3.withPackages (ps: [ ps.pexpect ps.playwright ps.pytest ps.pytest-xdist ps.requests ps.toml # Every import the suite makes is satisfied by `.#e2e` itself, so no lane needs a # pip install. Same reason as `just` and `setpriv` above: `nix develop` REPLACES # PATH, so a dependency absent from this list is not reachable. ps.pycrdt ])) pkgs.playwright-driver.browsers # Chromium crashes while shaping the CMS controls if the shell has no # discoverable fonts, so carry both fontconfig and a concrete font family. pkgs.fontconfig pkgs.dejavu_fonts # the external SSG binaries the suites drive. lichen shells out to # these by name, so without them on PATH a Hugo/mdBook site 500s with # "failed to execute hugo" and the suite reds for a missing binary # rather than a defect (7 suites did exactly that on 2026-08-14). pkgs.hugo pkgs.mdbook # ...and openssh, for exactly the same reason, found the same way. `lichen` runs # `ssh-keygen` BY NAME to make a deploy key (lichen-server/src/users.rs:347), and in # the container that call returns # run ssh-keygen: No such file or directory (os error 2) # which surfaces as `lichen-server::api::deploy_key_and_compact` and # `deploy_key_generate_failure_then_success` failing with an AssertionError -- two of # the standing reds, red for an ABSENCE and not for a defect. Measured in run # 3mur4cvwe4m22 (SOURCE_SHA c4bcfbca), the run's own failure text. # # ⚠ AND IT RESOLVES ON A DEV BOX, WHICH IS WHY IT SURVIVED. Inside `nix develop .#e2e` # here, `command -v ssh-keygen` answers /run/current-system/sw/bin/ssh-keygen -- a HOST # path, not a store path. The tool is on PATH because THIS MACHINE has it; the # container has no /run/current-system at all. A store path transfers and a host path # does not, so "it works locally" was never evidence about CI. (gawk was checked the # same way and is NOT added: it answers a /nix/store gawk-5.3.2 path, supplied by # stdenv to every mkShell, so it is already there despite being absent from this list.) # the managed OAuth domain fixture owns a temporary TLS proxy pkgs.caddy pkgs.openssh # port cleanup between tests: conftest teardown kills leftover # servers with `fuser -k <port>/tcp` (psmisc) and `lsof -ti tcp:<port>`. pkgs.psmisc pkgs.procps # pgrep: fail-closed inter-phase survivor checks pkgs.lsof # fc-list, so browser_preflight can COUNT fonts instead of printing # `fonts=unknown(no fc-list)`. That line was the blind spot: the preflight # already refused to report an absent reader as zero fonts, correctly — and # the answer turned out to BE zero. With fc-list present the artifact carries # a number, so this fix is checkable from the run rather than inferred. pkgs.fontconfig ]; env.FONTCONFIG_FILE = pkgs.makeFontsConf { fontDirectories = [ pkgs.dejavu_fonts ]; }; # NAMES THIS SHELL, so `run_in_e2e_shell` can tell it from any other nix shell. # IN_NIX_SHELL cannot: nix sets it in EVERY devshell, so the default shell sets it # too. scripts/ci-test.sh runs inside the DEFAULT shell. A guard on IN_NIX_SHELL # therefore keeps every e2e lane out of this shell, silently. This shell is where # the pinned LD_LIBRARY_PATH below lives. env.LICHEN_E2E_SHELL = "1"; shellHook = '' # Native pip wheels (including greenlet) need the pinned C++ runtime. export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath [ pkgs.stdenv.cc.cc.lib ]}''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" # PERF-8. Three runners (integration/run.sh:65, feature-crawl/run-e2e.sh:106, # linecov/run-linecov.sh) fall back to # nix eval --impure --expr "(builtins.getFlake (toString $REPO_ROOT)).inputs.nixpkgs.outPath" # when NIX_PATH is empty. getFlake on a PATH copies THE WHOLE WORKSPACE INTO THE STORE # first, and in CI that workspace now holds the restored target/ -- measured at 5.2 GB in # run 3, taking 4+ minutes before a single test ran. The cache restore makes it WORSE, # because the thing being copied is exactly what the restore just enlarged. # pkgs.path is the same nixpkgs, already realised, and costs nothing to name. # Each fallback is guarded by an is-NIX_PATH-empty test, so setting it here means # none of them ever runs. NOTE FOR EDITORS: this is a Nix indented string, so a bare # dollar-brace is interpolation and a bare pair of single quotes ENDS THE STRING. # Both of those broke this flake while this very comment was being written. Keep the # prose free of them. # ${nixpkgs}, NOT pkgs.path: pkgs.path is a SEPARATE 453 MB store path (nixpkgs # re-imported under its own hash), so naming it would make every container substitute # 453 MB it does not otherwise need -- reintroducing the cost this change removes. # ${nixpkgs} is the flake input, already fetched by the evaluation that builds this # shell, and it is byte-for-byte what the fallback computes today. export NIX_PATH=nixpkgs=${nixpkgs} export PLAYWRIGHT_BROWSERS_PATH=${pkgs.playwright-driver.browsers} export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 # PW-LOADER-1. The venv's playwright is the PIP wheel (setup.sh pip-installs # playwright==1.60.0 and LINECOV-3 makes the linecov lane use the venv rather than # the store copy), and that wheel ships its own driver/node: a 123 MB DYNAMICALLY # LINKED glibc binary. In run 3murc64enwv22 that produced, 32 times, # FileNotFoundError: .../site-packages/playwright/driver/node # ⚠ AND THE FILE IS NOT MISSING. It is present and executable. The ENOENT is the # kernel failing to find its ELF INTERPRETER: this host resolves # /lib64/ld-linux-x86-64.so.2 through nix-ld and the binary runs (v24.18.1); the # nixery container has neither nix-ld nor /lib64, so the LOADER is what is absent # and python reports it against the path it was asked to exec. Same shape as the # ssh-keygen absence found the same day: IT WORKS HERE BECAUSE THE HOST SUPPLIES # SOMETHING THE CONTAINER DOES NOT. # # Read out of the INSTALLED source at the pinned version, not from documentation -- # playwright/_impl/_driver.py:33 is # return (os.getenv("PLAYWRIGHT_NODEJS_PATH", str(driver_path / "node")), cli_path) # so this redirects the driver onto the nodejs_22 already in this devshell (declared # for `just test-js`, reused here). Version checked rather than assumed: playwright # 1.60 needs node >= 18 and this is 22.x, against the bundle's 24.x. # # It is also ROBUST TO THE DIAGNOSIS BEING WRONG: if the container's copy were # genuinely absent rather than unloadable, bypassing the bundled binary fixes that # case too. The mechanism above is what the evidence says; the fix does not depend # on winning that argument. export PLAYWRIGHT_NODEJS_PATH=${pkgs.nodejs_22}/bin/node # CRAWL-FIX-1. In the pipeline container there is no /etc/fonts, so fontconfig # falls back to its stock config and finds NO FONTS AT ALL. Chromium's browser # process then stalls ~3.3s PER NAVIGATION in font fallback — render_text_harfbuzz # errors whose reported font name is EMPTY — which turned a 30s goto budget into a timeout. # A fonts.conf naming the store's DejaVu directory takes the same path to ~150ms. # # THIS IS A CONTAINER DEFECT, NOT A TEST DEFECT: the bare guests pass because the # HOST supplies fonts, and the container inherits none. Per the operator's ruling, # a suite that passes bare and fails in CI is fixed in the container. export FONTCONFIG_FILE=${pkgs.makeFontsConf { fontDirectories = [ pkgs.dejavu_fonts ]; }} ''; }; } ); };}