Skip to content
28 changes: 24 additions & 4 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ BOOTSTRAP_CACHES=false
ACTION="switch" # default action after build: switch | boot
BUILD_FLAGS=()
NH_CMD=()
LOG_DIR="${XDG_STATE_HOME:-${HOME}/.local/state}/nixos-build"
LOG_FILE=""
# Colors for output (readonly constants)
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
Expand Down Expand Up @@ -56,9 +58,12 @@ Options:
--bootstrap Use extra substituters for first build (e.g., Determinate Nix)
-h, --help Show this help message

Logs:
Each run is recorded to %s/build-<timestamp>-<pid>.log

Usage Example:
${0} --offline
" "${0##*/}" "${PWD}" "$(hostname)"
" "${0##*/}" "${PWD}" "$(hostname)" "${LOG_DIR}"
}

# Status messages with printf
Expand Down Expand Up @@ -89,7 +94,21 @@ trap_error() {
}

trap trap_error ERR
trap 'true' EXIT # Cleanup hook (no-op; extend as needed)
# Close redirected fds so tee subprocesses see EOF, then wait for them to flush.
trap 'exec >&- 2>&-; wait' EXIT

setup_logging() {
LOG_FILE="${LOG_DIR}/build-$(date +%Y%m%d-%H%M%S)-$$.log"
mkdir -p "${LOG_DIR}"
# Merge stderr into stdout before the tee so a single sed process
# appends to the log. POSIX guarantees atomic O_APPEND only up to
# PIPE_BUF (4096 bytes on Linux); separate stdout/stderr appenders
# would interleave at byte boundaries when verbose nix output emits
# long single lines (store-path arrays, dumped derivations). The
# outer tee preserves ANSI on the terminal; sed strips the file copy.
exec > >(tee >(sed -u 's/\x1b\[[0-9;]*m//g' >>"${LOG_FILE}")) 2>&1
status_msg "${GREEN}" "Logging to: ${LOG_FILE}"
}
Comment thread
Bad3r marked this conversation as resolved.

# Parse command-line arguments
while [[ $# -gt 0 ]]; do
Expand Down Expand Up @@ -201,8 +220,8 @@ fi
# Used for initial Determinate Nix setup or similar migrations
BOOTSTRAP_SUBSTITUTERS=(
"https://mirrors.tuna.tsinghua.edu.cn/nix-channels/store"
"https://mirror.sjtu.edu.cn/nix-channels/store"
#"https://mirrors.ustc.edu.cn/nix-channels/store"
#"https://mirror.sjtu.edu.cn/nix-channels/store"
"https://mirrors.ustc.edu.cn/nix-channels/store"
"https://cache.nixos.org"
"https://cache.garnix.io?priority=38"
"https://cache.numtide.com"
Expand Down Expand Up @@ -423,6 +442,7 @@ run_firmware_updates() {
}

main() {
setup_logging
configure_nix_config
configure_build_flags

Expand Down
35 changes: 20 additions & 15 deletions docs/guides/custom-packages-style-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,22 +259,23 @@ Optional but recommended:

## Registering in the Overlay

After creating the package, register it in `modules/system76/custom-packages-overlay.nix`:
In-tree custom packages are exposed through the shared `customPackages` overlay at `modules/base/custom-packages-overlay.nix`. Add a single entry there for each new package:

```nix
_: {
configurations.nixos.system76.module = {
nixpkgs.overlays = [
(final: _prev: {
# Existing packages...
my-new-package = final.callPackage ../../packages/my-new-package { };
})
];
flake.lib.overlays.customPackages = final: prev: {
# Existing packages...
my-new-package = final.callPackage ../../packages/my-new-package { };
};
}
```

This makes the package available as `pkgs.my-new-package` throughout the configuration.
Hosts opt in by composing the shared overlay into `nixpkgs.overlays`. The wiring already exists for both managed hosts:

- `modules/system76/custom-packages-overlay.nix` composes the shared overlay and layers host-specific patches on top (e.g., the `system76-power` patch).
- `modules/tpnix/custom-packages-overlay.nix` composes the shared overlay alone.

Use the per-host files only for host-only patches, version overrides, or hardware-specific tweaks; new in-tree packages belong in the shared overlay so every host that opts in sees them. After registration the package is available as `pkgs.my-new-package` on every host whose `custom-packages-overlay` module is loaded.

## Hash Fetching Workflow

Expand All @@ -286,12 +287,14 @@ This makes the package available as `pkgs.my-new-package` throughout the configu
hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
```

2. Attempt to build:
2. Attempt to build the package through the overlay-aware host `pkgs`:

```bash
nix build .#my-package
nix build .#nixosConfigurations.system76.pkgs.my-package
```

Overlay-backed packages are not exposed as top-level flake outputs, so `nix build .#my-package` will fail with `does not provide attribute packages.x86_64-linux.my-package`. Always go through `nixosConfigurations.<host>.pkgs.<name>` to apply the overlay.

3. Copy the `got:` hash from the error message into your file.

Alternatively, use `nix-prefetch-github`:
Expand Down Expand Up @@ -352,13 +355,15 @@ Create a corresponding app module in `modules/apps/<name>.nix` when:
- The package is a user-facing application
- Users should be able to enable/disable it declaratively
- The package needs unfree allowlisting or extra configuration
- The package is host-visible tooling such as a shell helper or system-management script (e.g., `modules/apps/sss-pass-gpg-bootstrap.nix`, `modules/apps/sss-nix-repair.nix`)

Skip the app module when:

- The package is host-specific tooling
- The package is only used in devshells
- The package is a library or build tool

The `apps-catalog-sync` pre-commit hook (`modules/meta/hooks/apps-catalog-sync.nix`) enforces that every app module under `modules/apps/` has a matching entry in each host's `apps-enable.nix`. After adding the app module, register it in `modules/system76/apps-enable.nix` and `modules/tpnix/apps-enable.nix`. Use `lib.mkOverride 1100 false` as the default and flip individual host catalogs to `true` only where the tool should be installed.

See [Apps Module Style Guide](apps-module-style-guide.md) for the app module format.

## Validation Checklist
Expand All @@ -367,10 +372,10 @@ Before committing a new package:

- [ ] File exists at `packages/<name>/default.nix`
- [ ] All required meta fields are present
- [ ] Package is registered in `custom-packages-overlay.nix`
- [ ] `nix build .#<name>` succeeds (or via overlay: test in config)
- [ ] Package is registered in `modules/base/custom-packages-overlay.nix` (shared `customPackages` overlay)
- [ ] `nix build .#nixosConfigurations.<host>.pkgs.<name>` succeeds (overlay-backed packages are not exposed as top-level flake outputs)
- [ ] `nix flake check --accept-flake-config` passes
- [ ] App module created if user-facing (see [Apps Module Style Guide](apps-module-style-guide.md))
- [ ] App module created if user-facing or host-visible (see [Apps Module Style Guide](apps-module-style-guide.md)); `apps-catalog-sync` will fail the push if `modules/apps/<name>.nix` lacks a matching entry in every host's `apps-enable.nix`

## Reference Implementations

Expand Down
42 changes: 21 additions & 21 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

56 changes: 56 additions & 0 deletions modules/apps/rip.nix
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,61 @@ let
trash-put "''${TRASH_PUT_ARGS[@]}" "''${TRASH_DIR_ARGS[@]}" -- "''${FILES[@]}"
'';
};

ripCompletion = pkgs.writeTextFile {
name = "rip-zsh-completion";
destination = "/share/zsh/site-functions/_rip";
text = ''
#compdef rip

# Hand-written completion for the rip wrapper. Source of truth lives
# alongside the wrapper in this same module; the flag list must stay
# in sync with the case-statements in ripWrapper above.

_rip_files() {
# `(#m)` and `$MATCH` require extendedglob; ensure it is set even
# when the caller's shell disables it. `localoptions` confines the
# change to this function.
setopt localoptions extendedglob
# Mirror the escaping shim trash-cli uses in _trash_files (see
# _trash-put / _trash-restore on the same fpath). Operate on a
# copy of `words` because `line` only holds positional args while
# `CURRENT` indexes `words`; mixing the two leaves the in-progress
# word in the exclusion set and silently breaks completion when a
# half-typed path contains a metachar.
local -a tmp_words
tmp_words=( "''${words[@]}" )
tmp_words[CURRENT]=()
tmp_words=( ''${tmp_words//(#m)[\[\]()\\*?#<>~\^\|]/\\$MATCH} )
_files -F tmp_words
}
Comment thread
Bad3r marked this conversation as resolved.

_rip() {
local context state line curcontext="$curcontext"
typeset -A opt_args

# Exclusion lists mirror the wrapper's mode_count check: the four
# mode flags (--empty-trash, -s/--seance, -u/--unbury, positional
# FILES) are mutually exclusive. -i/--inspect is a trash-mode
# modifier and is rejected by the wrapper alongside the mode
# flags, so list it symmetrically. -u/--unbury also excludes the
# global positional spec (`*`) so files-to-trash completion stops
# firing once the operator has committed to a restore.
_arguments -C -s -S \
'(- *)'{-h,--help}'[print help and exit]' \
'(--empty-trash --seance -s --unbury -u -i --inspect *)--empty-trash[permanently empty the trash]' \
'(--empty-trash --seance -s --unbury -u -i --inspect *)'{-s,--seance}'[list files trashed from cwd]' \
'(--empty-trash --seance -s --unbury -u -i --inspect *)'{-u,--unbury}'[restore trashed files (interactive picker if no PATH)]:*::path to restore:_rip_files' \
'(--empty-trash --seance -s --unbury -u)'{-i,--inspect}'[inspect (ls -la) and prompt before trashing]' \
{-f,--force}'[skip all confirmation prompts]' \
'--graveyard=[override trash directory for this invocation]:trash dir:_files -/' \
{-r,-R,-d}'[rm compat, no-op]' \
'*:files to trash:_rip_files'
Comment thread
Bad3r marked this conversation as resolved.
}

_rip "$@"
'';
};
in
{
options.programs.rip.extended = {
Expand Down Expand Up @@ -290,6 +345,7 @@ let
environment.systemPackages = [
cfg.package
ripWrapper
ripCompletion
];

systemd.tmpfiles.rules = [
Expand Down
11 changes: 0 additions & 11 deletions notes.md

This file was deleted.