Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ __pycache__/
# Generated by `nix flake check` / any nix command in this repo —
# deliberately NOT pinned (library flake; see AGENTS.md "Core lib tests").
flake.lock
/result
56 changes: 51 additions & 5 deletions AGENTS.md

Large diffs are not rendered by default.

42 changes: 41 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ Everything under `icedos` is IceDOS's own, checked settings. The top-level group
| Key | What it controls |
| --- | --- |
| `icedos.repositories` | Which module repos to load and which modules to enable (see above). |
| `icedos.system` | System-wide settings: `arch`, `version` (stateVersion), `nixpkgsChannel`, `allowUnfree`, `generations`, `packages`, `permittedInsecurePackages`, `loadHardwareConfiguration`, the binary `cache`, `gc` (auto-cleanup), the `toolset` (CLI + hooks), extra `channels`/`overlays`, and `build-vm`. |
| `icedos.system` | System-wide settings: `arch`, `version` (stateVersion), `nixpkgsChannel`, `allowUnfree`, `generations`, `packages`, `permittedInsecurePackages`, `loadHardwareConfiguration`, the binary `cache`, `gc` (auto-cleanup), the `toolset` (CLI + hooks), the rebuild `tips` line, extra `channels`/`overlays`, and `build-vm`. |
| `icedos.users` | User accounts (home-manager integrated): password, groups, sudo, packages, … |
| `icedos.<category>.*` | Options exposed by the module repos you load, grouped by category — e.g. `icedos.applications.*` (apps like `btop`, `steam`), `icedos.hardware.*`, `icedos.desktop.*`, `icedos.tweaks.*`. Which categories exist depends on which repos you enable. |

Expand Down Expand Up @@ -430,6 +430,36 @@ New here? These cover almost everything:
| `icedos gc [--dry] [--days <N>] [--gens <N>]` | Free up disk space (--dry to preview). |
| `icedos repl` | Open a Nix REPL preloaded with your evaluated config, packages, and lib. |

### The tips line

Once `icedos.system.tips.list` is populated, `icedos rebuild` runs with a
persistent bottom bar: output is confined above a blank line and a one-line tip
pinned to the terminal's last row, visible from the first frame to the last.
The bar stays on the last row for the whole run, so short output leaves it a
screenful below the final line; restoring the terminal on exit scrolls it up so
the shell prompt lands beneath it:

```
$ icedos rebuild
> Caching config set

💡: icedos rebuild --dry prepares a rebuild without building anything.
```

`list` merges tips contributed by every loaded icedos module, so each
module advertises its own features; add machine-local tips from config if you
like. Set `enable = false` under `[icedos.system.tips]` to turn the line off. The
bar is TTY-only, so piped output stays clean. It makes room below your typed
command, restores the terminal on exit and on TERM/HUP/QUIT (SIGKILL excepted),
and leaves helpers and non-script dispatchers untouched.

Pinning the bar needs the cursor position, so the terminal is asked for it with
a DSR query. Terminals that do not answer within 200 ms — and multiplexers or
recorders that swallow the reply — get the tip as a plain trailing line instead;
nothing is scrolled blind. A resize re-pins the bar, but bash only runs the
`WINCH` handler once the current foreground command returns, so resizing mid-build
leaves the bar at its old coordinates until that build step finishes.

### Full command reference

| Command | Description |
Expand Down Expand Up @@ -467,6 +497,14 @@ With no flags this is a `switch`: it builds your configuration and activates it

Rebuilds can pass a GitHub token to nix as a `github.com` access token (higher API rate limits, private `github:` inputs). A literal token is resolved first: `--github-token <token>`, then the `ICEDOS_GITHUB_TOKEN` env var. Otherwise a token file is used: `--github-token-path <path>`, the `ICEDOS_GITHUB_TOKEN_PATH` env var, or the `icedos.system.githubTokenPath` option (default `/etc/icedos-github-token`). A token file your user can't read is fetched with `sudo cat` — you'll be asked for your sudo password once (on a terminal); in non-interactive sessions the rebuild falls back to running without the token. The toolset wrapper resolves the same file before invoking the orchestrator and exports `NIX_CONFIG`, so nix calls that precede it (e.g. a stale-lock `nix run` re-resolving `github:` inputs) authenticate as well. Avoid `icedos.system.githubToken` (a literal token in `config.toml`): it gets baked into the world-readable nix store, and every rebuild warns about it.

Alternatively, `--github-ssh` (or the `icedos.system.githubViaSsh` option, which `--no-github-ssh` turns off for a single run) rewrites every `github:` input of the generated state flake — repositories, module inputs, channels, overlays, `extraFlakes`, `nixpkgs`, `home-manager` — to `git+ssh://git@github.com/...`, so your ssh key authenticates those fetches. It is not a replacement for the token: the config flake's own inputs (e.g. `icedos` core) still resolve over https, so the token is still read and passed to nix exactly as above. The build's nix may run as root, so make sure the key is reachable there too (`ssh-add` your key, or point `GIT_SSH_COMMAND` at an agent). The two flags are the only per-run route: they set `ICEDOS_GITHUB_SSH` (`1`/`0`) for the flake-generation step, and a rebuild with neither flag clears it, so a value left exported in your shell cannot quietly override the option on later rebuilds.

Under the switch, an inline ref on a `github:` url becomes a query on the ssh url: a full 40-hex rev (any case) pins `?rev=<hash>`, a bare name pins `?ref=<name>` — which nix resolves as the `refs/heads/<name>` branch. A tag pinned as a bare name is therefore silently read as a branch of that name and fails to fetch. Nothing can tell the two apart at eval time, so every unqualified ref traces a warning; qualify it — `refs/heads/<branch>` or `refs/tags/<tag>` — and the warning goes away. Both qualified forms also resolve on the plain https path, so they are safe to leave in `config.toml`, which is shared by both transports. Short revs must be expanded to the full 40-hex hash.

