| name | add-nix-package |
| description | Add a new Nix package derivation to the giopkgs repository. Use this skill whenever the user wants to add, create, or package a new Nix derivation — especially when they provide a GitHub URL or project name. Also trigger when the user says things like "package X", "add X to giopkgs", "nix package for X", or passes a GitHub URL as an argument. This skill handles the full lifecycle: discovering the right builder, writing the derivation, building, and setting up auto-updates. |
Add Nix Package
You are adding a new package to the giopkgs Nix flake repository. This repo contains personal Nix derivations organized under packages/. The flake auto-discovers packages via packagesFromDirectoryRecursive.
Skill arguments
The user will typically provide a GitHub URL (e.g., https://github.com/owner/repo) or a project name. If invoked as a slash command, the argument is the URL or name.
Overview of steps
- Research the project to determine the right packaging approach
- Write the Nix derivation
- Build and iterate until it succeeds
- Set up auto-updates and verify the update script works
- Format and lint the final result
Step 1: Research the project
Given a GitHub URL or project name:
- Fetch the repo's main page to understand what it is
- Identify the language and build system — this determines which Nix builder to use:
- Go →
buildGoModule (look for go.mod)
- Rust →
rustPlatform.buildRustPackage (look for Cargo.toml / Cargo.lock)
- Node.js/npm →
buildNpmPackage (look for package.json + package-lock.json)
- Node.js/pnpm →
stdenv.mkDerivation with pnpmConfigHook (look for pnpm-lock.yaml)
- Python →
python3.pkgs.buildPythonPackage (look for pyproject.toml / setup.py)
- C/C++/generic →
stdenv.mkDerivation with cmake/meson/make
- AppImage →
appimageTools.wrapType2
- Pre-built binary →
stdenv.mkDerivation with simple install phase
- Check the latest release (tag or version) — prefer tagged releases over branch HEADs
- Identify the project's license — map it to a
lib.licenses.* value
- Note any native dependencies (openssl, pkg-config, system libraries, etc.)
- For Rust workspace repos, identify which crate/subdir contains the target binary
Step 2: Write the derivation
File placement
- Simple packages (single derivation, no patches, no extra files):
packages/<name>.nix
- Complex packages (patches, lock files, custom update scripts, multiple files):
packages/<name>/package.nix
If you're unsure, start with a simple .nix file — you can restructure into a directory later if needed.
Derivation structure
All packages use the callPackage pattern — the file is a function taking { pkgs-and-libs }: and returning a derivation.
Use nurl to compute the source hash:
nurl https://github.com/owner/repo <rev>
This outputs a fetchFromGitHub expression with the correct hash. Extract the hash from its output.
For Go packages, you'll need vendorHash. Set it to lib.fakeHash initially, attempt a build, then extract the correct hash from the error message.
For Rust packages, prefer cargoHash over cargoLock.lockFile. cargoHash is simpler (no vendored lock file to maintain) and nix-update can update it automatically. Use lib.fakeHash initially, build, extract the real hash. Only fall back to cargoLock.lockFile if cargoHash doesn't work (e.g., git dependencies in Cargo.lock that need outputHashes).
Template reference
Here's the general shape for each builder type. Adapt as needed — these are starting points, not rigid templates.
Go:
{
lib,
buildGoModule,
fetchFromGitHub,
}:
buildGoModule rec {
pname = "name";
version = "X.Y.Z";
src = fetchFromGitHub {
owner = "...";
repo = "...";
rev = "v${version}"; # or tag format used by the project
hash = "sha256-...";
};
vendorHash = "sha256-...";
ldflags = ["-s" "-w"];
meta = {
description = "...";
homepage = "https://github.com/owner/repo";
license = lib.licenses.mit; # adjust
maintainers = [];
mainProgram = "...";
};
}
Rust:
{
lib,
rustPlatform,
fetchFromGitHub,
pkg-config,
openssl,
stdenv,
darwin,
}:
rustPlatform.buildRustPackage rec {
pname = "name";
version = "X.Y.Z";
src = fetchFromGitHub {
owner = "...";
repo = "...";
rev = "v${version}";
hash = "sha256-...";
};
cargoHash = "sha256-...";
nativeBuildInputs = [pkg-config];
buildInputs = [openssl] ++ lib.optionals stdenv.hostPlatform.isDarwin [
darwin.apple_sdk.frameworks.Security
darwin.apple_sdk.frameworks.SystemConfiguration
];
meta = {
description = "...";
homepage = "https://github.com/owner/repo";
license = lib.licenses.mit;
maintainers = [];
mainProgram = "...";
};
}
Node (npm):
{
lib,
buildNpmPackage,
fetchFromGitHub,
}:
buildNpmPackage rec {
pname = "name";
version = "X.Y.Z";
src = fetchFromGitHub {
owner = "...";
repo = "...";
rev = "v${version}";
hash = "sha256-...";
};
npmDepsHash = "sha256-...";
meta = {
description = "...";
homepage = "https://github.com/owner/repo";
license = lib.licenses.mit;
maintainers = [];
mainProgram = "...";
};
}
Only include dependencies that are actually needed. Don't add pkg-config, openssl, or darwin frameworks unless the build requires them.
Meta attributes
Always include:
description — short, from the project's own description
homepage — the project URL
license — mapped to lib.licenses.*
maintainers — use [] (empty)
mainProgram — the binary name (for CLI tools)
Optionally:
platforms — only if the package is platform-specific
Step 3: Build and iterate
Build the package:
nix build .#<package-name>
Common issues and fixes:
- Hash mismatch: Extract the correct hash from the error output (the
got: sha256-... line) and update the derivation
- Missing dependencies: Read the build error, add the required
buildInputs or nativeBuildInputs
- Rust workspace: If the repo is a workspace, use
buildAndTestSubdir to select the right crate, or apply patches
- Cargo.lock not found: Copy
Cargo.lock into the package directory and reference it with cargoLock.lockFile = ./Cargo.lock; instead of cargoHash. This is a last resort — cargoHash is preferred since nix-update handles it automatically and there's no vendored file to maintain
- Go vendor issues: Some Go projects need
proxyVendor = true
- Test failures: If tests need network/external services, set
doCheck = false
Iterate until nix build succeeds. Then verify the binary works:
./result/bin/<program> --help
Step 4: Set up auto-updates
The repo has nightly auto-updates via GitHub Actions. For most packages, nix-update handles this automatically — no custom script needed.
When nix-update works out of the box
If the package uses a straightforward version + fetchFromGitHub with a standard tag format (like v${version}), nix-update can handle it. Test it:
nix-update --flake <package-name>
If this works (even as a noop showing "already up to date"), you're done — no custom update script needed.
When you need a custom update script
You need a custom script if:
- The version format is non-standard (e.g., includes git short hash, date-based)
- The package has multiple hashes that need coordinated updates (e.g.,
hashes.json)
- The tag format doesn't follow
v${version} and nix-update can't figure it out
- Post-update processing is needed (e.g., updating a vendored lock file)
For custom scripts, add passthru.updateScript = ./update.sh; and create the script. Keep it simple — use nix-update as the base and add post-processing only if needed:
#!/usr/bin/env bash
set -euo pipefail
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
nix-update --flake <package-name>
Make the script executable: chmod +x packages/<name>/update.sh
If the package started as a simple .nix file and now needs an update script, restructure it into a directory first.
Verify the update script
After setting up updates, run the update mechanism once locally to confirm it works. Since the package was just created at the latest version, this should be a noop:
nix-update --flake <package-name>
./packages/<name>/update.sh
Verify it exits cleanly and doesn't make unexpected changes.
Step 5: Format and lint
Before finishing, run the formatting and linting tools that the git hooks enforce:
alejandra packages/<name>.nix
statix check packages/<name>.nix
deadnix packages/<name>.nix
Fix any issues they flag.
Final checklist