mirror of
https://github.com/moons-14/dotfiles.git
synced 2026-10-06 08:28:11 +09:00
Compare commits
3
Commits
19bc309b8b
...
9771a85e3f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9771a85e3f | ||
|
|
5b1bde7d90 | ||
|
|
1d82ab92b6 |
+10
-6
@@ -19,9 +19,13 @@
|
||||
!/flake/
|
||||
!/overlays/
|
||||
|
||||
!/modules/applications/
|
||||
!/modules/hardwares/
|
||||
!/modules/profiles/
|
||||
!/modules/services/
|
||||
!/modules/systems/
|
||||
!/modules/users/
|
||||
!/images/
|
||||
!/secrets/
|
||||
|
||||
!/hosts/
|
||||
|
||||
!/libs/
|
||||
|
||||
!/modules/
|
||||
|
||||
!/tests/
|
||||
|
||||
@@ -1,28 +1,574 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Project Structure & Module Organization
|
||||
## Project Structure and Ownership
|
||||
|
||||
This repository manages NixOS and Home Manager configuration as a flake. `flake.nix` defines inputs and imports the project modules. Keep flake-level plumbing in `flake/`, reusable package overlays in `overlays/`, and development environments in `shells/`. Add configuration under the appropriate `modules/` category: `applications/`, `hardwares/`, `profiles/`, `services/`, `systems/`, or `users/`. Keep host- or user-specific choices near their owning module rather than in the root flake.
|
||||
This repository manages NixOS, nix-darwin, and Home Manager configurations as a
|
||||
flake. `flake.nix` defines inputs and delegates flake outputs through
|
||||
flake-parts. Keep configuration with the component that owns it, rather than in
|
||||
the root flake or an unrelated host.
|
||||
|
||||
| Path | Responsibility |
|
||||
| ----------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
||||
| `modules/applications/` | One software component, including GUI applications, window managers, desktop environments, CLI tools, and editors |
|
||||
| `modules/systems/` | OS foundations such as Nix, boot, locale, Wayland, and networking |
|
||||
| `modules/services/` | Daemons, long-running services, and configuration that involves permissions or user groups |
|
||||
| `modules/hardwares/` | Reusable drivers, hardware families, and VM or WSL guest configuration |
|
||||
| `modules/users/` | User identity and the user's NixOS-, nix-darwin-, and Home Manager-specific definitions |
|
||||
| `modules/profiles/` | Purpose- or form-factor-oriented compositions of multiple units |
|
||||
| `hosts/` | Machine-specific facts and the profiles or applications selected for each machine |
|
||||
| `libs/` | Registry, unit discovery, and host construction logic |
|
||||
| `overlays/` | Package replacements and additions |
|
||||
| `shells/` | Development shells |
|
||||
| `flake/` | Supporting flake outputs such as formatters, checks, and Git hooks |
|
||||
|
||||
Use **unit** as the generic internal term for a Registry-managed component and
|
||||
**profile** for a unit that composes multiple units. Do not introduce a
|
||||
`features/` layer. Window managers and desktop environments such as niri and
|
||||
GNOME belong in `modules/applications/`; do not create a separate `desktop/`
|
||||
module category.
|
||||
|
||||
Before adding configuration, decide whether it is owned by an application,
|
||||
system foundation, service, hardware family, user, profile, or individual host.
|
||||
Prefer the following placements:
|
||||
|
||||
| Configuration | Placement |
|
||||
| ---------------------------------------------------- | ------------------------------------------------ |
|
||||
| Nix settings shared by every system host | `modules/systems/nix/common.nix` |
|
||||
| NixOS-only boot configuration | `modules/systems/boot/.../nixos.nix` |
|
||||
| Ghostty-specific configuration | `modules/applications/ghostty/` |
|
||||
| niri-specific configuration | `modules/applications/niri/` |
|
||||
| Applications selected for the niri environment | `modules/profiles/interface/niri/meta.nix` |
|
||||
| GNOME itself | `modules/applications/gnome/` |
|
||||
| Docker daemon and Docker group membership | `modules/services/docker/nixos.nix` |
|
||||
| Reusable ThinkPad-family configuration | `modules/hardwares/thinkpad/` |
|
||||
| The laptop unit composition | `modules/profiles/platform/laptop/meta.nix` |
|
||||
| The development-environment unit composition | `modules/profiles/workload/development/meta.nix` |
|
||||
| A user's OS- and Home Manager-specific configuration | `modules/users/<name>/` |
|
||||
| x1g13-specific monitor layout | `hosts/x1g13/home.nix` |
|
||||
| x1g13-specific disk UUID | `hosts/x1g13/nixos.nix` |
|
||||
| Package replacement or addition | `overlays/` |
|
||||
| Formatter, checks, or Git hooks | `flake/` |
|
||||
|
||||
## Unit Discovery and Identity
|
||||
|
||||
A directory below `modules/` is a unit if, and only if, it directly contains at
|
||||
least one reserved file. Directories used only for classification, such as
|
||||
`modules/applications/` or `modules/profiles/interface/`, are namespaces rather
|
||||
than units when they have no reserved file of their own.
|
||||
|
||||
The Registry recognizes exactly these five reserved filenames:
|
||||
|
||||
| File | Target and responsibility |
|
||||
| ------------ | -------------------------------------------------------------------------------- |
|
||||
| `common.nix` | System-side configuration fragment shared by NixOS and nix-darwin |
|
||||
| `nixos.nix` | NixOS-only configuration fragment |
|
||||
| `darwin.nix` | nix-darwin-only configuration fragment |
|
||||
| `home.nix` | Home Manager configuration fragment |
|
||||
| `meta.nix` | Registry descriptor for dependencies, external modules, and descriptive metadata |
|
||||
|
||||
`common.nix` is never applied to Home Manager. OS-independent Home Manager
|
||||
configuration still belongs in `home.nix`.
|
||||
|
||||
The Registry derives a unit ID from the path relative to `modules/`, joining
|
||||
path components with dots. Category names remain plural. It also derives the
|
||||
enable option by prefixing the same components with `my` and appending `enable`.
|
||||
|
||||
| Unit directory | Unit ID | Enable option |
|
||||
| ---------------------------------- | ------------------------- | ----------------------------------- |
|
||||
| `modules/applications/ghostty/` | `applications.ghostty` | `my.applications.ghostty.enable` |
|
||||
| `modules/applications/niri/` | `applications.niri` | `my.applications.niri.enable` |
|
||||
| `modules/systems/boot/uefi/` | `systems.boot.uefi` | `my.systems.boot.uefi.enable` |
|
||||
| `modules/services/docker/` | `services.docker` | `my.services.docker.enable` |
|
||||
| `modules/hardwares/qemu-guest/` | `hardwares.qemu-guest` | `my.hardwares.qemu-guest.enable` |
|
||||
| `modules/users/moons/` | `users.moons` | `my.users.moons.enable` |
|
||||
| `modules/profiles/interface/niri/` | `profiles.interface.niri` | `my.profiles.interface.niri.enable` |
|
||||
|
||||
Represent option paths as attribute-path lists, never as Nix source encoded in
|
||||
strings or evaluated dynamically. Generate and read attributes with helpers such
|
||||
as `lib.setAttrByPath` and `lib.getAttrFromPath`:
|
||||
|
||||
```nix
|
||||
{
|
||||
id = "applications.ghostty";
|
||||
|
||||
optionPath = [
|
||||
"my"
|
||||
"applications"
|
||||
"ghostty"
|
||||
"enable"
|
||||
];
|
||||
|
||||
kind = "applications";
|
||||
name = "ghostty";
|
||||
|
||||
relativePath = [
|
||||
"applications"
|
||||
"ghostty"
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
For `modules/profiles/interface/niri/`, path inference additionally gives
|
||||
`kind = "profiles"`, `group = "interface"`, and `name = "niri"`. The path is
|
||||
always authoritative for identity. `meta.nix` may provide display metadata such
|
||||
as `description`, but it must not override or alias the unit ID.
|
||||
|
||||
## Unit Files and Fragment Contract
|
||||
|
||||
Only reserved files that a unit actually needs should exist. The Registry
|
||||
registers present fragments and does not require empty or placeholder files. All
|
||||
of the following are valid units:
|
||||
|
||||
```text
|
||||
# Home Manager only
|
||||
modules/applications/ghostty/
|
||||
├── home.nix
|
||||
└── settings.nix
|
||||
|
||||
# NixOS only
|
||||
modules/applications/gnome/
|
||||
└── nixos.nix
|
||||
|
||||
# nix-darwin only
|
||||
modules/systems/macos-defaults/
|
||||
└── darwin.nix
|
||||
|
||||
# NixOS and Home Manager, with metadata and helpers
|
||||
modules/applications/niri/
|
||||
├── nixos.nix
|
||||
├── home.nix
|
||||
├── meta.nix
|
||||
├── settings.nix
|
||||
└── keybindings.nix
|
||||
|
||||
# Metadata only, commonly a composition profile
|
||||
modules/profiles/interface/niri/
|
||||
└── meta.nix
|
||||
|
||||
# System configuration shared by NixOS and nix-darwin
|
||||
modules/systems/nix/
|
||||
└── common.nix
|
||||
```
|
||||
|
||||
If a unit has no `home.nix`, do not generate or apply a Home Manager module for
|
||||
it. The same rule applies independently to `common.nix`, `nixos.nix`, and
|
||||
`darwin.nix`.
|
||||
|
||||
### Configuration fragments
|
||||
|
||||
`common.nix`, `nixos.nix`, `darwin.nix`, and `home.nix` are configuration
|
||||
fragments to which the Registry adds the enable condition. They return the
|
||||
configuration for their class directly and must not define top-level `imports`,
|
||||
`options`, or `config` attributes:
|
||||
|
||||
```nix
|
||||
# modules/services/docker/nixos.nix
|
||||
{ primaryUser, ... }:
|
||||
{
|
||||
virtualisation.docker = {
|
||||
enable = true;
|
||||
autoPrune.enable = true;
|
||||
};
|
||||
|
||||
users.users.${primaryUser}.extraGroups = [
|
||||
"docker"
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
Conceptually, the Registry supplies a wrapper like this:
|
||||
|
||||
```nix
|
||||
{ config, lib, ... }@args:
|
||||
{
|
||||
config =
|
||||
lib.mkIf
|
||||
config.my.services.docker.enable
|
||||
(import dockerNixosPath args);
|
||||
}
|
||||
```
|
||||
|
||||
Do not add hand-written `mkEnableOption`, `cfg`, or `mkIf` boilerplate to each
|
||||
unit. The Registry generates the enable option from the unit path and guards the
|
||||
fragment.
|
||||
|
||||
### Helper files and directories
|
||||
|
||||
Every filename other than the five reserved names is an ordinary helper,
|
||||
regardless of its extension. The Registry neither discovers nor automatically
|
||||
imports helper files such as `settings.nix`, `keybindings.nix`, `packages.nix`,
|
||||
`colors.nix`, `rules.nix`, or `helpers.nix`. Import a helper explicitly from the
|
||||
reserved fragment that uses it:
|
||||
|
||||
```nix
|
||||
# modules/applications/niri/home.nix
|
||||
{ lib, ... }:
|
||||
let
|
||||
settings = import ./settings.nix;
|
||||
keybindings = import ./keybindings.nix;
|
||||
in
|
||||
{
|
||||
programs.niri.settings = lib.recursiveUpdate settings {
|
||||
binds = keybindings;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Do not use a leading underscore to mark a file private; `_settings.nix` has no
|
||||
special meaning. Give helper files descriptive names instead. When helpers are
|
||||
configuration functions, pass the module arguments explicitly and combine them
|
||||
with normal Nix expressions:
|
||||
|
||||
```nix
|
||||
# modules/applications/example/home.nix
|
||||
{ lib, ... }@args:
|
||||
lib.mkMerge [
|
||||
(import ./packages.nix args)
|
||||
(import ./settings.nix args)
|
||||
]
|
||||
```
|
||||
|
||||
The same discovery rule applies recursively to helper directories:
|
||||
|
||||
```text
|
||||
modules/applications/niri/
|
||||
├── home.nix
|
||||
└── parts/
|
||||
├── appearance.nix
|
||||
└── keybindings.nix
|
||||
```
|
||||
|
||||
Here `parts/` is not a unit because it directly contains no reserved file. A
|
||||
helper directory that directly contains `home.nix` or another reserved file is
|
||||
itself discovered as a unit, so never use reserved filenames inside a directory
|
||||
that is intended to contain helpers only.
|
||||
|
||||
### Registry metadata
|
||||
|
||||
`meta.nix` is a Registry descriptor, not a NixOS, nix-darwin, or Home Manager
|
||||
module. It may declare `description`, `includes`, and class-specific external
|
||||
module imports:
|
||||
|
||||
```nix
|
||||
# modules/applications/niri/meta.nix
|
||||
{ inputs, ... }:
|
||||
{
|
||||
description = "Niri Wayland compositor";
|
||||
|
||||
includes = [
|
||||
"systems.wayland"
|
||||
"services.xdg-portal"
|
||||
];
|
||||
|
||||
imports.nixos = [
|
||||
inputs.niri-flake.nixosModules.niri
|
||||
];
|
||||
|
||||
imports.home = [
|
||||
inputs.niri-flake.homeModules.niri
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
External modules, including modules supplied by flake inputs, define Nix module
|
||||
options and therefore belong in `meta.nix` under `imports.nixos`,
|
||||
`imports.darwin`, or `imports.home`. Do not place them in a configuration
|
||||
fragment's top-level `imports`: the Nix module system resolves imports before a
|
||||
configuration-level enable condition.
|
||||
|
||||
`includes` lists units to enable whenever the declaring unit is enabled. Always
|
||||
use fully qualified unit IDs:
|
||||
|
||||
```nix
|
||||
# modules/profiles/interface/niri/meta.nix
|
||||
{
|
||||
includes = [
|
||||
"applications.niri"
|
||||
"applications.ghostty"
|
||||
"applications.noctalia"
|
||||
"applications.vicinae"
|
||||
"applications.nautilus"
|
||||
"services.xdg-portal"
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
Never omit a prefix such as `applications.` merely because the including unit is
|
||||
a profile. Fully qualified IDs make ownership explicit and allow moves, name
|
||||
collisions, and missing dependencies to be detected. Do not enable another unit
|
||||
by assigning to its enable option from a class fragment; declare the dependency
|
||||
in `meta.includes`.
|
||||
|
||||
Application metadata should include only dependencies technically required for
|
||||
the application to work. A profile owns the user's choice to adopt several
|
||||
otherwise independent applications together. For example, niri may include the
|
||||
Wayland foundation and xdg-desktop-portal as technical dependencies, while
|
||||
`profiles.interface.niri` selects Ghostty, Vicinae, Noctalia, and Nautilus.
|
||||
Ghostty must not depend on niri, and niri-specific keybindings remain owned by
|
||||
the niri unit.
|
||||
|
||||
## Profiles
|
||||
|
||||
Profiles compose units by purpose or form factor; they do not replace clear
|
||||
application, system, service, or hardware ownership. Suitable profile namespaces
|
||||
include:
|
||||
|
||||
```text
|
||||
modules/profiles/
|
||||
├── base/
|
||||
├── interface/
|
||||
│ ├── niri/
|
||||
│ ├── gnome/
|
||||
│ ├── cli-minimal/
|
||||
│ └── cli-interactive/
|
||||
├── platform/
|
||||
│ ├── laptop/
|
||||
│ ├── thinkpad/
|
||||
│ ├── desktop/
|
||||
│ ├── vm/
|
||||
│ └── wsl/
|
||||
├── workload/
|
||||
│ ├── development/
|
||||
│ ├── personal/
|
||||
│ ├── server/
|
||||
│ └── remote/
|
||||
└── security/
|
||||
└── secure-boot/
|
||||
```
|
||||
|
||||
The `desktop/` name above is a form-factor profile under `profiles/platform/`,
|
||||
not a top-level module category.
|
||||
|
||||
A profile may consist only of `meta.includes`. Small settings that belong only
|
||||
to the composition and have no useful independent identity may go directly in
|
||||
the profile's `nixos.nix`, `darwin.nix`, or `home.nix`. Extract configuration to
|
||||
an appropriate application, system, service, or hardware unit when any of these
|
||||
conditions holds:
|
||||
|
||||
- It should be independently enableable.
|
||||
- Multiple profiles reuse it.
|
||||
- It owns separate configuration files.
|
||||
- Its NixOS, nix-darwin, and Home Manager implementations differ.
|
||||
- Other units depend on it.
|
||||
- It involves a daemon, permissions, or user groups.
|
||||
|
||||
## Registry Responsibilities
|
||||
|
||||
Implement unit discovery with Nix standard functionality such as
|
||||
`builtins.readDir`. Do not depend on an external indiscriminate auto-import
|
||||
mechanism, and do not design the repository around `import-tree`. Registry logic
|
||||
has these responsibilities:
|
||||
|
||||
1. Recursively visit directories below `modules/`.
|
||||
2. Check only the five reserved filenames directly within each directory.
|
||||
3. Register a directory as a unit when at least one reserved file exists there.
|
||||
4. Derive the unit ID from the path relative to `modules/`.
|
||||
5. Record only class fragments that exist.
|
||||
6. Evaluate `meta.nix` as a descriptor only when it exists.
|
||||
7. Exclude non-reserved files from discovery and implicit imports.
|
||||
8. Generate every unit's `my.<unit path>.enable` option.
|
||||
9. Enable included units from `meta.includes`.
|
||||
10. Raise a clear evaluation error for a reference to a missing unit ID.
|
||||
11. Apply only the fragments appropriate to the current host class.
|
||||
12. Pass `home.nix` to Home Manager only for hosts that enable Home Manager.
|
||||
|
||||
A unit record may conceptually look like this; the implementation need not use
|
||||
this exact representation:
|
||||
|
||||
```nix
|
||||
{
|
||||
id = "applications.ghostty";
|
||||
directory = ./applications/ghostty;
|
||||
|
||||
fragments = {
|
||||
common = null;
|
||||
nixos = null;
|
||||
darwin = null;
|
||||
home = ./applications/ghostty/home.nix;
|
||||
};
|
||||
|
||||
meta = { };
|
||||
}
|
||||
```
|
||||
|
||||
Keep the custom Registry limited to unit discovery, enable-option generation,
|
||||
`includes`, and class dispatch. Do not reimplement general Nix imports or Nix
|
||||
module evaluation. In particular, never infer a unit ID from metadata or
|
||||
implicitly load a non-reserved file.
|
||||
|
||||
## Hosts and Class Dispatch
|
||||
|
||||
`hosts/` is outside Registry discovery. A host contains machine-specific facts,
|
||||
differences, and unit selection, not reusable shared configuration. Appropriate
|
||||
host-owned data includes:
|
||||
|
||||
- Generated `hardware-configuration.nix`.
|
||||
- Disk UUIDs and disko target devices.
|
||||
- Monitor identifiers, layout, and scale.
|
||||
- MAC addresses and static IP addresses.
|
||||
- Kernel parameters required by one machine only.
|
||||
- `system.stateVersion`.
|
||||
- Host-specific secret references.
|
||||
- The profiles, applications, and other units enabled on that host.
|
||||
|
||||
A host registry may use a specification like this:
|
||||
|
||||
```nix
|
||||
# hosts/default.nix
|
||||
{
|
||||
x1g13 = {
|
||||
system = "x86_64-linux";
|
||||
user = "moons";
|
||||
path = ./x1g13;
|
||||
|
||||
profiles = [
|
||||
"base"
|
||||
"platform.thinkpad"
|
||||
"interface.niri"
|
||||
"interface.gnome"
|
||||
"workload.development"
|
||||
"workload.personal"
|
||||
"security.secure-boot"
|
||||
];
|
||||
|
||||
applications = [
|
||||
"codex-desktop"
|
||||
];
|
||||
|
||||
units = [
|
||||
"services.tailscale"
|
||||
];
|
||||
};
|
||||
|
||||
macbook = {
|
||||
system = "aarch64-darwin";
|
||||
user = "moons";
|
||||
path = ./macbook;
|
||||
|
||||
profiles = [
|
||||
"base"
|
||||
"platform.laptop"
|
||||
"workload.development"
|
||||
"workload.personal"
|
||||
];
|
||||
|
||||
applications = [
|
||||
"ghostty"
|
||||
];
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Treat entries in `profiles` and `applications` as IDs relative to their
|
||||
respective category roots. Add the category prefixes during host construction:
|
||||
|
||||
```nix
|
||||
selectedUnits =
|
||||
[ "users.${spec.user}" ]
|
||||
++ map (name: "profiles.${name}") spec.profiles
|
||||
++ map (name: "applications.${name}") spec.applications
|
||||
++ spec.units or [ ];
|
||||
```
|
||||
|
||||
`units` is an escape hatch for fully qualified service, system, hardware, or
|
||||
other unit IDs. Prefer profiles for the main composition; do not make hosts list
|
||||
large numbers of low-level units directly.
|
||||
|
||||
Host modules use normal Nix module semantics and are not Registry-guarded
|
||||
configuration fragments. For example:
|
||||
|
||||
```text
|
||||
hosts/x1g13/
|
||||
├── nixos.nix
|
||||
├── home.nix
|
||||
├── hardware-configuration.nix
|
||||
└── disko.nix
|
||||
```
|
||||
|
||||
`hosts/x1g13/nixos.nix` may explicitly load `hardware-configuration.nix` and
|
||||
`disko.nix` with the normal top-level Nix module `imports`. Do not confuse these
|
||||
host imports with the prohibition on top-level `imports` in unit configuration
|
||||
fragments.
|
||||
|
||||
Derive the system class from the host's `system`:
|
||||
|
||||
- A Linux NixOS host receives `common.nix` and `nixos.nix`.
|
||||
- A nix-darwin host receives `common.nix` and `darwin.nix`.
|
||||
- A host with integrated Home Manager additionally receives `home.nix`.
|
||||
|
||||
Home Manager is additive, not a system class mutually exclusive with NixOS or
|
||||
nix-darwin. The supported combinations are NixOS plus Home Manager and
|
||||
nix-darwin plus Home Manager. If standalone Home Manager is supported later, add
|
||||
an explicit host kind because `system` alone cannot distinguish it from NixOS.
|
||||
|
||||
Do not duplicate reusable settings in hosts, but do not force genuinely
|
||||
machine-specific values into a common unit merely to remove a host-local line.
|
||||
|
||||
## Coding Style and Implementation Rules
|
||||
|
||||
Use two-space indentation in Nix files and let `nixfmt` decide layout. Prefer
|
||||
small units, explicit imports, and descriptive kebab-case names, for example
|
||||
`modules/services/media-server/nixos.nix`. Use camelCase for Nix attributes
|
||||
unless an upstream option dictates otherwise. Shell snippets must pass `shfmt`
|
||||
and `shellcheck`; YAML, TOML, and Markdown are formatted by the configured
|
||||
treefmt tools.
|
||||
|
||||
When implementing or modifying modules:
|
||||
|
||||
- Do not create `features/` or a top-level `desktop/` module category.
|
||||
- Do not add per-unit `mkEnableOption`, `cfg`, or `mkIf` boilerplate; the Registry
|
||||
derives and guards enable options from paths.
|
||||
- Put unit dependencies in `meta.includes`, not in direct assignments to another
|
||||
unit's enable option from a class fragment.
|
||||
- Do not assume any non-reserved file is discovered or loaded automatically.
|
||||
- Do not require an `_` prefix for helper or private files.
|
||||
- Do not create unused `common.nix`, `nixos.nix`, `darwin.nix`, `home.nix`, or
|
||||
`meta.nix` files.
|
||||
- Do not override a path-derived unit ID from `meta.nix`.
|
||||
- Keep technical application dependencies separate from the applications a
|
||||
personal environment chooses to combine in a profile.
|
||||
- Do not rely on module-list ordering to override values. Use Nix module
|
||||
priorities such as `lib.mkDefault`, `lib.mkForce`, `lib.mkBefore`, or
|
||||
`lib.mkAfter` explicitly when required.
|
||||
- Keep Registry responsibilities narrow; use normal Nix imports and module
|
||||
evaluation for everything outside discovery, generated enables, includes, and
|
||||
class dispatch.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
|
||||
- `nix develop .#dotnix` enters the main development shell and installs the repository's pre-commit hooks.
|
||||
- `nix develop .#android` provides Android platform tools such as `adb` and `fastboot`.
|
||||
- `nix develop .#dotnix` enters the main development shell and installs the
|
||||
repository's pre-commit hooks.
|
||||
- `nix develop .#android` provides Android platform tools such as `adb` and
|
||||
`fastboot`.
|
||||
- `nix fmt` formats all supported files through treefmt.
|
||||
- `nix flake check` evaluates flake outputs and runs configured checks.
|
||||
- `pre-commit run --all-files` runs formatting, dead-code and static Nix checks, shell linting, and secret scanning.
|
||||
- `nix flake update` refreshes pinned inputs in `flake.lock`; review lockfile changes before committing.
|
||||
- `pre-commit run --all-files` runs formatting, dead-code and static Nix checks,
|
||||
shell linting, and secret scanning.
|
||||
- `nix flake update` refreshes pinned inputs in `flake.lock`; review lockfile
|
||||
changes before committing.
|
||||
|
||||
If direnv is installed, `direnv allow` activates the `dotnix` shell from `.envrc` automatically.
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
|
||||
Use two-space indentation in Nix files and let `nixfmt` decide layout. Prefer small modules with explicit imports and descriptive kebab-case filenames, for example `modules/services/media-server.nix`. Use camelCase for Nix attributes unless an upstream option dictates otherwise. Shell snippets must pass `shfmt` and `shellcheck`; YAML, TOML, and Markdown are formatted by the configured treefmt tools.
|
||||
If direnv is installed, `direnv allow` activates the `dotnix` shell from `.envrc`
|
||||
automatically.
|
||||
|
||||
## Testing Guidelines
|
||||
|
||||
There is no separate unit-test suite. Before submitting changes, run `nix flake check` and `pre-commit run --all-files`. For system-specific changes, also build or evaluate the affected NixOS/Home Manager configuration without switching the live machine. Never commit generated secrets, `.age` plaintext, or local `.direnv/` state.
|
||||
There is no separate unit-test suite. Before submitting changes, run
|
||||
`nix flake check` and `pre-commit run --all-files`. For system-specific changes,
|
||||
also build or evaluate the affected NixOS, nix-darwin, or Home Manager
|
||||
configuration without switching the live machine. Never commit generated
|
||||
secrets, `.age` plaintext, or local `.direnv/` state.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
For Registry changes, test discovery of each supported fragment combination,
|
||||
dependency closure through `meta.includes`, missing-unit errors, and class
|
||||
dispatch. Verify that helper files are ignored until explicitly imported and
|
||||
that directories without a directly contained reserved file remain namespaces.
|
||||
|
||||
Recent history favors short, lowercase, imperative subjects such as `fix` and `update action`; automated dependency commits use `chore(deps): ...`. Prefer a specific summary that states the affected area, such as `shells: add deployment tools`. Keep commits focused. Pull requests should explain the motivation, list affected hosts or profiles, report validation commands, and note any manual migration or secret-management steps. Include screenshots only for visible desktop or application configuration changes.
|
||||
## Commit and Pull Request Guidelines
|
||||
|
||||
Recent history favors short, lowercase, imperative subjects such as `fix` and
|
||||
`update action`; automated dependency commits use `chore(deps): ...`. Prefer a
|
||||
specific summary that states the affected area, such as
|
||||
`shells: add deployment tools`. Keep commits focused. Pull requests should
|
||||
explain the motivation, list affected hosts or profiles, report validation
|
||||
commands, and note any manual migration or secret-management steps. Include
|
||||
screenshots only for visible desktop or application configuration changes.
|
||||
|
||||
Generated
+22
@@ -570,6 +570,27 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-darwin": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1783744694,
|
||||
"narHash": "sha256-2cp6N3rrwnGYLTx9l6N+NI+kwrCWxvJUbj5WJhvB29A=",
|
||||
"owner": "nix-darwin",
|
||||
"repo": "nix-darwin",
|
||||
"rev": "c3e90c89649b07d1a96e4b9dd6cd0d6e44b91a74",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-darwin",
|
||||
"ref": "nix-darwin-26.05",
|
||||
"repo": "nix-darwin",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-index-database": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
@@ -962,6 +983,7 @@
|
||||
"lanzaboote": "lanzaboote",
|
||||
"llm-agents": "llm-agents",
|
||||
"niri-flake": "niri-flake",
|
||||
"nix-darwin": "nix-darwin",
|
||||
"nix-index-database": "nix-index-database",
|
||||
"nixos-hardware": "nixos-hardware",
|
||||
"nixos-wsl": "nixos-wsl",
|
||||
|
||||
@@ -11,6 +11,11 @@
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
nix-darwin = {
|
||||
url = "github:nix-darwin/nix-darwin/nix-darwin-26.05";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
# Hardware / Platform
|
||||
nixos-hardware.url = "github:NixOS/nixos-hardware/master";
|
||||
nixos-wsl.url = "github:nix-community/NixOS-WSL";
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
imports = [
|
||||
./formatter.nix
|
||||
./git-hooks.nix
|
||||
./registry.nix
|
||||
];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
inputs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
dotfilesLib = import ../libs {
|
||||
inherit inputs lib;
|
||||
root = ../.;
|
||||
};
|
||||
hostSpecsPath = ../hosts/default.nix;
|
||||
hostSpecs =
|
||||
if builtins.pathExists hostSpecsPath then
|
||||
let
|
||||
value = import hostSpecsPath;
|
||||
in
|
||||
if builtins.isFunction value then
|
||||
value (
|
||||
builtins.intersectAttrs (builtins.functionArgs value) {
|
||||
inherit inputs lib;
|
||||
}
|
||||
)
|
||||
else
|
||||
value
|
||||
else
|
||||
{ };
|
||||
configurations = dotfilesLib.hosts.mkConfigurations hostSpecs;
|
||||
in
|
||||
{
|
||||
flake = {
|
||||
inherit (configurations) darwinConfigurations nixosConfigurations;
|
||||
lib = dotfilesLib;
|
||||
};
|
||||
|
||||
perSystem =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
checks.registry = import ../tests/registry.nix {
|
||||
inherit inputs lib pkgs;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
x1g9 = {
|
||||
system = "x86_64-linux";
|
||||
user = "moons";
|
||||
path = ./x1g9;
|
||||
|
||||
profiles = [
|
||||
"base"
|
||||
"interface.cli-minimal"
|
||||
"platform.laptop"
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{ }
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
imports = [
|
||||
./hardware.nix
|
||||
];
|
||||
|
||||
system.stateVersion = "26.05";
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
@@ -0,0 +1,17 @@
|
||||
{
|
||||
inputs,
|
||||
lib ? inputs.nixpkgs.lib,
|
||||
root,
|
||||
}:
|
||||
let
|
||||
registry = import ./registry.nix {
|
||||
inherit inputs lib;
|
||||
modulesRoot = root + "/modules";
|
||||
};
|
||||
hosts = import ./hosts.nix {
|
||||
inherit inputs lib registry;
|
||||
};
|
||||
in
|
||||
{
|
||||
inherit hosts registry;
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
{
|
||||
inputs,
|
||||
lib,
|
||||
registry,
|
||||
}:
|
||||
let
|
||||
ensure =
|
||||
condition: message: value:
|
||||
if condition then value else throw "host registry: ${message}";
|
||||
|
||||
isLinux = system: lib.hasSuffix "-linux" system;
|
||||
isDarwin = system: lib.hasSuffix "-darwin" system;
|
||||
|
||||
hostFile =
|
||||
spec: name:
|
||||
let
|
||||
path = spec.path + "/${name}";
|
||||
in
|
||||
if builtins.pathExists path then path else null;
|
||||
|
||||
selectedUnits =
|
||||
spec:
|
||||
[ "users.${spec.user}" ]
|
||||
++ map (name: "profiles.${name}") (spec.profiles or [ ])
|
||||
++ map (name: "applications.${name}") (spec.applications or [ ])
|
||||
++ (spec.units or [ ]);
|
||||
|
||||
validateSpec =
|
||||
name: spec:
|
||||
ensure (builtins.isAttrs spec) "${name}: host specification must be an attribute set" (
|
||||
ensure (spec ? system && builtins.isString spec.system) "${name}: system is required" (
|
||||
ensure (isLinux spec.system || isDarwin spec.system)
|
||||
"${name}: unsupported system '${spec.system}'; expected a Linux NixOS or Darwin system"
|
||||
(
|
||||
ensure (spec ? user && builtins.isString spec.user && spec.user != "") "${name}: user is required" (
|
||||
ensure (spec ? path && builtins.pathExists spec.path)
|
||||
"${name}: path must name an existing host directory"
|
||||
(
|
||||
ensure
|
||||
(lib.all
|
||||
(field: builtins.isList (spec.${field} or [ ]) && lib.all builtins.isString (spec.${field} or [ ]))
|
||||
[
|
||||
"profiles"
|
||||
"applications"
|
||||
"units"
|
||||
]
|
||||
)
|
||||
"${name}: profiles, applications, and units must be lists of strings"
|
||||
(ensure (builtins.isBool (spec.homeManager or true)) "${name}: homeManager must be a boolean" spec)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
mkSpecialArgs = name: spec: {
|
||||
inherit inputs registry;
|
||||
inherit (spec) system;
|
||||
hostName = name;
|
||||
primaryUser = spec.user;
|
||||
};
|
||||
|
||||
mkHomeManagerModule =
|
||||
name: spec: selected:
|
||||
let
|
||||
homePath = hostFile spec "home.nix";
|
||||
homeModules = [
|
||||
(registry.mkModule { class = "home"; })
|
||||
(registry.mkSelectionModule selected)
|
||||
]
|
||||
++ lib.optional (homePath != null) homePath;
|
||||
in
|
||||
{
|
||||
imports = [ inputs.home-manager.nixosModules.home-manager ];
|
||||
|
||||
home-manager = {
|
||||
useGlobalPkgs = true;
|
||||
useUserPackages = true;
|
||||
extraSpecialArgs = mkSpecialArgs name spec;
|
||||
users.${spec.user}.imports = homeModules;
|
||||
};
|
||||
};
|
||||
|
||||
mkDarwinHomeManagerModule =
|
||||
name: spec: selected:
|
||||
let
|
||||
homePath = hostFile spec "home.nix";
|
||||
homeModules = [
|
||||
(registry.mkModule { class = "home"; })
|
||||
(registry.mkSelectionModule selected)
|
||||
]
|
||||
++ lib.optional (homePath != null) homePath;
|
||||
in
|
||||
{
|
||||
imports = [ inputs.home-manager.darwinModules.home-manager ];
|
||||
|
||||
home-manager = {
|
||||
useGlobalPkgs = true;
|
||||
useUserPackages = true;
|
||||
extraSpecialArgs = mkSpecialArgs name spec;
|
||||
users.${spec.user}.imports = homeModules;
|
||||
};
|
||||
};
|
||||
|
||||
mkNixos =
|
||||
name: rawSpec:
|
||||
let
|
||||
spec = validateSpec name rawSpec;
|
||||
selected = registry.validateUnitIds (selectedUnits spec);
|
||||
nixosPath = hostFile spec "nixos.nix";
|
||||
modules = [
|
||||
(registry.mkModule { class = "nixos"; })
|
||||
(registry.mkSelectionModule selected)
|
||||
{ networking.hostName = lib.mkDefault name; }
|
||||
]
|
||||
++ lib.optional (spec.homeManager or true) (mkHomeManagerModule name spec selected)
|
||||
++ lib.optional (nixosPath != null) nixosPath;
|
||||
in
|
||||
inputs.nixpkgs.lib.nixosSystem {
|
||||
inherit (spec) system;
|
||||
specialArgs = mkSpecialArgs name spec;
|
||||
inherit modules;
|
||||
};
|
||||
|
||||
mkDarwin =
|
||||
name: rawSpec:
|
||||
let
|
||||
spec = validateSpec name rawSpec;
|
||||
selected = registry.validateUnitIds (selectedUnits spec);
|
||||
darwinPath = hostFile spec "darwin.nix";
|
||||
modules = [
|
||||
(registry.mkModule { class = "darwin"; })
|
||||
(registry.mkSelectionModule selected)
|
||||
{ networking.hostName = lib.mkDefault name; }
|
||||
]
|
||||
++ lib.optional (spec.homeManager or true) (mkDarwinHomeManagerModule name spec selected)
|
||||
++ lib.optional (darwinPath != null) darwinPath;
|
||||
in
|
||||
ensure (inputs ? nix-darwin) "${name}: the nix-darwin input is required" (
|
||||
inputs.nix-darwin.lib.darwinSystem {
|
||||
inherit (spec) system;
|
||||
specialArgs = mkSpecialArgs name spec;
|
||||
inherit modules;
|
||||
}
|
||||
);
|
||||
|
||||
mkConfigurations =
|
||||
hostSpecs:
|
||||
let
|
||||
validated = lib.mapAttrs validateSpec hostSpecs;
|
||||
in
|
||||
{
|
||||
nixosConfigurations = lib.mapAttrs mkNixos (
|
||||
lib.filterAttrs (_: spec: isLinux spec.system) validated
|
||||
);
|
||||
darwinConfigurations = lib.mapAttrs mkDarwin (
|
||||
lib.filterAttrs (_: spec: isDarwin spec.system) validated
|
||||
);
|
||||
};
|
||||
in
|
||||
{
|
||||
inherit
|
||||
mkConfigurations
|
||||
mkDarwin
|
||||
mkNixos
|
||||
selectedUnits
|
||||
;
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
{
|
||||
inputs,
|
||||
lib,
|
||||
modulesRoot,
|
||||
}:
|
||||
let
|
||||
reservedFiles = {
|
||||
common = "common.nix";
|
||||
nixos = "nixos.nix";
|
||||
darwin = "darwin.nix";
|
||||
home = "home.nix";
|
||||
meta = "meta.nix";
|
||||
};
|
||||
|
||||
isFile = kind: kind == "regular" || kind == "symlink";
|
||||
|
||||
ensure =
|
||||
condition: message: value:
|
||||
if condition then value else throw "unit registry: ${message}";
|
||||
|
||||
callWithAvailableArgs =
|
||||
value: availableArgs:
|
||||
if builtins.isFunction value then
|
||||
value (builtins.intersectAttrs (builtins.functionArgs value) availableArgs)
|
||||
else
|
||||
value;
|
||||
|
||||
pathFor =
|
||||
relativePath:
|
||||
if relativePath == [ ] then
|
||||
modulesRoot
|
||||
else
|
||||
modulesRoot + "/${lib.concatStringsSep "/" relativePath}";
|
||||
|
||||
entryIsFile = entries: name: builtins.hasAttr name entries && isFile entries.${name};
|
||||
|
||||
normalizeMeta =
|
||||
unit:
|
||||
let
|
||||
metaPath = unit.fragments.meta;
|
||||
importedValue =
|
||||
if metaPath == null then
|
||||
{ }
|
||||
else
|
||||
callWithAvailableArgs (import metaPath) {
|
||||
inherit inputs lib unit;
|
||||
};
|
||||
imported =
|
||||
ensure (builtins.isAttrs importedValue) "${unit.id}: meta.nix must return an attribute set"
|
||||
importedValue;
|
||||
allowedKeys = [
|
||||
"description"
|
||||
"includes"
|
||||
"imports"
|
||||
];
|
||||
unknownKeys = lib.filter (name: !(builtins.elem name allowedKeys)) (builtins.attrNames imported);
|
||||
description = imported.description or null;
|
||||
includes = imported.includes or [ ];
|
||||
imports = imported.imports or { };
|
||||
allowedImportKeys = [
|
||||
"nixos"
|
||||
"darwin"
|
||||
"home"
|
||||
];
|
||||
unknownImportKeys =
|
||||
if builtins.isAttrs imports then
|
||||
lib.filter (name: !(builtins.elem name allowedImportKeys)) (builtins.attrNames imports)
|
||||
else
|
||||
[ ];
|
||||
normalized = {
|
||||
inherit description includes;
|
||||
imports = {
|
||||
nixos = imports.nixos or [ ];
|
||||
darwin = imports.darwin or [ ];
|
||||
home = imports.home or [ ];
|
||||
};
|
||||
};
|
||||
in
|
||||
ensure (unknownKeys == [ ])
|
||||
"${unit.id}: meta.nix has unsupported keys: ${lib.concatStringsSep ", " unknownKeys}"
|
||||
(
|
||||
ensure (description == null || builtins.isString description)
|
||||
"${unit.id}: meta.description must be a string"
|
||||
(
|
||||
ensure (builtins.isList includes && lib.all builtins.isString includes)
|
||||
"${unit.id}: meta.includes must be a list of fully qualified unit IDs"
|
||||
(
|
||||
ensure (lib.unique includes == includes) "${unit.id}: meta.includes contains duplicate unit IDs" (
|
||||
ensure (builtins.isAttrs imports) "${unit.id}: meta.imports must be an attribute set" (
|
||||
ensure (unknownImportKeys == [ ])
|
||||
"${unit.id}: meta.imports has unsupported classes: ${lib.concatStringsSep ", " unknownImportKeys}"
|
||||
(
|
||||
ensure (lib.all builtins.isList [
|
||||
normalized.imports.nixos
|
||||
normalized.imports.darwin
|
||||
normalized.imports.home
|
||||
]) "${unit.id}: every meta.imports.<class> value must be a list" normalized
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
makeUnit =
|
||||
relativePath: entries:
|
||||
let
|
||||
directory = pathFor relativePath;
|
||||
id = lib.concatStringsSep "." relativePath;
|
||||
fragments = lib.mapAttrs (
|
||||
_class: fileName: if entryIsFile entries fileName then directory + "/${fileName}" else null
|
||||
) reservedFiles;
|
||||
baseUnit = {
|
||||
inherit
|
||||
id
|
||||
directory
|
||||
fragments
|
||||
relativePath
|
||||
;
|
||||
optionPath = [ "my" ] ++ relativePath ++ [ "enable" ];
|
||||
kind = builtins.head relativePath;
|
||||
name = lib.last relativePath;
|
||||
}
|
||||
//
|
||||
lib.optionalAttrs (builtins.length relativePath > 2 && builtins.head relativePath == "profiles")
|
||||
{
|
||||
group = builtins.elemAt relativePath 1;
|
||||
};
|
||||
in
|
||||
ensure (relativePath != [ ]) "the modules root cannot itself be a unit" (
|
||||
ensure (lib.all (component: component != "" && !(lib.hasInfix "." component)) relativePath)
|
||||
"${id}: path components must be non-empty and must not contain dots"
|
||||
(baseUnit // { meta = normalizeMeta baseUnit; })
|
||||
);
|
||||
|
||||
walk =
|
||||
relativePath:
|
||||
let
|
||||
directory = pathFor relativePath;
|
||||
entries = builtins.readDir directory;
|
||||
hasReservedFile = lib.any (fileName: entryIsFile entries fileName) (
|
||||
builtins.attrValues reservedFiles
|
||||
);
|
||||
childDirectories = lib.filter (name: entries.${name} == "directory") (builtins.attrNames entries);
|
||||
current = lib.optional hasReservedFile (makeUnit relativePath entries);
|
||||
children = lib.concatMap (name: walk (relativePath ++ [ name ])) childDirectories;
|
||||
in
|
||||
current ++ children;
|
||||
|
||||
discoveredUnits =
|
||||
ensure (builtins.pathExists modulesRoot) "modules root does not exist: ${toString modulesRoot}"
|
||||
(walk [ ]);
|
||||
unitsById = builtins.listToAttrs (map (unit: lib.nameValuePair unit.id unit) discoveredUnits);
|
||||
|
||||
dependencyValidation = lib.foldl' (
|
||||
valid: unit:
|
||||
lib.foldl' (
|
||||
inner: includedId:
|
||||
if builtins.hasAttr includedId unitsById then
|
||||
inner
|
||||
else
|
||||
throw "unit registry: ${unit.id} includes missing unit '${includedId}'"
|
||||
) valid unit.meta.includes
|
||||
) true discoveredUnits;
|
||||
|
||||
units = builtins.seq dependencyValidation unitsById;
|
||||
unitIds = builtins.attrNames units;
|
||||
|
||||
getUnit =
|
||||
id: if builtins.hasAttr id units then units.${id} else throw "unit registry: unknown unit '${id}'";
|
||||
|
||||
validateUnitIds =
|
||||
ids:
|
||||
ensure (
|
||||
builtins.isList ids && lib.all builtins.isString ids
|
||||
) "selected units must be a list of strings" (map (id: builtins.seq (getUnit id) id) ids);
|
||||
|
||||
optionDefinitions = lib.foldl' lib.recursiveUpdate { } (
|
||||
map (
|
||||
unit:
|
||||
lib.setAttrByPath unit.optionPath (
|
||||
lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description =
|
||||
if unit.meta.description == null then
|
||||
"Whether to enable the ${unit.id} unit."
|
||||
else
|
||||
"Whether to enable ${unit.meta.description}.";
|
||||
}
|
||||
)
|
||||
) discoveredUnits
|
||||
);
|
||||
|
||||
enabled = config: unit: lib.getAttrFromPath unit.optionPath config;
|
||||
|
||||
enableUnit = id: lib.setAttrByPath (getUnit id).optionPath true;
|
||||
|
||||
includeConfig =
|
||||
config: unit: lib.mkIf (enabled config unit) (lib.mkMerge (map enableUnit unit.meta.includes));
|
||||
|
||||
fragmentClasses = {
|
||||
nixos = [
|
||||
"common"
|
||||
"nixos"
|
||||
];
|
||||
darwin = [
|
||||
"common"
|
||||
"darwin"
|
||||
];
|
||||
home = [ "home" ];
|
||||
};
|
||||
|
||||
applyFragment =
|
||||
{
|
||||
config,
|
||||
fragmentPath,
|
||||
options,
|
||||
specialArgs,
|
||||
unit,
|
||||
}:
|
||||
let
|
||||
fragment = import fragmentPath;
|
||||
directArgs = specialArgs // {
|
||||
inherit
|
||||
config
|
||||
lib
|
||||
options
|
||||
specialArgs
|
||||
unit
|
||||
;
|
||||
};
|
||||
fragmentArgSpec = builtins.functionArgs fragment;
|
||||
fragmentArgs = builtins.listToAttrs (
|
||||
lib.concatMap (
|
||||
name:
|
||||
if builtins.hasAttr name directArgs then
|
||||
[ (lib.nameValuePair name directArgs.${name}) ]
|
||||
else if fragmentArgSpec.${name} then
|
||||
[ ]
|
||||
else
|
||||
[ (lib.nameValuePair name config._module.args.${name}) ]
|
||||
) (builtins.attrNames fragmentArgSpec)
|
||||
);
|
||||
resultValue = if builtins.isFunction fragment then fragment fragmentArgs else fragment;
|
||||
result =
|
||||
ensure (builtins.isAttrs resultValue)
|
||||
"${unit.id}: ${builtins.baseNameOf fragmentPath} must return an attribute set"
|
||||
resultValue;
|
||||
forbiddenKeys = lib.filter (name: builtins.hasAttr name result) [
|
||||
"imports"
|
||||
"options"
|
||||
"config"
|
||||
];
|
||||
in
|
||||
ensure (forbiddenKeys == [ ])
|
||||
"${unit.id}: ${builtins.baseNameOf fragmentPath} is a configuration fragment and cannot define top-level ${lib.concatStringsSep ", " forbiddenKeys}"
|
||||
result;
|
||||
|
||||
externalImports = class: lib.concatMap (unit: unit.meta.imports.${class}) discoveredUnits;
|
||||
|
||||
mkModule =
|
||||
{ class }:
|
||||
ensure (builtins.hasAttr class fragmentClasses) "unsupported module class '${class}'" (
|
||||
builtins.seq dependencyValidation (
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
options,
|
||||
specialArgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
fragmentConfigs = lib.concatMap (
|
||||
unit:
|
||||
lib.filter (value: value != null) (
|
||||
map (
|
||||
fragmentClass:
|
||||
let
|
||||
fragmentPath = unit.fragments.${fragmentClass};
|
||||
in
|
||||
if fragmentPath == null then
|
||||
null
|
||||
else
|
||||
lib.mkIf (enabled config unit) (applyFragment {
|
||||
inherit
|
||||
config
|
||||
fragmentPath
|
||||
options
|
||||
specialArgs
|
||||
unit
|
||||
;
|
||||
})
|
||||
) fragmentClasses.${class}
|
||||
)
|
||||
) discoveredUnits;
|
||||
in
|
||||
{
|
||||
imports = externalImports class;
|
||||
options = optionDefinitions;
|
||||
config = lib.mkMerge ((map (includeConfig config) discoveredUnits) ++ fragmentConfigs);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
mkSelectionModule =
|
||||
selectedIds:
|
||||
let
|
||||
checkedIds = validateUnitIds (lib.unique selectedIds);
|
||||
in
|
||||
{
|
||||
config = lib.mkMerge (map enableUnit checkedIds);
|
||||
};
|
||||
in
|
||||
{
|
||||
inherit
|
||||
getUnit
|
||||
mkModule
|
||||
mkSelectionModule
|
||||
unitIds
|
||||
units
|
||||
validateUnitIds
|
||||
;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
# Bashtop theme with Nord palette (https://www.nordtheme.com)
|
||||
# by Justin Zobel <[email protected]>
|
||||
|
||||
# Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: "#RRGGBB", "#BW" or "0-255 0-255 0-255"
|
||||
# example for white: "#ffffff", "#ff" or "255 255 255".
|
||||
|
||||
# All graphs and meters can be gradients
|
||||
# For single color graphs leave "mid" and "end" variable empty.
|
||||
# Use "start" and "end" variables for two color gradient
|
||||
# Use "start", "mid" and "end" for three color gradient
|
||||
|
||||
# Main background, empty for terminal default, need to be empty if you want transparent background
|
||||
theme[main_bg]="#282A36"
|
||||
|
||||
# Main text color
|
||||
theme[main_fg]="#BD93F9"
|
||||
|
||||
# Title color for boxes
|
||||
theme[title]="#f8f8f2"
|
||||
|
||||
# Highlight color for keyboard shortcuts
|
||||
theme[hi_fg]="#ff79c6"
|
||||
|
||||
# Background color of selected item in processes box
|
||||
theme[selected_bg]="#44475A"
|
||||
|
||||
# Foreground color of selected item in processes box
|
||||
theme[selected_fg]="#ECEFF4"
|
||||
|
||||
# Color of inactive/disabled text
|
||||
theme[inactive_fg]="#6272a4"
|
||||
|
||||
# Misc colors for processes box including mini cpu graphs, details memory graph and details status text
|
||||
theme[proc_misc]="#BD93F9"
|
||||
|
||||
# Cpu box outline color
|
||||
theme[cpu_box]="#ff79c6"
|
||||
|
||||
# Memory/disks box outline color
|
||||
theme[mem_box]="#ff79c6"
|
||||
|
||||
# Net up/down box outline color
|
||||
theme[net_box]="#ff79c6"
|
||||
|
||||
# Processes box outline color
|
||||
theme[proc_box]="#ff79c6"
|
||||
|
||||
# Box divider line and small boxes line color
|
||||
theme[div_line]="#ff79c6"
|
||||
|
||||
# Temperature graph colors
|
||||
theme[temp_start]="#bd93f9"
|
||||
theme[temp_mid]="#bd93f9"
|
||||
theme[temp_end]="#bd93f9"
|
||||
|
||||
# CPU graph colors
|
||||
theme[cpu_start]="#bd93f9"
|
||||
theme[cpu_mid]="#bd93f9"
|
||||
theme[cpu_end]="#bd93f9"
|
||||
|
||||
# Mem/Disk free meter
|
||||
theme[free_start]="#bd93f9"
|
||||
theme[free_mid]="#bd93f9"
|
||||
theme[free_end]="#bd93f9"
|
||||
|
||||
# Mem/Disk cached meter
|
||||
theme[cached_start]="#bd93f9"
|
||||
theme[cached_mid]="#bd93f9"
|
||||
theme[cached_end]="#bd93f9"
|
||||
|
||||
# Mem/Disk available meter
|
||||
theme[available_start]="#bd93f9"
|
||||
theme[available_mid]="#bd93f9"
|
||||
theme[available_end]="#bd93f9"
|
||||
|
||||
# Mem/Disk used meter
|
||||
theme[used_start]="#bd93f9"
|
||||
theme[used_mid]="#bd93f9"
|
||||
theme[used_end]="#bd93f9"
|
||||
|
||||
# Download graph colors
|
||||
theme[download_start]="#bd93f9"
|
||||
theme[download_mid]="#bd93f9"
|
||||
theme[download_end]="#bd93f9"
|
||||
|
||||
# Upload graph colors
|
||||
theme[upload_start]="#bd93f9"
|
||||
theme[upload_mid]="#bd93f9"
|
||||
theme[upload_end]="#bd93f9"
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
programs.btop = {
|
||||
enable = true;
|
||||
|
||||
settings.color_theme = "dracula";
|
||||
themes.dracula = builtins.readFile ./dracula.theme;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
inputs,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
{
|
||||
home.packages = [
|
||||
inputs.llm-agents.packages.${pkgs.stdenv.hostPlatform.system}.claude-code
|
||||
];
|
||||
|
||||
home.file.".claude/settings.json".text = builtins.toJSON {
|
||||
statusLine = {
|
||||
type = "command";
|
||||
command = "bun x ccusage statusline --no-offline";
|
||||
padding = 0;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
[Hotkey]
|
||||
# トリガーキーを押すたびに切り替える
|
||||
EnumerateWithTriggerKeys=False
|
||||
# 一時的に第1入力メソッドに切り替える
|
||||
AltTriggerKeys=
|
||||
# 切り替え時は第1入力メソッドをスキップする
|
||||
EnumerateSkipFirst=False
|
||||
# Time limit in milliseconds for triggering modifier key shortcuts
|
||||
ModifierOnlyKeyTimeout=250
|
||||
|
||||
[Hotkey/TriggerKeys]
|
||||
1=Zenkaku_Hankaku
|
||||
|
||||
[Hotkey/ActivateKeys]
|
||||
0=Henkan
|
||||
|
||||
[Hotkey/DeactivateKeys]
|
||||
0=Muhenkan
|
||||
|
||||
[Hotkey/PrevPage]
|
||||
0=Up
|
||||
|
||||
[Hotkey/NextPage]
|
||||
0=Down
|
||||
|
||||
[Hotkey/PrevCandidate]
|
||||
0=Shift+Tab
|
||||
|
||||
[Hotkey/NextCandidate]
|
||||
0=Tab
|
||||
|
||||
[Hotkey/TogglePreedit]
|
||||
0=Control+Alt+P
|
||||
|
||||
[Behavior]
|
||||
# デフォルトで有効にする
|
||||
ActiveByDefault=False
|
||||
# フォーカス時に状態をリセット
|
||||
resetStateWhenFocusIn=No
|
||||
# 入力状態を共有する
|
||||
ShareInputState=No
|
||||
# アプリケーションにプリエディットを表示する
|
||||
PreeditEnabledByDefault=True
|
||||
# 入力メソッドを切り替える際に入力メソッドの情報を表示する
|
||||
ShowInputMethodInformation=True
|
||||
# フォーカスを変更する際に入力メソッドの情報を表示する
|
||||
showInputMethodInformationWhenFocusIn=False
|
||||
# 入力メソッドの情報をコンパクトに表示する
|
||||
CompactInputMethodInformation=True
|
||||
# 第1入力メソッドの情報を表示する
|
||||
ShowFirstInputMethodInformation=True
|
||||
# デフォルトのページサイズ
|
||||
DefaultPageSize=5
|
||||
# XKB オプションより優先する
|
||||
OverrideXkbOption=False
|
||||
# カスタム XKB オプション
|
||||
CustomXkbOption=
|
||||
# Force Enabled Addons
|
||||
EnabledAddons=
|
||||
# Force Disabled Addons
|
||||
DisabledAddons=
|
||||
# Preload input method to be used by default
|
||||
PreloadInputMethod=True
|
||||
# パスワード欄に入力メソッドを許可する
|
||||
AllowInputMethodForPassword=False
|
||||
# パスワード入力時にプリエディットテキストを表示する
|
||||
ShowPreeditForPassword=False
|
||||
# ユーザーデータを保存する間隔(分)
|
||||
AutoSavePeriod=30
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
description = "base system configuration";
|
||||
|
||||
includes = [
|
||||
"systems.locale"
|
||||
"systems.networking"
|
||||
"systems.nix"
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
description = "minimal command-line environment";
|
||||
|
||||
includes = [
|
||||
"applications.btop"
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
description = "laptop platform configuration";
|
||||
|
||||
includes = [
|
||||
"systems.boot.uefi"
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{ lib, ... }:
|
||||
{
|
||||
boot.loader = {
|
||||
systemd-boot.enable = lib.mkDefault true;
|
||||
systemd-boot.configurationLimit = lib.mkDefault 8;
|
||||
efi.canTouchEfiVariables = lib.mkDefault true;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
time.timeZone = "Asia/Tokyo";
|
||||
|
||||
i18n = {
|
||||
defaultLocale = "ja_JP.UTF-8";
|
||||
extraLocaleSettings = {
|
||||
LC_ADDRESS = "ja_JP.UTF-8";
|
||||
LC_IDENTIFICATION = "ja_JP.UTF-8";
|
||||
LC_MEASUREMENT = "ja_JP.UTF-8";
|
||||
LC_MONETARY = "ja_JP.UTF-8";
|
||||
LC_NAME = "ja_JP.UTF-8";
|
||||
LC_NUMERIC = "ja_JP.UTF-8";
|
||||
LC_PAPER = "ja_JP.UTF-8";
|
||||
LC_TELEPHONE = "ja_JP.UTF-8";
|
||||
LC_TIME = "ja_JP.UTF-8";
|
||||
};
|
||||
};
|
||||
|
||||
console.keyMap = "jp106";
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
networking.networkmanager.enable = true;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
nix.settings.experimental-features = [
|
||||
"nix-command"
|
||||
"flakes"
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{ primaryUser, ... }:
|
||||
{
|
||||
home = {
|
||||
username = primaryUser;
|
||||
homeDirectory = "/home/${primaryUser}";
|
||||
stateVersion = "26.05";
|
||||
};
|
||||
|
||||
programs.home-manager.enable = true;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{ primaryUser, ... }:
|
||||
{
|
||||
users.users.${primaryUser} = {
|
||||
isNormalUser = true;
|
||||
description = "moons";
|
||||
extraGroups = [
|
||||
"networkmanager"
|
||||
"wheel"
|
||||
];
|
||||
};
|
||||
|
||||
nix.settings = {
|
||||
allowed-users = [ primaryUser ];
|
||||
trusted-users = [
|
||||
"root"
|
||||
primaryUser
|
||||
];
|
||||
};
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
includes = [ "applications.missing" ];
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
imports = [ ];
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
system.stateVersion = 6;
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
fileSystems."/" = {
|
||||
device = "/dev/null";
|
||||
fsType = "ext4";
|
||||
};
|
||||
|
||||
boot.loader.grub.enable = false;
|
||||
system.stateVersion = "26.05";
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
marker ? "host",
|
||||
...
|
||||
}:
|
||||
{
|
||||
test.systemValues = [ "common:${marker}" ];
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
test.systemValues = [ "darwin" ];
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
test.homeValues = [ "home" ];
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{ lib, ... }:
|
||||
{
|
||||
description = "alpha test application";
|
||||
|
||||
includes = [ "services.nested" ];
|
||||
|
||||
imports.nixos = [
|
||||
{
|
||||
options.test = {
|
||||
external = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "external-default";
|
||||
};
|
||||
systemValues = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
|
||||
imports.darwin = [
|
||||
{
|
||||
options.test.systemValues = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
};
|
||||
}
|
||||
];
|
||||
|
||||
imports.home = [
|
||||
{
|
||||
options.test.homeValues = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
};
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
test.systemValues = [ "nixos" ];
|
||||
test.external = "set-by-nixos-fragment";
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
test.homeValues = [ "beta-home" ];
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
thisFileMustNotBeDiscovered = true;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
includes = [
|
||||
"applications.alpha"
|
||||
"applications.beta"
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{ lib, ... }:
|
||||
{
|
||||
imports.nixos = [
|
||||
{
|
||||
options.test.nested = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
};
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
test.nested = true;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
home = {
|
||||
username = "test";
|
||||
homeDirectory = if pkgs.stdenv.hostPlatform.isDarwin then "/Users/test" else "/home/test";
|
||||
stateVersion = "26.05";
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
{
|
||||
inputs,
|
||||
lib,
|
||||
pkgs,
|
||||
}:
|
||||
let
|
||||
registry = import ../libs/registry.nix {
|
||||
inherit inputs lib;
|
||||
modulesRoot = ./fixtures/registry/modules;
|
||||
};
|
||||
|
||||
hostLib = import ../libs/hosts.nix {
|
||||
inherit inputs lib registry;
|
||||
};
|
||||
|
||||
baseModule = {
|
||||
options = {
|
||||
home = {
|
||||
username = lib.mkOption { type = lib.types.str; };
|
||||
homeDirectory = lib.mkOption { type = lib.types.str; };
|
||||
stateVersion = lib.mkOption { type = lib.types.str; };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
eval =
|
||||
class: selected:
|
||||
lib.evalModules {
|
||||
specialArgs = {
|
||||
inherit pkgs;
|
||||
marker = "special-arg";
|
||||
};
|
||||
modules = [
|
||||
baseModule
|
||||
(registry.mkModule { inherit class; })
|
||||
(registry.mkSelectionModule selected)
|
||||
];
|
||||
};
|
||||
|
||||
nixos = eval "nixos" [ "profiles.interface.test" ];
|
||||
darwin = eval "darwin" [ "applications.alpha" ];
|
||||
home = eval "home" [ "profiles.interface.test" ];
|
||||
|
||||
nixosHost = hostLib.mkNixos "registry-test" {
|
||||
system = "x86_64-linux";
|
||||
user = "test";
|
||||
path = ./fixtures/registry/hosts;
|
||||
profiles = [ "interface.test" ];
|
||||
};
|
||||
|
||||
darwinHost = hostLib.mkDarwin "registry-test-darwin" {
|
||||
system = "aarch64-darwin";
|
||||
user = "test";
|
||||
path = ./fixtures/registry/hosts;
|
||||
applications = [ "alpha" ];
|
||||
};
|
||||
|
||||
missingUnit = builtins.tryEval (
|
||||
builtins.deepSeq (registry.validateUnitIds [ "applications.missing" ]) true
|
||||
);
|
||||
|
||||
invalidDependencyRegistry = import ../libs/registry.nix {
|
||||
inherit inputs lib;
|
||||
modulesRoot = ./fixtures/registry-invalid-dependency/modules;
|
||||
};
|
||||
missingDependency = builtins.tryEval (builtins.deepSeq invalidDependencyRegistry.unitIds true);
|
||||
|
||||
invalidFragmentRegistry = import ../libs/registry.nix {
|
||||
inherit inputs lib;
|
||||
modulesRoot = ./fixtures/registry-invalid-fragment/modules;
|
||||
};
|
||||
invalidFragmentEvaluation = lib.evalModules {
|
||||
modules = [
|
||||
(invalidFragmentRegistry.mkModule { class = "home"; })
|
||||
(invalidFragmentRegistry.mkSelectionModule [ "applications.broken" ])
|
||||
];
|
||||
};
|
||||
invalidFragment = builtins.tryEval (builtins.deepSeq invalidFragmentEvaluation.config true);
|
||||
|
||||
assertions =
|
||||
assert
|
||||
registry.unitIds == [
|
||||
"applications.alpha"
|
||||
"applications.beta"
|
||||
"profiles.interface.test"
|
||||
"services.nested"
|
||||
"users.test"
|
||||
];
|
||||
assert !(builtins.elem "namespace" registry.unitIds);
|
||||
assert nixos.config.my.profiles.interface.test.enable;
|
||||
assert nixos.config.my.applications.alpha.enable;
|
||||
assert nixos.config.my.applications.beta.enable;
|
||||
assert nixos.config.my.services.nested.enable;
|
||||
assert nixos.config.test.nested;
|
||||
assert
|
||||
nixos.config.test.systemValues == [
|
||||
"common:special-arg"
|
||||
"nixos"
|
||||
];
|
||||
assert nixos.config.test.external == "set-by-nixos-fragment";
|
||||
assert
|
||||
darwin.config.test.systemValues == [
|
||||
"common:special-arg"
|
||||
"darwin"
|
||||
];
|
||||
assert
|
||||
home.config.test.homeValues == [
|
||||
"home"
|
||||
"beta-home"
|
||||
];
|
||||
assert nixosHost.config.my.profiles.interface.test.enable;
|
||||
assert nixosHost.config.home-manager.users.test.my.applications.alpha.enable;
|
||||
assert
|
||||
nixosHost.config.home-manager.users.test.test.homeValues == [
|
||||
"home"
|
||||
"beta-home"
|
||||
];
|
||||
assert darwinHost.config.my.applications.alpha.enable;
|
||||
assert darwinHost.config.home-manager.users.test.my.applications.alpha.enable;
|
||||
assert darwinHost.config.home-manager.users.test.test.homeValues == [ "home" ];
|
||||
assert
|
||||
darwinHost.config.test.systemValues == [
|
||||
"common:host"
|
||||
"darwin"
|
||||
];
|
||||
assert !missingUnit.success;
|
||||
assert !missingDependency.success;
|
||||
assert !invalidFragment.success;
|
||||
true;
|
||||
in
|
||||
assert assertions;
|
||||
pkgs.runCommand "unit-registry-evaluation-tests" { } ''
|
||||
touch "$out"
|
||||
''
|
||||
Reference in New Issue
Block a user