---
name: nixos-modern-cli
description: 在 NixOS 系统上工作时使用。确保正确使用现代 Nix/NixOS CLI、完整的 shell 能力、sudo 权限和正确的系统维护流程；含 Nix store 路径陷阱与密钥/`path:` input 的仓库外组织方式。
---

# NixOS 现代 CLI 指南

## 关键认知：NixOS 不是传统 Linux 发行版

NixOS 是一个声明式、不可变的 Linux 发行版。关键区别：

- **没有 `/usr/bin`、`/usr/lib`** — 所有软件位于 Nix store（`/nix/store`）
- **没有 `apt`、`yum`、`pacman`** — 使用 `nix` 命令或编辑 `/etc/nixos/configuration.nix`（或 flake）
- **没有 `/etc/default/grub`、`/etc/fstab`** — 这些由 NixOS 配置生成
- **Shell PATH 默认不含常用工具** — 使用 `nix shell nixpkgs#<pkg>` 获取临时工具
- **`sudo` 正常工作**，`systemctl` 行为与预期一致
- **必须用 `nixos-rebuild`** 应用配置变更

## 第一步：检查 CLI 能力

获取 shell 访问后，验证可用工具：

```bash
# 检查现代 nixos CLI（首选）
nixos --help 2>/dev/null && echo "nixos-cli available"

# 检查 nix-command（优于传统 nix-* 命令）
nix --help 2>/dev/null && echo "nix-command available"
```

## 现代命令 vs 传统命令

始终优先使用现代等位命令：

| 传统命令 | 现代命令（优先） |
|----------|----------------|
| `nixos-rebuild switch` | `nixos rebuild switch`（有 nixos-cli 时）或 `nixos-rebuild switch` |
| `nix-env -iA` | `nix profile install` |
| `nix-shell` | `nix shell` |
| `nix-build` | `nix build` |
| `nix-collect-garbage` | `nix store gc` |
| `nix-store --optimise` | `nix store optimise` |
| `nix-channel --update` | 使用 flake 则不需要 |

如果有 `nixos-cli`（软件包 `nixos-cli`）：
```bash
nixos rebuild switch --flake /etc/nixos
nixos generation delete --all  # 清理旧 generation
nixos store gc                 # 垃圾回收
nixos store optimise           # 优化 store
```

如果只有 `nix-command`：
```bash
sudo nixos-rebuild switch --flake /etc/nixos
nix store gc
nix store optimise
```

## Shell 环境

NixOS 的 shell 环境极简。需要系统 PATH 之外的命令时：

```bash
# 临时 shell，包含所需工具
nix shell nixpkgs#git nixpkgs#ripgrep nixpkgs#curl

# 或进入持久开发 shell
nix develop nixpkgs#<package>
```

### 运行需要 POSIX 工具的脚本

许多 shell 脚本依赖 `grep`、`sed`、`tr`、`head`、`tail`、`python3` 等。
在 NixOS 上这些默认不在 PATH 中。使用 `nix shell` 的 `--command` 标志在齐全的环境中运行脚本：

```bash
# 单条命令，附带所需包
nix shell nixpkgs#python3 nixpkgs#coreutils nixpkgs#gnused \
  nixpkgs#gnugrep nixpkgs#bash --command \
  bash -c 'python3 my_script.py arg1 arg2'

# 复杂多步流程
nix shell nixpkgs#python3 nixpkgs#coreutils nixpkgs#gnused \
  nixpkgs#gnugrep nixpkgs#bash --command bash -c '
cd /path/to/project
python3 install.py 2>&1
ls -la output/
echo "done"
'
```

**Shell 脚本常用包对照：**

| 工具 | 包名 |
|------|------|
| `python3` | `nixpkgs#python3` |
| `grep`、`ls`、`cat`、`head`、`tail`、`wc`、`tr`、`sort`、`mkdir`、`rm`、`find` | `nixpkgs#coreutils` |
| `sed` | `nixpkgs#gnused` |
| `bash`（完整版） | `nixpkgs#bash` |
| `awk` | `nixpkgs#gawk` |
| `git` | `nixpkgs#git` |

## 系统维护

### 检查系统状态
```bash
systemctl status
systemctl --failed
```

### 查看日志
```bash
journalctl -xe
journalctl -u <service-name> -f
```

### 更新系统
```bash
cd /etc/nixos
nix flake update
sudo nixos-rebuild switch --flake .
```

### 清理
```bash
# 删除旧 generation
sudo nixos-rebuild list-generations
sudo nix-env --delete-generations old
# 或用 nixos-cli：
nixos generation delete --all

# 垃圾回收
nix store gc
nix store optimise
```

### 服务管理
```bash
sudo systemctl start/stop/restart <service>
sudo systemctl enable/disable <service>
systemctl --user start/stop/restart <service>
```

## 常见陷阱

