init templates
This commit is contained in:
parent
c41685983f
commit
93bff3aade
7 changed files with 345 additions and 0 deletions
24
README.md
24
README.md
|
|
@ -33,3 +33,27 @@ boilerplate feature my-feature
|
||||||
boilerplate layer my-layer
|
boilerplate layer my-layer
|
||||||
boilerplate package my-package
|
boilerplate package my-package
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Templates
|
||||||
|
|
||||||
|
I've found myself copying a lot of nix files from one project to the next. I'd like to reduce the
|
||||||
|
amount of time I spend doing this, so I'm just going to be making reusable nix flake templates.
|
||||||
|
These are defined in the `templates` directory and exposed in `modules/templates.nix`. They're
|
||||||
|
separated like that for both cleanliness and to avoid funkiness with `import-tree`.
|
||||||
|
|
||||||
|
You can use my templates like so (using my generic flake for example):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
nix flake init -t github:jackjohn7/nixconf#generic
|
||||||
|
```
|
||||||
|
|
||||||
|
### Generic
|
||||||
|
|
||||||
|
My _generic_ template is just a basic flake following the
|
||||||
|
[dendritic pattern](https://dendrix.oeiuwq.com/Dendritic.html) based on this repository itself.
|
||||||
|
I have also included the `boilerplate` package so that boilerplate for basic pieces can just be
|
||||||
|
generated instead of copied, pasted, and cleaned up.
|
||||||
|
|
||||||
|
If you find any bugs or want to enhance it, feel free to open a PR. I can't promise I'll accept
|
||||||
|
your change since I use it myself but you'll still have your own fork at the end of the day for
|
||||||
|
your own consumption.
|
||||||
|
|
|
||||||
9
modules/templates.nix
Normal file
9
modules/templates.nix
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
flake.templates = {
|
||||||
|
generic = {
|
||||||
|
path = ../templates/generic;
|
||||||
|
description = "A generic dendritic flake using patterns I enjoy";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
11
templates/generic/flake.nix
Normal file
11
templates/generic/flake.nix
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
|
|
||||||
|
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||||
|
import-tree.url = "github:vic/import-tree";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs =
|
||||||
|
inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } (inputs.import-tree ./nix-modules);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,233 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import textwrap
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
KIND_LAYOUTS = {
|
||||||
|
"layer": Path("nix-modules/features/layers"),
|
||||||
|
"feature": Path("nix-modules/features"),
|
||||||
|
"host": Path("nix-modules/hosts"),
|
||||||
|
"package": Path("nix-modules/packages"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
NAME_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.-]*$")
|
||||||
|
|
||||||
|
|
||||||
|
def nix_string(value: str) -> str:
|
||||||
|
return json.dumps(value)
|
||||||
|
|
||||||
|
|
||||||
|
def git_root() -> Path | None:
|
||||||
|
result = subprocess.run(
|
||||||
|
["git", "rev-parse", "--show-toplevel"],
|
||||||
|
check=False,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
return None
|
||||||
|
return Path(result.stdout.strip())
|
||||||
|
|
||||||
|
|
||||||
|
def host_templates(name: str) -> dict[Path, str]:
|
||||||
|
return {
|
||||||
|
Path("default.nix"):
|
||||||
|
textwrap.dedent(
|
||||||
|
f"""\
|
||||||
|
{{ self, inputs, ... }}:
|
||||||
|
{{
|
||||||
|
flake.nixosConfigurations.{name} = inputs.nixpkgs.lib.nixosSystem {{
|
||||||
|
modules = [
|
||||||
|
self.nixosModules.{name}Configuration
|
||||||
|
];
|
||||||
|
}};
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
Path("configuration.nix"):
|
||||||
|
textwrap.dedent(
|
||||||
|
f"""\
|
||||||
|
{{ self, inputs, ... }}:
|
||||||
|
{{
|
||||||
|
flake.nixosModules.{name}Configuration =
|
||||||
|
{{ pkgs, lib, ... }}:
|
||||||
|
{{
|
||||||
|
imports = [
|
||||||
|
self.nixosModules.{name}Hardware
|
||||||
|
];
|
||||||
|
}};
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
Path("hardware-configuration.nix"):
|
||||||
|
textwrap.dedent(
|
||||||
|
f"""\
|
||||||
|
{{ self, inputs, ... }}:
|
||||||
|
{{
|
||||||
|
flake.nixosModules.{name}Hardware =
|
||||||
|
{{ lib, pkgs, ... }}:
|
||||||
|
{{
|
||||||
|
config = {{
|
||||||
|
# Copy the generated hardware configuration here.
|
||||||
|
}};
|
||||||
|
}};
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def template(kind: str, name: str) -> str:
|
||||||
|
if kind in {"layer", "feature"}:
|
||||||
|
return textwrap.dedent(
|
||||||
|
f"""\
|
||||||
|
{{ self, inputs, ... }}:
|
||||||
|
{{
|
||||||
|
flake.nixosModules.{name} = {{ pkgs, lib, ... }}:
|
||||||
|
{{
|
||||||
|
config = {{
|
||||||
|
}};
|
||||||
|
}};
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
if kind == "host":
|
||||||
|
quoted_name = nix_string(name)
|
||||||
|
return textwrap.dedent(
|
||||||
|
f"""\
|
||||||
|
{{ self, inputs, ... }}:
|
||||||
|
{{
|
||||||
|
flake.nixosModules.{name} = {{ pkgs, lib, ... }}:
|
||||||
|
{{
|
||||||
|
config = {{
|
||||||
|
networking.hostName = {quoted_name};
|
||||||
|
# system.stateVersion = "25.11";
|
||||||
|
}};
|
||||||
|
}};
|
||||||
|
|
||||||
|
flake.nixosConfigurations.{name} = inputs.nixpkgs.lib.nixosSystem {{
|
||||||
|
modules = [
|
||||||
|
self.nixosModules.{name}
|
||||||
|
];
|
||||||
|
}};
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
if kind == "package":
|
||||||
|
quoted_name = nix_string(name)
|
||||||
|
return textwrap.dedent(
|
||||||
|
f"""\
|
||||||
|
{{ self, inputs, ... }}:
|
||||||
|
{{
|
||||||
|
perSystem =
|
||||||
|
{{ pkgs, ... }}:
|
||||||
|
{{
|
||||||
|
packages.{name} = pkgs.writeShellApplication {{
|
||||||
|
name = {quoted_name};
|
||||||
|
runtimeInputs = [ ];
|
||||||
|
text = ''
|
||||||
|
echo "TODO: implement {name}"
|
||||||
|
'';
|
||||||
|
}};
|
||||||
|
}};
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
raise ValueError(f"Unsupported kind: {kind}")
|
||||||
|
|
||||||
|
|
||||||
|
def planned_files(kind: str, name: str, multifile: bool) -> dict[Path, str]:
|
||||||
|
if kind == "host":
|
||||||
|
return host_templates(name)
|
||||||
|
|
||||||
|
target = Path("default.nix") if multifile else Path(f"{name}.nix")
|
||||||
|
return {target: template(kind, name)}
|
||||||
|
|
||||||
|
|
||||||
|
def write_file(path: Path, content: str) -> None:
|
||||||
|
if path.exists():
|
||||||
|
raise FileExistsError(f"Refusing to overwrite existing path: {path}")
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
path.write_text(content.rstrip() + "\n", encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def stage(paths: list[Path], root: Path) -> None:
|
||||||
|
subprocess.run(
|
||||||
|
["git", "add", "--", *[str(path.relative_to(root)) for path in paths]],
|
||||||
|
check=True,
|
||||||
|
cwd=root,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(description="Generate Nix boilerplate modules")
|
||||||
|
parser.add_argument("kind", choices=sorted(KIND_LAYOUTS))
|
||||||
|
parser.add_argument("name")
|
||||||
|
parser.add_argument("--no-git-stage", action="store_true", help="Do not stage created files")
|
||||||
|
parser.add_argument("--dry-run", action="store_true", help="Print planned files without creating them")
|
||||||
|
parser.add_argument(
|
||||||
|
"--multifile",
|
||||||
|
action="store_true",
|
||||||
|
help="Create <name>/default.nix instead of <name>.nix",
|
||||||
|
)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
args = parse_args()
|
||||||
|
|
||||||
|
if not NAME_RE.fullmatch(args.name):
|
||||||
|
print(
|
||||||
|
"name must match ^[A-Za-z0-9][A-Za-z0-9_.-]*$",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
root = git_root()
|
||||||
|
if root is None:
|
||||||
|
if args.dry_run or args.no_git_stage:
|
||||||
|
root = Path.cwd()
|
||||||
|
else:
|
||||||
|
print("not inside a git repository; use --no-git-stage", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
files = planned_files(args.kind, args.name, args.multifile)
|
||||||
|
base = root / KIND_LAYOUTS[args.kind]
|
||||||
|
target_base = base / args.name if args.kind == "host" else base
|
||||||
|
targets = [target_base / rel for rel in files]
|
||||||
|
|
||||||
|
if args.dry_run:
|
||||||
|
print("dry run: no files will be created")
|
||||||
|
for target in targets:
|
||||||
|
print(target.relative_to(root))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
for rel_path, content in files.items():
|
||||||
|
write_file(target_base / rel_path, content)
|
||||||
|
except FileExistsError as err:
|
||||||
|
print(err, file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if not args.no_git_stage:
|
||||||
|
stage(targets, root)
|
||||||
|
|
||||||
|
for target in targets:
|
||||||
|
print(target.relative_to(root))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
{ self, inputs, ... }:
|
||||||
|
{
|
||||||
|
perSystem =
|
||||||
|
{ pkgs, lib, ... }:
|
||||||
|
{
|
||||||
|
packages.boilerplate = pkgs.stdenvNoCC.mkDerivation {
|
||||||
|
pname = "boilerplate";
|
||||||
|
version = "0.1.0";
|
||||||
|
|
||||||
|
src = ./.;
|
||||||
|
|
||||||
|
dontConfigure = true;
|
||||||
|
dontBuild = true;
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
|
||||||
|
substituteInPlace boilerplate.py \
|
||||||
|
--replace-fail '#!/usr/bin/env python3' '#!${pkgs.python3}/bin/python3'
|
||||||
|
install -Dm755 boilerplate.py "$out/bin/boilerplate"
|
||||||
|
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "Generate boilerplate Nix modules";
|
||||||
|
license = lib.licenses.mit;
|
||||||
|
mainProgram = "boilerplate";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
10
templates/generic/nix-modules/parts.nix
Normal file
10
templates/generic/nix-modules/parts.nix
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
systems = [
|
||||||
|
"x86_64-linux"
|
||||||
|
"x86_64-darwin"
|
||||||
|
"aarch64-linux"
|
||||||
|
"aarch64-darwin"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
26
templates/generic/nix-modules/shell.nix
Normal file
26
templates/generic/nix-modules/shell.nix
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
perSystem =
|
||||||
|
{ pkgs, self', ... }:
|
||||||
|
{
|
||||||
|
# An example devshell with some Rust and Nix tools
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
bacon
|
||||||
|
cargo
|
||||||
|
rust-analyzer
|
||||||
|
rustc
|
||||||
|
rustfmt
|
||||||
|
clippy
|
||||||
|
glibc
|
||||||
|
sea-orm-cli
|
||||||
|
nixfmt
|
||||||
|
nil
|
||||||
|
alejandra
|
||||||
|
self'.packages.boilerplate
|
||||||
|
];
|
||||||
|
nativeBuildInputs = [ pkgs.pkg-config ];
|
||||||
|
env.RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue