Files
nixpkgs/docs/reference/patterns.md

499 lines
7.7 KiB
Markdown
Raw Normal View History

# Code Patterns and Anti-Patterns
Common code patterns and anti-patterns used in m3ta-nixpkgs.
## Overview
This document outlines recommended patterns and common pitfalls when working with m3ta-nixpkgs.
## Code Patterns
### Package Pattern
#### CallPackage Registry
Use `callPackage` for lazy evaluation:
```nix
# Good - pkgs/default.nix
{
inherit (pkgs) callPackage;
} rec {
code2prompt = callPackage ./code2prompt {};
mem0 = callPackage ./mem0 {};
}
```
#### Meta Fields
Always include complete `meta` information:
```nix
# Good
meta = with lib; {
description = "My awesome package";
homepage = "https://github.com/author/package";
changelog = "https://github.com/author/package/releases/tag/v${version}";
license = licenses.mit;
platforms = platforms.linux;
mainProgram = "program-name";
};
```
### Module Pattern
#### Standard Module Structure
```nix
# Good
{ config, lib, pkgs, ... }:
with lib; let
cfg = config.m3ta.myModule;
in {
options.m3ta.myModule = {
enable = mkEnableOption "my module";
# ... options
};
config = mkIf cfg.enable {
# ... configuration
};
}
```
#### mkEnableOption
Always use `mkEnableOption` for enable flags:
```nix
# Good
options.m3ta.myModule = {
enable = mkEnableOption "my module";
};
# Bad
options.m3ta.myModule.enable = mkOption {
type = types.bool;
default = false;
};
```
#### Conditional Configuration
Use `mkIf` for conditional config:
```nix
# Good
config = mkIf cfg.enable {
services.my-service.enable = true;
};
```
#### Multiple Conditions
Use `mkMerge` for multiple conditions:
```nix
# Good
config = mkMerge [
(mkIf cfg.feature1.enable {
# config for feature1
})
(mkIf cfg.feature2.enable {
# config for feature2
})
];
```
### Import Pattern
#### Multi-line Imports
Multi-line, trailing commas:
```nix
# Good
{
lib,
stdenv,
fetchFromGitHub,
}:
```
#### Explicit Dependencies
```nix
# Good
{
lib,
stdenv,
openssl,
pkg-config,
}:
stdenv.mkDerivation {
buildInputs = [openssl pkg-config];
}
```
## Anti-Patterns
### lib.fakeHash in Commits
**Bad**: Committing `lib.fakeHash`
```nix
# Bad - Never commit this!
src = fetchFromGitHub {
hash = lib.fakeHash;
};
```
**Solution**: Build to get real hash:
```bash
nix build .#your-package
# Copy actual hash from error message
```
### Flat Module Files
**Bad**: All modules in one file
```nix
# Bad - Hard to maintain
{config, lib, pkgs, ...}: {
options.m3ta.cli = {
tool1 = mkEnableOption "tool1";
tool2 = mkEnableOption "tool2";
# ... many more
};
config = mkMerge [
(mkIf config.cli.tool1.enable {...})
(mkIf config.cli.tool2.enable {...})
# ... many more
];
}
```
**Solution**: Organize by category
```nix
# Good - modules/home-manager/cli/
# modules/home-manager/cli/default.nix
{
imports = [
./tool1.nix
./tool2.nix
];
}
```
### Hardcoded Ports
**Bad**: Hardcoding ports in services
```nix
# Bad
services.nginx = {
enable = true;
httpConfig = ''
server {
listen 80;
}
'';
};
```
**Solution**: Use port management
```nix
# Good
m3ta.ports = {
enable = true;
definitions = {nginx = 80;};
};
services.nginx = {
enable = true;
httpConfig = ''
server {
listen ${toString (config.m3ta.ports.get "nginx")};
}
'';
};
```
### Skipping Meta Fields
**Bad**: Incomplete meta information
```nix
# Bad
meta = {
description = "My package";
};
```
**Solution**: Include all fields
```nix
# Good
meta = with lib; {
description = "My awesome package";
homepage = "https://github.com/author/package";
license = licenses.mit;
platforms = platforms.linux;
mainProgram = "program-name";
};
```
### with pkgs; in Modules
**Bad**: Using `with pkgs;` at module level
```nix
# Bad
{config, lib, pkgs, ...}: with pkgs; {
config.environment.systemPackages = [
vim
git
];
}
```
**Solution**: Explicit package references or limited with
```nix
# Good - Explicit references
{config, lib, pkgs, ...}: {
config.environment.systemPackages = with pkgs; [
vim
git
];
}
# Or - Full references
{config, lib, pkgs, ...}: {
config.environment.systemPackages = [
pkgs.vim
pkgs.git
];
}
```
### Orphaned Package Directories
**Bad**: Creating directory without registering
```nix
# Bad - Package not visible
# pkgs/my-package/default.nix exists
# But pkgs/default.nix doesn't reference it
```
**Solution**: Register in `pkgs/default.nix`
```nix
# Good
{
inherit (pkgs) callPackage;
} rec {
my-package = callPackage ./my-package {};
}
```
## Type Safety
### No Type Suppression
**Bad**: Using `as any`
```nix
# Bad
let
value = someFunction config as any;
in
# ...
```
**Solution**: Fix underlying type issues
```nix
# Good
let
value = someFunction config;
in
# Ensure value has correct type
```
### Proper Type Definitions
```nix
# Good
options.m3ta.myModule = {
enable = mkEnableOption "my module";
port = mkOption {
type = types.port;
default = 8080;
description = "Port to run on";
};
};
```
## Naming Conventions
### Package Names
**Good**: `lowercase-hyphen`
```nix
code2prompt
hyprpaper-random
launch-webapp
```
**Bad**: CamelCase or underscores
```nix
code2Prompt # Bad
hyprpaper_random # Bad
```
### Variables
**Good**: `camelCase`
```nix
portHelpers
configFile
serviceName
```
**Bad**: Snake_case or kebab-case
```nix
port_helpers # Bad
port-helpers # Bad
```
### Module Options
**Good**: `m3ta.*` namespace for m3ta modules, `cli.*` namespace for CLI modules
```nix
m3ta.ports.enable = true;
cli.zellij-ps.enable = true;
```
**Bad**: Flat namespace
```nix
ports.enable = true; # Potential conflict
zellij-ps.enable = true; # Hard to find
```
## Performance
### Lazy Evaluation
**Good**: Use `callPackage`
```nix
# Good - Only builds requested package
code2prompt = callPackage ./code2prompt {};
```
**Bad**: Building all packages
```nix
# Bad - Builds everything even if not used
code2prompt = import ./code2prompt {};
```
### Selective Imports
**Good**: Import only needed modules
```nix
# Good
imports = [
m3ta-nixpkgs.nixosModules.mem0
];
```
**Bad**: Importing all modules
```nix
# Bad - Imports and evaluates all modules
imports = [
m3ta-nixpkgs.nixosModules.default
];
```
## Security
### No Secrets in Store
**Bad**: Putting secrets in configuration
```nix
# Bad - Secret in Nix store
m3ta.mem0.llm.apiKey = "sk-xxx";
```
**Solution**: Use secret files
```nix
# Good - Secret from file
m3ta.mem0.llm.apiKeyFile = "/run/secrets/openai-api-key";
```
### Proper User/Group
**Good**: Dedicated users for services
```nix
# Good
users.users.mem0 = {
isSystemUser = true;
group = "mem0";
};
```
### Service Hardening
**Good**: Enable systemd hardening
```nix
# Good
systemd.services.mem0.serviceConfig = {
NoNewPrivileges = true;
PrivateTmp = true;
ProtectSystem = "strict";
ProtectHome = true;
};
```
## Best Practices Summary
| Practice | Do | Don't |
|----------|-----|--------|
| Hash fetching | Get real hash from build error | Commit `lib.fakeHash` |
| Module organization | Categorize by function | Put all in one file |
| Port management | Use `m3ta.ports` module | Hardcode ports |
| Meta fields | Include all fields | Skip fields |
| Type safety | Fix type errors | Use `as any` |
| Dependencies | Explicit declarations | Implicit deps |
| Imports | Multi-line, trailing comma | Single-line, no comma |
| Naming | Follow conventions | Mix styles |
| Secrets | Use file-based | Put in config |
| Evaluation | Lazy (`callPackage`) | Import everything |
## Related
- [Contributing Guide](../CONTRIBUTING.md) - Code style and guidelines
- [Architecture](../ARCHITECTURE.md) - Understanding repository structure
- [Adding Packages](../guides/adding-packages.md) - Package creation patterns