- **找不到命令？** 临时安装：`nix shell nixpkgs#<cmd>`
- **`nix` 命令未找到？** 二进制文件在 `/run/current-system/sw/bin/nix`，即使不在 PATH 中也可用完整路径调用
- **安装后找不到二进制？** Nix store 路径不在标准 PATH 中 — 使用完整路径或将包添加到 `environment.systemPackages`
- **`nix-env` 变更不持久？** `nix-env` 是命令式的，绕过 NixOS 声明式配置 — 推荐编辑 `/etc/nixos/`
- **需要编辑配置文件？** 编辑 `/etc/nixos/` 中的文件，然后 `sudo nixos-rebuild switch`
- **如何安装系统级软件包？** 添加到 `configuration.nix` 的 `environment.systemPackages`，然后 rebuild

## Nix Store 路径陷阱

Nix 将软件安装在 `/nix/store/<hash>-<name>-<version>` 中。写入到配置文件
（`.gitconfig`、`.bashrc`、systemd unit 等）的绝对 Nix store 路径在系统更新
或 `nix store gc` 后会立即失效。

### 典型场景：GitHub CLI 凭据助手

`gh auth setup-git` 会将 gh 的绝对路径写入 `~/.gitconfig`：

```
[credential "https://github.com"]
    helper = !/nix/store/pidh15...-gh-2.94.0/bin/.gh-wrapped auth git-credential
```

GC 回收旧 store 路径后，Git 无法调用凭据助手，回退到交互式终端提示。
非交互环境下直接失败，症状为：

```
fatal: could not read Username for 'https://github.com': terminal prompts disabled
```

### 通用修复模式

将硬编码的绝对路径替换为通过 `$PATH` 查找的命令名：

```bash
# 修复前（不稳定）
git config --global credential.https://github.com.helper \
  '!/nix/store/xxxx-gh-2.94.0/bin/.gh-wrapped auth git-credential'

# 修复后（持久）
git config --global credential.https://github.com.helper \
  '!gh auth git-credential'
```

> **规则**：任何写入配置文件的 Nix store 绝对路径都会成为定时炸弹。
> 优先使用裸命令名（依赖 `$PATH`）或指向 `/run/current-system/sw/bin/`
> 的符号链接（该路径在系统更新时由 NixOS 自动替换）。

### 识别方法

检查常见配置文件中是否包含 `/nix/store/` 路径：

```bash
grep -rn '/nix/store/' ~/.gitconfig ~/.bashrc ~/.zshrc ~/.config/ 2>/dev/null
```

### 其他常见案例

| 工具 | 陷阱 | 修复 |
|------|------|------|
| `pip install --user` | `~/.local/bin/` 中的脚本 shebang 指向 Nix store Python | 使用 `nix shell` 或虚拟环境，不要用 `pip install --user` |
| `gem install` | 同 pip，二进制 stub 指向特定 Ruby 版本 | 使用 nixpkgs 中的 Ruby 包或 `bundler` |
| `cargo install` | 二进制 hardcode 了构建时的 Nix store rpath | 使用 nixpkgs 中的 Rust 包或 `nix build` |
| systemd unit `ExecStart=` | 指向 `/nix/store/…` 的绝对路径 | 使用 `lib.getExe pkg` 或在 `$PATH` 中引用 |

## 密钥与 flake 组合：为什么必须放在仓库外

**Nix 只把 git 跟踪的文件拷进 store。** 这一条与"`/etc/nixos` 是 git 仓库"叠加后，
使密钥文件**没有留在仓库内的出路**：

| 做法 | 后果 |
|------|------|
| 提交密钥文件 | 密钥进入版本库（若仓库有 remote 则已泄露） |
| 加进 `.gitignore` | flake 求值**直接报错**——store 里拿不到该文件 |

```
error: Path 'credentials/config.nix' in the repository is not tracked by Git.
```

### 正确做法：仓库外 + `path:` input

把密钥放在仓库**之外**的普通目录（同样受 git 管理更好，但不进本仓库），
再用 `path:` input 引入：

```nix
# flake.nix
inputs = {
  nixos-secrets = { flake = false; url = "path:/home/<user>/.nixos-secrets"; };
};
```

```nix
# 消费侧模块——注意 `...` 并**不会**绑定该参数，必须显式列出
{ nixosSecrets, ... }:
{
  imports = [ "${nixosSecrets}/config.nix" ];
}
```

> ⚠️ **陷阱 1：`path:` input 被 `flake.lock` 锁定，内容改动不会自动生效。**
> `path:` 锁的是路径，但 Nix 仍按 lock 里的记录取内容。改了密钥文件后必须：
>
> ```bash
> nix flake lock --update-input nixos-secrets
> ```
>
> 否则新密钥不生效，表现为"改了配置却没变化"——极易误判为配置写错。

> ⚠️ **陷阱 2：`{ nixosSecrets, ... }` 中的 `...` 不绑定该参数。**
> 必须像上面那样**显式列出**所需 input；只写 `{ ... }` 会在引用时报
> `undefined variable`。

### 判据

- 任何**含凭据**的值（API key、token、session key、密码）→ 仓库外 + `path:` input
- 任何**不含凭据**的配置 → 正常入库
- 不要把"整份配置"移出仓库来图省事——只移出真正敏感的那部分

