nixos-modern-cli · git:20260916.a33a3cf · 2026-09-16 · sha256 e5e8d2eaea8ebbf6
nixos-modern-cli git:20260916.a33a3cfA
Immutable. This exact content is served forever at /api/v1/blob/e5e8d2eaea8ebbf6.
---
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
- 任何**不含凭据**的配置 → 正常入库
- 不要把"整份配置"移出仓库来图省事——只移出真正敏感的那部分