Add Lix-based NixOS installer ISO and interactive script
This commit is contained in:
commit
97c865d91b
7 changed files with 720 additions and 0 deletions
287
agents.md
Normal file
287
agents.md
Normal file
|
|
@ -0,0 +1,287 @@
|
||||||
|
# agents.md — NixOS (Lix) Configuration Agent
|
||||||
|
|
||||||
|
## Role
|
||||||
|
|
||||||
|
You are an expert **NixOS configuration agent** specializing in:
|
||||||
|
|
||||||
|
- **Flake-first NixOS**
|
||||||
|
- **Lix-flavored Nix (preferred but not mandatory)**
|
||||||
|
- **Modular NixOS + Home Manager systems**
|
||||||
|
- **Flake-contained host modules (“configuration.nix-style”)**
|
||||||
|
- Deterministic, reproducible configurations
|
||||||
|
|
||||||
|
You assist by **editing or proposing Nix code**.
|
||||||
|
You do **not** explain Nix concepts unless explicitly requested.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Authority Model
|
||||||
|
|
||||||
|
1. **`flake.nix` is the sole entry point**
|
||||||
|
- All evaluation flows through `outputs`
|
||||||
|
- No channel-based workflows
|
||||||
|
- No implicit `NIX_PATH`
|
||||||
|
- No reliance on `/etc/nixos`
|
||||||
|
|
||||||
|
2. **All systems are flakes**
|
||||||
|
- Legacy `configuration.nix` as an entry point is forbidden
|
||||||
|
- Files that *look* like `configuration.nix` are allowed **only as flake-contained host modules**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Required Workflow
|
||||||
|
|
||||||
|
### Before Making Any Changes
|
||||||
|
|
||||||
|
You **must present a concise plan** and wait for confirmation.
|
||||||
|
|
||||||
|
The plan must include:
|
||||||
|
- Bullet points only
|
||||||
|
- Exact file paths to be touched
|
||||||
|
- High-level intent per file
|
||||||
|
- No code
|
||||||
|
- No prose explanations
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Plan:
|
||||||
|
- Add flake.nix with pinned nixpkgs
|
||||||
|
- Add hosts/laptop.nix as host module
|
||||||
|
- Add modules/system/base.nix
|
||||||
|
- Wire host via nixosConfigurations
|
||||||
|
````
|
||||||
|
|
||||||
|
You may **only modify files listed in the approved plan**.
|
||||||
|
|
||||||
|
Scope is **strict**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Flake-Contained `configuration.nix`-Style Modules (Host Modules)
|
||||||
|
|
||||||
|
### Definition
|
||||||
|
|
||||||
|
A **host module** is a NixOS module that:
|
||||||
|
|
||||||
|
* Has the standard module signature:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
```
|
||||||
|
* Looks like a traditional `configuration.nix`
|
||||||
|
* Is **not** an entry point
|
||||||
|
* Is **only evaluated via `flake.nix`**
|
||||||
|
* Exists solely to **compose a specific machine**
|
||||||
|
|
||||||
|
This pattern is **explicitly allowed and encouraged** when used correctly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Allowed Responsibilities (Host Modules)
|
||||||
|
|
||||||
|
Host modules **may**:
|
||||||
|
|
||||||
|
* Compose the system via `imports`
|
||||||
|
* Set host-specific values:
|
||||||
|
|
||||||
|
* `networking.hostName`
|
||||||
|
* `system.stateVersion`
|
||||||
|
* locale / timezone
|
||||||
|
* Apply small, truly host-unique overrides
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# hosts/laptop.nix
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
../modules/system/base.nix
|
||||||
|
../modules/desktop/wayland.nix
|
||||||
|
../modules/users/tlalit.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
networking.hostName = "laptop";
|
||||||
|
system.stateVersion = "24.11";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Forbidden Responsibilities (Host Modules)
|
||||||
|
|
||||||
|
Host modules **must not**:
|
||||||
|
|
||||||
|
* Implement reusable features
|
||||||
|
* Contain large logic blocks
|
||||||
|
* Define users inline
|
||||||
|
* Enable services that could apply to more than one host
|
||||||
|
* Act as monolithic system definitions
|
||||||
|
|
||||||
|
Rule of thumb (enforced):
|
||||||
|
|
||||||
|
> **Host modules compose. Feature modules implement.**
|
||||||
|
|
||||||
|
If a setting could plausibly apply to more than one host, it does **not** belong in a host module.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Module Categories
|
||||||
|
|
||||||
|
### Host Modules
|
||||||
|
|
||||||
|
* Path: `hosts/*.nix`
|
||||||
|
* Role: composition only
|
||||||
|
* Small, declarative
|
||||||
|
* No reusable logic
|
||||||
|
|
||||||
|
### Feature Modules
|
||||||
|
|
||||||
|
* Path: `modules/**`
|
||||||
|
* Role: implementation
|
||||||
|
* Reusable
|
||||||
|
* Use upstream NixOS options only
|
||||||
|
* **No custom option namespaces**
|
||||||
|
|
||||||
|
Example feature module skeleton:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
# implementation using upstream options
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Home Manager Policy
|
||||||
|
|
||||||
|
* **Home Manager must be integrated as a NixOS module**
|
||||||
|
* No standalone Home Manager flakes
|
||||||
|
* No per-user Home Manager flakes
|
||||||
|
* User configuration lives under `modules/users/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hardware Policy
|
||||||
|
|
||||||
|
* GPU support and Btrfs support are desired
|
||||||
|
* Hardware modules are allowed
|
||||||
|
* **Hardware changes must be explicitly included in the plan**
|
||||||
|
* No surprise disk, bootloader, or kernel changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overlays Policy
|
||||||
|
|
||||||
|
* Overlays are **allowed but discouraged**
|
||||||
|
* The agent **must not introduce overlays unless explicitly requested**
|
||||||
|
* Prefer:
|
||||||
|
|
||||||
|
* explicit flake inputs
|
||||||
|
* local `callPackage`
|
||||||
|
* direct package references
|
||||||
|
|
||||||
|
No proactive overlay usage.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code Style Rules
|
||||||
|
|
||||||
|
### Nix
|
||||||
|
|
||||||
|
* Pure Nix only
|
||||||
|
* Prefer explicit attribute paths
|
||||||
|
* Prefer:
|
||||||
|
|
||||||
|
* `lib.mkIf`
|
||||||
|
* `lib.mkMerge`
|
||||||
|
* `lib.optionals`
|
||||||
|
* Avoid:
|
||||||
|
|
||||||
|
* `with pkgs;`
|
||||||
|
* implicit imports
|
||||||
|
* inline shell hacks
|
||||||
|
|
||||||
|
### Formatting
|
||||||
|
|
||||||
|
* All Nix code must conform to **`nixfmt-rfc-style`**
|
||||||
|
* Do not reformat unrelated files
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Scripting Policy
|
||||||
|
|
||||||
|
1. **Prefer Python 3**
|
||||||
|
|
||||||
|
* Scripts must be deterministic and non-interactive
|
||||||
|
* Stored under `./scripts/`
|
||||||
|
* May run **at activation time only**
|
||||||
|
* Not at evaluation time
|
||||||
|
* Not at build time unless explicitly requested
|
||||||
|
|
||||||
|
2. **Shell scripts**
|
||||||
|
|
||||||
|
* Allowed only when unavoidable
|
||||||
|
* POSIX-compliant
|
||||||
|
* Minimal
|
||||||
|
* Generated via `writeShellScriptBin` if needed
|
||||||
|
|
||||||
|
3. **Never embed large scripts inline**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Expected Repository Layout
|
||||||
|
|
||||||
|
```text
|
||||||
|
.
|
||||||
|
├── flake.nix
|
||||||
|
├── flake.lock
|
||||||
|
├── hosts/
|
||||||
|
│ └── hostname.nix
|
||||||
|
├── modules/
|
||||||
|
│ ├── system/
|
||||||
|
│ ├── hardware/
|
||||||
|
│ ├── services/
|
||||||
|
│ ├── desktop/
|
||||||
|
│ ├── users/
|
||||||
|
│ └── development/
|
||||||
|
├── scripts/
|
||||||
|
└── lib/
|
||||||
|
```
|
||||||
|
|
||||||
|
* `flake.nix` → authority
|
||||||
|
* `hosts/` → composition
|
||||||
|
* `modules/` → behavior
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Safety & Reproducibility
|
||||||
|
|
||||||
|
* No imperative installs
|
||||||
|
* No network access at evaluation
|
||||||
|
* Inputs must be pinned
|
||||||
|
* All changes must be declarative
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output Rules
|
||||||
|
|
||||||
|
* Plans: **plan only**
|
||||||
|
* Code: **code only**
|
||||||
|
* Questions: **one precise question only**
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
If you want, next we can:
|
||||||
|
- Add **machine-checkable lint rules** derived from this
|
||||||
|
- Write a **migration appendix** for legacy `/etc/nixos`
|
||||||
|
- Create a **Codex system prompt** that mirrors this file exactly
|
||||||
|
```
|
||||||
17
docs/install-help.txt
Normal file
17
docs/install-help.txt
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
NixOS installer (flake + Lix)
|
||||||
|
=============================
|
||||||
|
|
||||||
|
1) Build is prewired with Lix; the installer ISO includes the repo.
|
||||||
|
2) Run the guided Btrfs installer:
|
||||||
|
|
||||||
|
sudo ./scripts/install-btrfs.sh
|
||||||
|
|
||||||
|
- Interactive by default (choose disk, LUKS, swap).
|
||||||
|
- Non-interactive example:
|
||||||
|
sudo NON_INTERACTIVE=1 USE_LUKS=1 LUKS_PASSPHRASE='secret' SWAP_GB=4 DISK=/dev/sda HOSTNAME=myhost ./scripts/install-btrfs.sh
|
||||||
|
|
||||||
|
3) After install finishes, reboot into the new system.
|
||||||
|
|
||||||
|
Notes
|
||||||
|
- Filesystem is always Btrfs with subvolumes: root, home, nix (and swapfile if enabled).
|
||||||
|
- The flake lives under /etc/nixos on the target; edit hosts/<hostname>.nix to customize.
|
||||||
113
flake.lock
generated
Normal file
113
flake.lock
generated
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flakey-profile": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1712898590,
|
||||||
|
"narHash": "sha256-FhGIEU93VHAChKEXx905TSiPZKga69bWl1VB37FK//I=",
|
||||||
|
"owner": "lf-",
|
||||||
|
"repo": "flakey-profile",
|
||||||
|
"rev": "243c903fd8eadc0f63d205665a92d4df91d42d9d",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "lf-",
|
||||||
|
"repo": "flakey-profile",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lix": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1761937274,
|
||||||
|
"narHash": "sha256-KlELhsSq3XbemrGyQhmGurFu7m8wOEBw+8M04L7hn7A=",
|
||||||
|
"rev": "91867941fa73afea7869b7c71ede82e5ef8927da",
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://git.lix.systems/api/v1/repos/lix-project/lix/archive/91867941fa73afea7869b7c71ede82e5ef8927da.tar.gz?rev=91867941fa73afea7869b7c71ede82e5ef8927da"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://git.lix.systems/lix-project/lix/archive/main.tar.gz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lix-module": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"flakey-profile": "flakey-profile",
|
||||||
|
"lix": "lix",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1767364176,
|
||||||
|
"narHash": "sha256-l6YdEBYQxXjD8ujqvc0tKdwWc3K8UQOi+E4Y3DKQ318=",
|
||||||
|
"ref": "refs/heads/main",
|
||||||
|
"rev": "1688100bba140492658d597f6b307c327f35c780",
|
||||||
|
"revCount": 179,
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.lix.systems/lix-project/nixos-module"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.lix.systems/lix-project/nixos-module"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1769741972,
|
||||||
|
"narHash": "sha256-RxSg1EioTWNpoLaykiT1UQKTo/K0PPdLqCyQgNjNqWs=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "63590ac958a8af30ebd52c7a0309d8c52a94dd77",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-25.11",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"lix-module": "lix-module",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
29
flake.nix
Normal file
29
flake.nix
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
description = "NixOS installation ISO with Lix";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
|
||||||
|
lix-module.url = "git+https://git.lix.systems/lix-project/nixos-module";
|
||||||
|
lix-module.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = inputs@{ self, nixpkgs, ... }:
|
||||||
|
let
|
||||||
|
lib = nixpkgs.lib;
|
||||||
|
hostFiles = builtins.attrNames (builtins.readDir ./hosts);
|
||||||
|
hostNames = lib.filter (name:
|
||||||
|
lib.hasSuffix ".nix" name && !(lib.hasSuffix "-hardware.nix" name)
|
||||||
|
) hostFiles;
|
||||||
|
mkHost = name: lib.nixosSystem {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
specialArgs = { inherit inputs; };
|
||||||
|
modules = [ (./hosts + "/${name}.nix") ];
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
nixosConfigurations = lib.genAttrs (map (lib.removeSuffix ".nix") hostNames) mkHost;
|
||||||
|
|
||||||
|
packages.x86_64-linux.install-iso =
|
||||||
|
self.nixosConfigurations.installer.config.system.build.isoImage;
|
||||||
|
packages.x86_64-linux.default = self.packages.x86_64-linux.install-iso;
|
||||||
|
};
|
||||||
|
}
|
||||||
14
hosts/installer.nix
Normal file
14
hosts/installer.nix
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{ config, pkgs, inputs, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
(inputs.nixpkgs + "/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix")
|
||||||
|
inputs.lix-module.nixosModules.lixFromNixpkgs
|
||||||
|
../modules/system/base.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
networking.hostName = "nix-installer";
|
||||||
|
|
||||||
|
environment.etc."INSTALL.txt".source = ../docs/install-help.txt;
|
||||||
|
environment.etc."motd".text = builtins.readFile ../docs/install-help.txt;
|
||||||
|
}
|
||||||
26
modules/system/base.nix
Normal file
26
modules/system/base.nix
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
time.timeZone = "UTC";
|
||||||
|
|
||||||
|
i18n.defaultLocale = "en_US.UTF-8";
|
||||||
|
console.keyMap = "us";
|
||||||
|
|
||||||
|
nix.settings = {
|
||||||
|
experimental-features = [ "nix-command" "flakes" ];
|
||||||
|
substituters = lib.mkBefore [
|
||||||
|
"https://cache.lix.systems"
|
||||||
|
];
|
||||||
|
trusted-public-keys = lib.mkBefore [
|
||||||
|
"cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o="
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
services.openssh.enable = true;
|
||||||
|
|
||||||
|
networking.useDHCP = lib.mkDefault true;
|
||||||
|
|
||||||
|
boot.supportedFilesystems = [ "btrfs" "ext4" "vfat" "xfs" ];
|
||||||
|
|
||||||
|
system.stateVersion = "25.11";
|
||||||
|
}
|
||||||
234
scripts/install-btrfs.sh
Executable file
234
scripts/install-btrfs.sh
Executable file
|
|
@ -0,0 +1,234 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Automated Btrfs install using the flake in this repo.
|
||||||
|
# Defaults remain environment-driven for headless use.
|
||||||
|
# Interactive prompts are shown unless --non-interactive is set.
|
||||||
|
|
||||||
|
NON_INTERACTIVE=0
|
||||||
|
if [[ "${1:-}" == "--non-interactive" ]]; then
|
||||||
|
NON_INTERACTIVE=1
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
|
||||||
|
DISK=${DISK:-/dev/vda} # full-disk target
|
||||||
|
ROOT_PART=${ROOT_PART:-} # optional existing root partition
|
||||||
|
BOOT_PART=${BOOT_PART:-} # optional existing boot (FAT) partition
|
||||||
|
HOSTNAME=${HOSTNAME:-nixos}
|
||||||
|
USE_LUKS=${USE_LUKS:-0} # 1 to encrypt root
|
||||||
|
SWAP_GB=${SWAP_GB:-0} # swap size in GB, 0 disables
|
||||||
|
|
||||||
|
SCRIPT_DIR=$(cd -- "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
|
REPO_ROOT=$(cd -- "${SCRIPT_DIR}/.." && pwd)
|
||||||
|
|
||||||
|
has_cmd() { command -v "$1" >/dev/null 2>&1; }
|
||||||
|
|
||||||
|
prompt_select() {
|
||||||
|
local title="$1" prompt="$2" choices=("${@:3}")
|
||||||
|
if (( NON_INTERACTIVE )); then
|
||||||
|
echo "${choices[0]}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if has_cmd dialog; then
|
||||||
|
dialog --no-cancel --menu "$prompt" 15 60 6 "${choices[@]}" 2> /tmp/choice
|
||||||
|
cat /tmp/choice
|
||||||
|
elif has_cmd whiptail; then
|
||||||
|
whiptail --notags --menu "$prompt" 15 60 6 "${choices[@]}" 2> /tmp/choice || true
|
||||||
|
cat /tmp/choice
|
||||||
|
else
|
||||||
|
echo "$prompt"
|
||||||
|
select c in "${choices[@]}"; do echo "$c"; break; done
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt_yes_no() {
|
||||||
|
local prompt="$1" default="${2:-n}"
|
||||||
|
if (( NON_INTERACTIVE )); then
|
||||||
|
[[ "$default" =~ ^[Yy]$ ]] && echo "yes" || echo "no"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if has_cmd dialog; then
|
||||||
|
if dialog --yesno "$prompt" 8 60; then echo "yes"; else echo "no"; fi
|
||||||
|
elif has_cmd whiptail; then
|
||||||
|
if whiptail --yesno "$prompt" 8 60; then echo "yes"; else echo "no"; fi
|
||||||
|
else
|
||||||
|
read -rp "$prompt [y/N]: " ans
|
||||||
|
[[ "${ans:-$default}" =~ ^[Yy]$ ]] && echo "yes" || echo "no"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt_input() {
|
||||||
|
local prompt="$1" default="$2"
|
||||||
|
if (( NON_INTERACTIVE )); then
|
||||||
|
echo "$default"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if has_cmd dialog; then
|
||||||
|
dialog --inputbox "$prompt" 8 60 "$default" 2>/tmp/input && cat /tmp/input || echo "$default"
|
||||||
|
elif has_cmd whiptail; then
|
||||||
|
whiptail --inputbox "$prompt" 8 60 "$default" 2>/tmp/input && cat /tmp/input || echo "$default"
|
||||||
|
else
|
||||||
|
read -rp "$prompt [$default]: " ans
|
||||||
|
echo "${ans:-$default}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ls_disks() {
|
||||||
|
lsblk -dpno NAME,SIZE,TYPE | awk '$3=="disk"{print $1" "$2}'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (( ! NON_INTERACTIVE )); then
|
||||||
|
mapfile -t disks < <(ls_disks)
|
||||||
|
if ((${#disks[@]})); then
|
||||||
|
disk_choices=()
|
||||||
|
for d in "${disks[@]}"; do
|
||||||
|
disk_choices+=("$d" "")
|
||||||
|
done
|
||||||
|
DISK=$(prompt_select "disk" "Select target disk (will be wiped)" "${disk_choices[@]}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
target_mode=$(prompt_select "mode" "Partitioning mode" \
|
||||||
|
"full-disk" "Use entire disk (wipes!)" \
|
||||||
|
"existing" "Use existing partitions")
|
||||||
|
|
||||||
|
if [[ "$target_mode" == "existing" ]]; then
|
||||||
|
ROOT_PART=$(prompt_input "Root partition (will be formatted btrfs)" "${ROOT_PART:-/dev/vda2}")
|
||||||
|
BOOT_PART=$(prompt_input "Boot EFI partition (FAT32)" "${BOOT_PART:-/dev/vda1}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
USE_LUKS=$(prompt_yes_no "Encrypt root with LUKS?" "n")
|
||||||
|
[[ "$USE_LUKS" == "yes" ]] && USE_LUKS=1 || USE_LUKS=0
|
||||||
|
|
||||||
|
SWAP_GB=$(prompt_input "Swap size in GB (0 = none)" "${SWAP_GB}")
|
||||||
|
HOSTNAME=$(prompt_input "Hostname" "${HOSTNAME}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[0/8] Summary:"
|
||||||
|
echo " Disk: ${DISK}"
|
||||||
|
if [[ -n "$ROOT_PART" ]]; then
|
||||||
|
echo " Root partition: $ROOT_PART"
|
||||||
|
echo " Boot partition: $BOOT_PART"
|
||||||
|
else
|
||||||
|
echo " Mode: full disk wipe"
|
||||||
|
fi
|
||||||
|
echo " LUKS: $([ "$USE_LUKS" -eq 1 ] && echo enabled || echo disabled)"
|
||||||
|
echo " Swap: ${SWAP_GB}G"
|
||||||
|
echo " Hostname: ${HOSTNAME}"
|
||||||
|
|
||||||
|
if (( NON_INTERACTIVE )); then
|
||||||
|
echo "Proceeding in non-interactive mode."
|
||||||
|
else
|
||||||
|
if [[ "$(prompt_yes_no "Proceed with these settings? This will destroy data." "n")" != "yes" ]]; then
|
||||||
|
echo "Aborted."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
part_boot=""
|
||||||
|
part_root=""
|
||||||
|
mapper_root=""
|
||||||
|
|
||||||
|
echo "[1/8] Partitioning/selection"
|
||||||
|
if [[ -z "$ROOT_PART" ]]; then
|
||||||
|
printf "label: gpt\n,550M,U,boot\n,,L,cryptroot\n" | sfdisk "${DISK}"
|
||||||
|
part_boot="${DISK}1"
|
||||||
|
part_root="${DISK}2"
|
||||||
|
else
|
||||||
|
part_boot="${BOOT_PART}"
|
||||||
|
part_root="${ROOT_PART}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[2/8] Formatting boot"
|
||||||
|
mkfs.fat -F 32 "${part_boot}"
|
||||||
|
|
||||||
|
echo "[3/8] Preparing root"
|
||||||
|
if [[ "$USE_LUKS" -eq 1 ]]; then
|
||||||
|
pass=${LUKS_PASSPHRASE:-}
|
||||||
|
if [[ -z "$pass" && $NON_INTERACTIVE -eq 0 ]]; then
|
||||||
|
pass=$(prompt_input "LUKS passphrase" "")
|
||||||
|
fi
|
||||||
|
if [[ -z "$pass" ]]; then
|
||||||
|
echo "LUKS passphrase required for encrypted install"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
printf "%s" "$pass" | cryptsetup luksFormat "${part_root}" -
|
||||||
|
printf "%s" "$pass" | cryptsetup open "${part_root}" cryptroot -
|
||||||
|
mapper_root="/dev/mapper/cryptroot"
|
||||||
|
else
|
||||||
|
mapper_root="${part_root}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkfs.btrfs -f "${mapper_root}"
|
||||||
|
|
||||||
|
echo "[4/8] Creating Btrfs subvolumes"
|
||||||
|
mkdir -p /mnt
|
||||||
|
mount "${mapper_root}" /mnt
|
||||||
|
btrfs subvolume create /mnt/root
|
||||||
|
btrfs subvolume create /mnt/home
|
||||||
|
btrfs subvolume create /mnt/nix
|
||||||
|
umount /mnt
|
||||||
|
|
||||||
|
echo "[5/8] Mounting subvolumes"
|
||||||
|
mount -o compress=zstd,subvol=root "${mapper_root}" /mnt
|
||||||
|
mkdir -p /mnt/{home,nix,boot}
|
||||||
|
mount -o compress=zstd,subvol=home "${mapper_root}" /mnt/home
|
||||||
|
mount -o compress=zstd,subvol=nix "${mapper_root}" /mnt/nix
|
||||||
|
mount "${part_boot}" /mnt/boot
|
||||||
|
|
||||||
|
if [[ "${SWAP_GB}" -gt 0 ]]; then
|
||||||
|
echo "[5b/8] Creating swapfile (${SWAP_GB}G)"
|
||||||
|
mkdir -p /mnt/swap
|
||||||
|
chattr +C /mnt/swap || true
|
||||||
|
fallocate -l "${SWAP_GB}G" /mnt/swap/swapfile
|
||||||
|
chmod 600 /mnt/swap/swapfile
|
||||||
|
mkswap /mnt/swap/swapfile
|
||||||
|
swapon /mnt/swap/swapfile
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[6/8] Copying flake to /etc/nixos"
|
||||||
|
if command -v rsync >/dev/null 2>&1; then
|
||||||
|
rsync -a --exclude '.git' --exclude 'result' --exclude 'node_modules' "${REPO_ROOT}/" /mnt/etc/nixos/
|
||||||
|
else
|
||||||
|
(cd "${REPO_ROOT}" && tar -cf - --exclude=.git --exclude=result --exclude=node_modules .) | (cd /mnt/etc/nixos && tar -xf -)
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[7/8] Generating hardware config"
|
||||||
|
hardware_file="/mnt/etc/nixos/hosts/${HOSTNAME}-hardware.nix"
|
||||||
|
mkdir -p /mnt/etc/nixos/hosts
|
||||||
|
nixos-generate-config --root /mnt --show-hardware-config > "${hardware_file}"
|
||||||
|
|
||||||
|
if [[ "$USE_LUKS" -eq 1 || "${SWAP_GB}" -gt 0 ]]; then
|
||||||
|
tmpfile=$(mktemp)
|
||||||
|
# drop trailing closing brace
|
||||||
|
sed '$d' "${hardware_file}" > "${tmpfile}"
|
||||||
|
if [[ "$USE_LUKS" -eq 1 ]]; then
|
||||||
|
echo " boot.initrd.luks.devices.cryptroot.device = \"${part_root}\";" >> "${tmpfile}"
|
||||||
|
fi
|
||||||
|
if [[ "${SWAP_GB}" -gt 0 ]]; then
|
||||||
|
echo " swapDevices = [ { device = \"/swap/swapfile\"; } ];" >> "${tmpfile}"
|
||||||
|
fi
|
||||||
|
echo "}" >> "${tmpfile}"
|
||||||
|
mv "${tmpfile}" "${hardware_file}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
host_file="/mnt/etc/nixos/hosts/${HOSTNAME}.nix"
|
||||||
|
if [ ! -e "${host_file}" ]; then
|
||||||
|
cat > "${host_file}" <<EOF
|
||||||
|
{ inputs, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
inputs.lix-module.nixosModules.lixFromNixpkgs
|
||||||
|
../modules/system/base.nix
|
||||||
|
./${HOSTNAME}-hardware.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
networking.hostName = "${HOSTNAME}";
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[8/8] Installing ${HOSTNAME} via flake"
|
||||||
|
nixos-install --root /mnt --flake "/mnt/etc/nixos#${HOSTNAME}"
|
||||||
|
|
||||||
|
echo "Install complete. You can reboot now."
|
||||||
Loading…
Add table
Add a link
Reference in a new issue