A `?rev=` pin is resolved by nix against `HEAD` alone, so every url the switch rewrites also carries the branch it was pinned from (`&ref=<name>`), or `&allRefs=1` when no branch is known — otherwise a rev living outside the default branch's history would fail to fetch. `&allRefs=1` makes nix fetch *every* ref of that repo, so a rev-pinned `nixpkgsChannel` (`github:nixos/nixpkgs/<40-hex>`) becomes an all-refs clone of nixpkgs; pin such channels by branch name where you can. Urls that were already `git+...` in your `config.toml` keep their transport and their rev pins. One unrelated fix rides along for them regardless of the switch: a `narHash` pin is no longer spelled onto a git-scheme url, because nix has no such query parameter there and folds it into the remote address, producing something `git ls-remote` cannot resolve. Such an input is now emitted unpinned instead of unfetchable.

**Flipping the switch re-locks every input IceDOS does not pin itself.** Changing the transport changes each input's recorded `original`, and nix re-locks anything whose `original` no longer matches the lock — resolving the current tip. Repository inputs and patched-module `_source` inputs keep their pin (IceDOS bakes the rev into those and matches the two spellings), but `nixpkgs`, `home-manager`, channels, overlays, `extraFlakes` and unpinned module inputs do not. So turning the switch on, and turning it off again with `--no-github-ssh`, each act as an update for those inputs. Treat a flip as an update, not as a free toggle: `--no-github-ssh` is there to get a build through without ssh, not to leave your lock untouched. Rewriting `nixpkgs`/`home-manager` channels also turns their tarball fetch into a full git clone, so the first ssh build transfers noticeably more than before.

#### Action flags — *what kind of build*

| Flag | Effect | Typical use |
Expand Down Expand Up @@ -502,6 +540,8 @@ Rebuilds can pass a GitHub token to nix as a `github.com` access token (higher A
| `--target <host>` | Deploy/activate the built system on a remote host (pairs with `--builder`). |
| `--github-token <token>` | Use this literal GitHub token for nix's `github.com` fetches. Wins over every file-based source. (Visible in the process list while the rebuild runs.) |
| `--github-token-path <path>` | Use `<path>` (a file containing a GitHub token) for nix's `github.com` fetches. Overrides `ICEDOS_GITHUB_TOKEN_PATH` and `icedos.system.githubTokenPath`. |
| `--github-ssh` | Fetch the generated state flake's `github:` inputs over `git+ssh://git@github.com/...` with your ssh key instead of the token. Per-run opt-in (`icedos.system.githubViaSsh` turns it on permanently); the config flake's own inputs still use the token. |
| `--no-github-ssh` | Force https for this run, overriding `icedos.system.githubViaSsh`. Use it to get a build through without ssh (e.g. while debugging an agent). Not free: flipping the transport re-locks every input IceDOS does not pin itself. |
| `--nh-args ...` | Forward extra args to `nh os` (place after other flags; consumes until `--build-args`). |
| `--build-args ...` | Forward all remaining args to the final rebuild command. **Must be last.** |
| `--genflake-only` | *(advanced)* Generate and lock the state flake, then exit without building. |
Expand Down
10 changes: 10 additions & 0 deletions build/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@ def main(argv: list[str] | None = None) -> int:
os.environ["NIX_CONFIG"] = BASE_NIX_CONFIG
if opts.logs:
os.environ["ICEDOS_LOGGING"] = "1"
# Read by the genflake eval, which bakes the resolved value into the
# generated flake (the env var does not reach the build stage's pure eval).
# "0" is not the same as unset: it overrides a config.toml `true`. With no
# flag the variable is REMOVED rather than left alone, so a value exported
# in the user's shell cannot silently override the option on every later
# rebuild — the flags are the only way to reach it.
if opts.github_ssh is None:
os.environ.pop("ICEDOS_GITHUB_SSH", None)
else:
os.environ["ICEDOS_GITHUB_SSH"] = "1" if opts.github_ssh else "0"

env = from_environment()
trace = opts.trace
Expand Down
8 changes: 8 additions & 0 deletions build/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class Options:
repos_select: list[str] = field(default_factory=list)
github_token: str | None = None
github_token_path: str | None = None
# Tri-state: None = not asked for, so icedos.system.githubViaSsh decides.
github_ssh: bool | None = None
nh_build_args: list[str] = field(default_factory=list)
global_build_args: list[str] = field(default_factory=list)
logs: bool = False
Expand Down Expand Up @@ -141,6 +143,12 @@ def parse_args(argv: list[str]) -> tuple[Options, list[str]]:
_die("error: --github-token-path requires a path")
opts.github_token_path = argv[i + 1]
i += 2
elif arg == "--github-ssh":
opts.github_ssh = True
i += 1
elif arg == "--no-github-ssh":
opts.github_ssh = False
i += 1
elif arg == "--logs":
opts.logs = True
i += 1
Expand Down
7 changes: 7 additions & 0 deletions build/tests/test_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ def test_github_token_flags_are_captured(self):
self.assertEqual(opts.github_token, "tok")
self.assertEqual(opts.github_token_path, "/p")

def test_github_ssh_flag_is_captured(self):
# Tri-state: unset must stay None so config.toml decides, and
# --no-github-ssh must be distinguishable from "not asked for".
self.assertTrue(_parse(["--github-ssh"])[0].github_ssh)
self.assertIs(_parse(["--no-github-ssh"])[0].github_ssh, False)
self.assertIsNone(_parse([])[0].github_ssh)

def test_unknown_arg_exits(self):
with self.assertRaises(SystemExit):
_parse(["--nope"])
Expand Down
Loading