Compare commits
1 commit
main
...
netboot-in
Author | SHA1 | Date | |
---|---|---|---|
|
4f8745bb75 |
33 changed files with 473 additions and 1214 deletions
13
.github/workflows/build.yml
vendored
13
.github/workflows/build.yml
vendored
|
@ -11,18 +11,15 @@ jobs:
|
|||
fail-fast: false
|
||||
matrix:
|
||||
tag:
|
||||
- nixos-24.05
|
||||
- nixos-22.11
|
||||
- nixos-unstable
|
||||
os:
|
||||
- nscloud-ubuntu-22.04-arm64-4x16
|
||||
- ubuntu-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v20
|
||||
with:
|
||||
nix_path: nixpkgs=https://github.com/NixOS/nixpkgs/archive/nixpkgs-unstable.tar.gz
|
||||
- name: Build image
|
||||
run: ./build-images.sh "${{ matrix.tag }}" "$(nix eval --raw --impure --expr builtins.currentSystem)"
|
||||
run: ./build-images.sh "${{matrix.tag}}"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
15
.github/workflows/update-flake-lock.yml
vendored
15
.github/workflows/update-flake-lock.yml
vendored
|
@ -9,11 +9,16 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v30
|
||||
uses: cachix/install-nix-action@v20
|
||||
- name: Update flake.lock
|
||||
uses: DeterminateSystems/update-flake-lock@v24
|
||||
uses: DeterminateSystems/update-flake-lock@v19
|
||||
with:
|
||||
pr-labels: |
|
||||
merge-queue
|
||||
token: ${{ secrets.GH_TOKEN_FOR_UPDATES }}
|
||||
pr-body: |
|
||||
Automated changes by the update-flake-lock
|
||||
```
|
||||
{{ env.GIT_COMMIT_MESSAGE }}
|
||||
```
|
||||
bors merge
|
||||
|
|
15
.mergify.yml
15
.mergify.yml
|
@ -1,15 +0,0 @@
|
|||
queue_rules:
|
||||
- name: default
|
||||
merge_conditions:
|
||||
- check-success=buildbot/nix-build
|
||||
defaults:
|
||||
actions:
|
||||
queue:
|
||||
merge_method: rebase
|
||||
pull_request_rules:
|
||||
- name: merge using the merge queue
|
||||
conditions:
|
||||
- base=main
|
||||
- label~=merge-queue|dependencies
|
||||
actions:
|
||||
queue: {}
|
43
README.md
43
README.md
|
@ -3,18 +3,21 @@
|
|||
Automatically weekly updated images for NixOS. This project is intended to extend the images created by hydra.nixos.org.
|
||||
We are currently creating the images listed below:
|
||||
|
||||
## Kexec tarballs
|
||||
## Netboot images
|
||||
|
||||
These images are used for unattended remote installation in [nixos-anywhere](https://github.com/numtide/nixos-anywhere).
|
||||
You can boot the netboot image using this [ipxe script](https://github.com/nix-community/nixos-images/releases/download/nixos-unstable/netboot-x86_64-linux.ipxe).
|
||||
It consists of the [kernel image](https://github.com/nix-community/nixos-images/releases/download/nixos-unstable/bzImage-x86_64-linux) and [initrd](https://github.com/nix-community/nixos-images/releases/download/nixos-unstable/bzImage-x86_64-linux).
|
||||
|
||||
## Kexec tarballs
|
||||
|
||||
Kexec is a mechanism in Linux to load a new kernel from a running Linux to
|
||||
replace the current kernel. This is useful for booting the Nixos installer from
|
||||
existing Linux distributions, such as server provider that do not offer a NixOS
|
||||
option. After running kexec, the NixOS installer exists only in memory. At the
|
||||
time of writing, this requires secure boot off in BIOS settings and at least 1GB
|
||||
of physical RAM (swap does not count) in the system. If not enough RAM is available,
|
||||
the initrd cannot be loaded. Because the NixOS runs only in RAM, users can reformat
|
||||
all the system's discs to prepare for a new NixOS installation.
|
||||
time of writing, this requires at least 1.5GB of physical RAM (swap does not
|
||||
count) in the system. If not enough RAM is available, the initrd cannot be
|
||||
loaded. Because the NixOS runs only in RAM, users can reformat all the system's
|
||||
discs to prepare for a new NixOS installation.
|
||||
|
||||
It can be booted as follows by running these commands as root:
|
||||
|
||||
|
@ -27,32 +30,12 @@ The kexec installer comes with the following features:
|
|||
|
||||
- Re-uses ssh host keys from the sshd to not break `.ssh/known_hosts`
|
||||
- Authorized ssh keys are read from `/root/.ssh/authorized_keys`, `/root/.ssh/authorized_keys2` and `/etc/ssh/authorized_keys.d/root`
|
||||
- Static ip addresses and routes are restored after reboot.
|
||||
Interface that had dynamic addresses before are configured with DHCP and
|
||||
to accept prefixes from ipv6 router advertisement
|
||||
- (experimental, only tested for nixos-unstable) Static ip addresses and routes
|
||||
are restored after reboot. Interface that had dynamic addresses before are
|
||||
configured with DHCP and to accept prefixes from ipv6 router advertisment
|
||||
|
||||
|
||||
The actual kexec happens with a slight delay (6s). This allows for easier
|
||||
integration into automated nixos installation scripts, since you can cleanly
|
||||
disconnect from the running machine before the kexec takes place. The tarball
|
||||
is also designed to be run from NixOS, which can be useful for new installations
|
||||
|
||||
## Iso installer images
|
||||
|
||||
This image allows to boot a NixOS installer off a USB-Stick.
|
||||
This installer has been optimized for remote installation i.e.
|
||||
with [nixos-anywhere](https://github.com/numtide/nixos-anywhere) and [clan](https://docs.clan.lol/getting-started/installer/) notably:
|
||||
|
||||
* Enables openssh by default
|
||||
* Generates a random root password on each login
|
||||
* Enables a Tor hidden SSH service so that by using the `torify ssh <hash>.onion`,
|
||||
one can log in from remote machines.
|
||||
* Prints a QR-Code that contains local addresses, the root password
|
||||
* Includes the [IWD](https://wiki.archlinux.org/title/iwd) deamon for easier wifi setups:
|
||||
* Run `iwctl` in the terminal for an interactive wifi setup interface.
|
||||
|
||||
![Screenshot of the installer](https://github.com/nix-community/nixos-images/releases/download/assets/image-installer-screenshot.jpg)
|
||||
|
||||
## Netboot images
|
||||
|
||||
You can boot the netboot image using this [ipxe script](https://github.com/nix-community/nixos-images/releases/download/nixos-unstable/netboot-x86_64-linux.ipxe).
|
||||
It consists of the [kernel image](https://github.com/nix-community/nixos-images/releases/download/nixos-unstable/bzImage-x86_64-linux) and [initrd](https://github.com/nix-community/nixos-images/releases/download/nixos-unstable/initrd-x86_64-linux).
|
||||
|
|
10
bors.toml
Normal file
10
bors.toml
Normal file
|
@ -0,0 +1,10 @@
|
|||
cut_body_after = "" # don't include text from the PR body in the merge commit message
|
||||
status = [
|
||||
# garnix
|
||||
"Evaluate flake.nix",
|
||||
"package netboot-nixos-2211 [x86_64-linux]",
|
||||
"package kexec-installer-nixos-2211 [x86_64-linux]",
|
||||
"package kexec-installer-nixos-unstable [x86_64-linux]",
|
||||
"check kexec-installer-unstable [x86_64-linux]",
|
||||
"package netboot-nixos-unstable [x86_64-linux]"
|
||||
]
|
|
@ -5,54 +5,45 @@ set -xeuo pipefail
|
|||
shopt -s lastpipe
|
||||
|
||||
build_netboot_image() {
|
||||
declare -r tag=$1 channel=$2 arch=$3 tmp=$4
|
||||
img=$(nix build --print-out-paths --option accept-flake-config true -L ".#packages.${arch}.netboot-nixos-${channel//./}")
|
||||
kernel=$(echo "$img"/*Image)
|
||||
kernelName=$(basename "$kernel")
|
||||
ln -s "$kernel" "$tmp/$kernelName-$arch"
|
||||
echo "$tmp/$kernelName-$arch"
|
||||
declare -r tag=$1 arch=$2 tmp=$3
|
||||
img=$(nix build --print-out-paths --option accept-flake-config true -L ".#packages.${arch}.netboot-${tag//.}")
|
||||
ln -s "$img/bzImage" "$tmp/bzImage-$arch"
|
||||
echo "$tmp/bzImage-$arch"
|
||||
ln -s "$img/initrd" "$tmp/initrd-$arch"
|
||||
echo "$tmp/initrd-$arch"
|
||||
sed -e "s!^kernel $kernelName!kernel https://github.com/nix-community/nixos-images/releases/download/${tag}/$kernelName-${arch}!" \
|
||||
sed -e "s!^kernel bzImage!kernel https://github.com/nix-community/nixos-images/releases/download/${tag}/bzImage-${arch}!" \
|
||||
-e "s!^initrd initrd!initrd https://github.com/nix-community/nixos-images/releases/download/${tag}/initrd-${arch}!" \
|
||||
-e "s!initrd=initrd!initrd=initrd-${arch}!" \
|
||||
<"$img/netboot.ipxe" \
|
||||
>"$tmp/netboot-$arch.ipxe"
|
||||
< "$img/netboot.ipxe" \
|
||||
> "$tmp/netboot-$arch.ipxe"
|
||||
echo "$tmp/netboot-$arch.ipxe"
|
||||
}
|
||||
|
||||
build_kexec_installer() {
|
||||
declare -r channel=$1 arch=$2 tmp=$3 variant=$4
|
||||
out=$(nix build --print-out-paths --option accept-flake-config true -L ".#packages.${arch}.kexec-installer-nixos-${channel}${variant}")
|
||||
declare -r tag=$1 arch=$2 tmp=$3 variant=$4
|
||||
out=$(nix build --print-out-paths --option accept-flake-config true -L ".#packages.${arch}.kexec-installer-${tag//.}${variant}")
|
||||
echo "$out/nixos-kexec-installer${variant}-$arch.tar.gz"
|
||||
}
|
||||
|
||||
build_image_installer() {
|
||||
declare -r channel=$1 arch=$2 tmp=$3
|
||||
out=$(nix build --print-out-paths --option accept-flake-config true -L ".#packages.${arch}.image-installer-nixos-${channel//./}")
|
||||
echo "$out/iso/nixos-installer-${arch}.iso"
|
||||
}
|
||||
|
||||
main() {
|
||||
declare -r tag=${1:-nixos-unstable} arch=${2:-x86_64-linux}
|
||||
tmp="$(mktemp -d)"
|
||||
trap 'rm -rf -- "$tmp"' EXIT
|
||||
(
|
||||
channel=$(if [[ "$tag" == nixos-unstable ]]; then echo "unstable"; else echo "stable"; fi)
|
||||
build_kexec_installer "$channel" "$arch" "$tmp" ""
|
||||
build_kexec_installer "$channel" "$arch" "$tmp" "-noninteractive"
|
||||
build_netboot_image "$tag" "$channel" "$arch" "$tmp"
|
||||
build_image_installer "$channel" "$arch" "$tmp"
|
||||
build_kexec_installer "$tag" "$arch" "$tmp" ""
|
||||
build_kexec_installer "$tag" "$arch" "$tmp" "-noninteractive"
|
||||
build_netboot_image "$tag" "$arch" "$tmp"
|
||||
) | readarray -t assets
|
||||
for asset in "${assets[@]}"; do
|
||||
pushd "$(dirname "$asset")"
|
||||
sha256sum "$(basename "$asset")" >> "$TMP/sha256sums"
|
||||
popd
|
||||
done
|
||||
assets+=("$TMP/sha256sums")
|
||||
|
||||
if ! gh release view "$tag"; then
|
||||
gh release create --title "$tag (build $(date +"%Y-%m-%d"))" "$tag"
|
||||
fi
|
||||
gh release upload --clobber "$tag" "${assets[@]}"
|
||||
# Since we cannot atomically update a release, we delete the old one before
|
||||
gh release delete "$tag" </dev/null || true
|
||||
gh release create --title "$tag (build $(date +"%Y-%m-%d"))" "$tag" "${assets[@]}" </dev/null
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
|
53
flake.lock
53
flake.lock
|
@ -1,28 +1,46 @@
|
|||
{
|
||||
"nodes": {
|
||||
"nixos-stable": {
|
||||
"disko": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1729181673,
|
||||
"narHash": "sha256-LDiPhQ3l+fBjRATNtnuDZsBS7hqoBtPkKBkhpoBHv3I=",
|
||||
"lastModified": 1683508929,
|
||||
"narHash": "sha256-AqkIrwewCL8+zlkqhNxheF+kOfyakzZDk43SqRTIqRE=",
|
||||
"owner": "nix-community",
|
||||
"repo": "disko",
|
||||
"rev": "2a59f5cf641607dbecb0cfec3ae32247e4aeb311",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "disko",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixos-2211": {
|
||||
"locked": {
|
||||
"lastModified": 1681932375,
|
||||
"narHash": "sha256-tSXbYmpnKSSWpzOrs27ie8X3I0yqKA6AuCzCYNtwbCU=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "4eb33fe664af7b41a4c446f87d20c9a0a6321fa3",
|
||||
"rev": "3d302c67ab8647327dba84fbdb443cdbf0e82744",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-24.05",
|
||||
"ref": "release-22.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixos-unstable": {
|
||||
"locked": {
|
||||
"lastModified": 1729450260,
|
||||
"narHash": "sha256-3GNZr0V4b19RZ5mlyiY/4F8N2pzitvjDU6aHMWjAqLI=",
|
||||
"lastModified": 1681914506,
|
||||
"narHash": "sha256-frb95rhVUKAeRdHKfD2vbO1kv8U+G9JMAoLHLCRPNa4=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "e3f55158e7587c5a5fdb0e86eb7ca4f455f0928f",
|
||||
"rev": "cafa2f02fbbcade5c5c257c190061da555d90913",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -32,9 +50,26 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1683442750,
|
||||
"narHash": "sha256-IiJ0WWW6OcCrVFl1ijE+gTaP0ChFfV6dNkJR05yStmw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "eb751d65225ec53de9cf3d88acbf08d275882389",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixos-stable": "nixos-stable",
|
||||
"disko": "disko",
|
||||
"nixos-2211": "nixos-2211",
|
||||
"nixos-unstable": "nixos-unstable"
|
||||
}
|
||||
}
|
||||
|
|
103
flake.nix
103
flake.nix
|
@ -2,84 +2,61 @@
|
|||
description = "NixOS images";
|
||||
|
||||
inputs.nixos-unstable.url = "github:NixOS/nixpkgs/nixos-unstable-small";
|
||||
inputs.nixos-stable.url = "github:NixOS/nixpkgs/nixos-24.05";
|
||||
inputs.nixos-2211.url = "github:NixOS/nixpkgs/release-22.11";
|
||||
inputs.disko.url = "github:nix-community/disko";
|
||||
|
||||
nixConfig.extra-substituters = [ "https://nix-community.cachix.org" ];
|
||||
nixConfig.extra-trusted-public-keys = [ "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" ];
|
||||
nixConfig.extra-substituters = [
|
||||
"https://cache.garnix.io"
|
||||
];
|
||||
nixConfig.extra-trusted-public-keys = [
|
||||
"cache.garnix.io:CTFPyKSLcx5RMJKfLo5EEPUObbA78b0YQ2DTCJXqr9g="
|
||||
];
|
||||
|
||||
outputs = { self, nixos-unstable, nixos-stable }:
|
||||
let
|
||||
supportedSystems = [ "riscv64-linux" ];
|
||||
outputs = { self, nixos-unstable, nixos-2211, disko }: let
|
||||
supportedSystems = [ "aarch64-linux" "x86_64-linux" ];
|
||||
forAllSystems = nixos-unstable.lib.genAttrs supportedSystems;
|
||||
in
|
||||
{
|
||||
hydraJobs = { inherit (self) checks; };
|
||||
packages = forAllSystems (system:
|
||||
let
|
||||
netboot = nixpkgs: (import (nixpkgs + "/nixos/release.nix") { }).netboot.${system};
|
||||
kexec-installer = nixpkgs: module: (nixpkgs.legacyPackages.${system}.nixos [ module self.nixosModules.kexec-installer ]).config.system.build.kexecTarball;
|
||||
netboot-installer = nixpkgs: (nixpkgs.legacyPackages.${system}.nixos [ self.nixosModules.netboot-installer ]).config.system.build.netboot;
|
||||
image-installer = nixpkgs: (nixpkgs.legacyPackages.${system}.nixos [ self.nixosModules.image-installer ]).config.system.build.isoImage;
|
||||
in
|
||||
{
|
||||
in {
|
||||
packages = forAllSystems (system: let
|
||||
netboot = nixpkgs: (import (nixpkgs + "/nixos/release.nix") {}).netboot.${system};
|
||||
kexec-installer = nixpkgs: modules: (nixpkgs.legacyPackages.${system}.nixos (modules ++ [self.nixosModules.kexec-installer])).config.system.build.kexecTarball;
|
||||
netboot-installer = nixpkgs: (nixpkgs.legacyPackages.${system}.nixos [self.nixosModules.netboot-installer]).config.system.build.netboot;
|
||||
in {
|
||||
netboot-nixos-unstable = netboot nixos-unstable;
|
||||
netboot-nixos-stable = netboot nixos-stable;
|
||||
kexec-installer-nixos-unstable = kexec-installer nixos-unstable {};
|
||||
kexec-installer-nixos-stable = kexec-installer nixos-stable {};
|
||||
netboot-nixos-2211 = netboot nixos-2211;
|
||||
kexec-installer-nixos-unstable = kexec-installer nixos-unstable [];
|
||||
kexec-installer-nixos-2211 = kexec-installer nixos-2211 [];
|
||||
|
||||
image-installer-nixos-unstable = image-installer nixos-unstable;
|
||||
image-installer-nixos-stable = image-installer nixos-stable;
|
||||
|
||||
kexec-installer-nixos-unstable-noninteractive = kexec-installer nixos-unstable {
|
||||
_file = __curPos.file;
|
||||
system.kexec-installer.name = "nixos-kexec-installer-noninteractive";
|
||||
imports = [
|
||||
kexec-installer-nixos-unstable-noninteractive = kexec-installer nixos-unstable [
|
||||
{ system.kexec-installer.name = "nixos-kexec-installer-noninteractive"; }
|
||||
self.nixosModules.noninteractive
|
||||
({pkgs, ...}: {
|
||||
boot.kernelPackages = disko.legacyPackages.${pkgs.hostPlatform.system}.linuxPackages_bcachefs;
|
||||
})
|
||||
];
|
||||
kexec-installer-nixos-2211-noninteractive = kexec-installer nixos-2211 [
|
||||
{ system.kexec-installer.name = "nixos-kexec-installer-noninteractive"; }
|
||||
self.nixosModules.noninteractive
|
||||
];
|
||||
};
|
||||
kexec-installer-nixos-stable-noninteractive = kexec-installer nixos-stable {
|
||||
_file = __curPos.file;
|
||||
system.kexec-installer.name = "nixos-kexec-installer-noninteractive";
|
||||
imports = [
|
||||
self.nixosModules.noninteractive
|
||||
];
|
||||
};
|
||||
|
||||
netboot-installer-nixos-unstable = netboot-installer nixos-unstable;
|
||||
netboot-installer-nixos-stable = netboot-installer nixos-stable;
|
||||
netboot-installer-nixos-2211 = netboot-installer nixos-2211;
|
||||
});
|
||||
nixosModules = {
|
||||
kexec-installer = ./nix/kexec-installer/module.nix;
|
||||
noninteractive = ./nix/noninteractive.nix;
|
||||
# TODO: also add a test here once we have https://github.com/NixOS/nixpkgs/pull/228346 merged
|
||||
netboot-installer = ./nix/netboot-installer/module.nix;
|
||||
image-installer = ./nix/image-installer/module.nix;
|
||||
};
|
||||
checks =
|
||||
let
|
||||
# re-export the packages as checks
|
||||
packages = forAllSystems (system: nixos-unstable.lib.mapAttrs' (n: nixos-unstable.lib.nameValuePair "package-${n}") self.packages.${system});
|
||||
checks =
|
||||
let
|
||||
pkgsUnstable = nixos-unstable.legacyPackages.riscv64-linux;
|
||||
pkgsStable = nixos-stable.legacyPackages.riscv64-linux;
|
||||
|
||||
bootTests = pkgs: channel: suffix: pkgs.lib.mapAttrs' (name: pkgs.lib.nameValuePair "${name}${suffix}") (pkgs.callPackages ./nix/image-installer/tests.nix {
|
||||
nixpkgs = channel;
|
||||
nixosModules = self.nixosModules;
|
||||
});
|
||||
in
|
||||
{
|
||||
kexec-installer-unstable = pkgsUnstable.callPackage ./nix/kexec-installer/test.nix {
|
||||
kexecTarball = self.packages.riscv64-linux.kexec-installer-nixos-unstable-noninteractive;
|
||||
checks.x86_64-linux = let
|
||||
pkgs = nixos-unstable.legacyPackages.x86_64-linux;
|
||||
in {
|
||||
kexec-installer-unstable = pkgs.callPackage ./nix/kexec-installer/test.nix {};
|
||||
shellcheck = pkgs.runCommand "shellcheck" {
|
||||
nativeBuildInputs = [ pkgs.shellcheck ];
|
||||
} ''
|
||||
shellcheck ${(pkgs.nixos [self.nixosModules.kexec-installer]).config.system.build.kexecRun}
|
||||
touch $out
|
||||
'';
|
||||
kexec-installer-2211 = nixos-2211.legacyPackages.x86_64-linux.callPackage ./nix/kexec-installer/test.nix {};
|
||||
};
|
||||
|
||||
kexec-installer-stable = nixos-stable.legacyPackages.riscv64-linux.callPackage ./nix/kexec-installer/test.nix {
|
||||
kexecTarball = self.packages.riscv64-linux.kexec-installer-nixos-stable-noninteractive;
|
||||
};
|
||||
} // (bootTests pkgsUnstable nixos-unstable "-nixos-unstable")
|
||||
// (bootTests pkgsStable nixos-stable "-nixos-stable");
|
||||
in
|
||||
nixos-unstable.lib.recursiveUpdate packages { riscv64-linux = checks; };
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
{
|
||||
options.hidden-ssh-announce = {
|
||||
enable = lib.mkEnableOption "hidden-ssh-announce";
|
||||
script = lib.mkOption {
|
||||
type = lib.types.package;
|
||||
default = pkgs.writers.writeDash "test-output" "echo $1";
|
||||
description = ''
|
||||
script to run when the hidden tor service was started and they hostname is known.
|
||||
takes the hostname as $1
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.hidden-ssh-announce.enable {
|
||||
services.openssh.enable = true;
|
||||
services.tor = {
|
||||
enable = true;
|
||||
relay.onionServices.hidden-ssh = {
|
||||
version = 3;
|
||||
map = [
|
||||
{
|
||||
port = 22;
|
||||
target.port = 22;
|
||||
}
|
||||
];
|
||||
};
|
||||
client.enable = true;
|
||||
};
|
||||
systemd.services.hidden-ssh-announce = {
|
||||
description = "announce hidden ssh";
|
||||
after = [
|
||||
"tor.service"
|
||||
"network-online.target"
|
||||
];
|
||||
wants = [
|
||||
"tor.service"
|
||||
"network-online.target"
|
||||
];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
# ${pkgs.tor}/bin/torify
|
||||
ExecStart = pkgs.writeShellScript "announce-hidden-service" ''
|
||||
set -efu
|
||||
until test -e ${config.services.tor.settings.DataDirectory}/onion/hidden-ssh/hostname; do
|
||||
echo "still waiting for ${config.services.tor.settings.DataDirectory}/onion/hidden-ssh/hostname"
|
||||
sleep 1
|
||||
done
|
||||
|
||||
${config.hidden-ssh-announce.script} "$(cat ${config.services.tor.settings.DataDirectory}/onion/hidden-ssh/hostname)"
|
||||
'';
|
||||
PrivateTmp = "true";
|
||||
User = "tor";
|
||||
Type = "oneshot";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
{
|
||||
lib,
|
||||
pkgs,
|
||||
modulesPath,
|
||||
...
|
||||
}:
|
||||
let
|
||||
network-status = pkgs.writeShellScriptBin "network-status" ''
|
||||
export PATH=${
|
||||
lib.makeBinPath (
|
||||
with pkgs;
|
||||
[
|
||||
iproute2
|
||||
coreutils
|
||||
gnugrep
|
||||
nettools
|
||||
gum
|
||||
]
|
||||
)
|
||||
}
|
||||
set -efu -o pipefail
|
||||
msgs=()
|
||||
if [[ -e /var/shared/qrcode.utf8 ]]; then
|
||||
qrcode=$(gum style --border-foreground 240 --border normal "$(< /var/shared/qrcode.utf8)")
|
||||
msgs+=("$qrcode")
|
||||
fi
|
||||
network_status="Root password: $(cat /var/shared/root-password)
|
||||
Local network addresses:
|
||||
$(ip -brief -color addr | grep -v 127.0.0.1)
|
||||
$([[ -e /var/shared/onion-hostname ]] && echo "Onion address: $(cat /var/shared/onion-hostname)" || echo "Onion address: Waiting for tor network to be ready...")
|
||||
Multicast DNS: $(hostname).local"
|
||||
network_status=$(gum style --border-foreground 240 --border normal "$network_status")
|
||||
msgs+=("$network_status")
|
||||
msgs+=("Press 'Ctrl-C' for console access")
|
||||
|
||||
gum join --vertical "''${msgs[@]}"
|
||||
'';
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
(modulesPath + "/installer/cd-dvd/installation-cd-base.nix")
|
||||
../installer.nix
|
||||
../noveau-workaround.nix
|
||||
./hidden-ssh-announcement.nix
|
||||
./wifi.nix
|
||||
];
|
||||
systemd.tmpfiles.rules = [ "d /var/shared 0777 root root - -" ];
|
||||
services.openssh.settings.PermitRootLogin = "yes";
|
||||
system.activationScripts.root-password = ''
|
||||
mkdir -p /var/shared
|
||||
${pkgs.xkcdpass}/bin/xkcdpass --numwords 3 --delimiter - --count 1 > /var/shared/root-password
|
||||
echo "root:$(cat /var/shared/root-password)" | chpasswd
|
||||
'';
|
||||
hidden-ssh-announce = {
|
||||
enable = true;
|
||||
script = pkgs.writeShellScript "write-hostname" ''
|
||||
set -efu
|
||||
export PATH=${
|
||||
lib.makeBinPath (
|
||||
with pkgs;
|
||||
[
|
||||
iproute2
|
||||
coreutils
|
||||
jq
|
||||
qrencode
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
mkdir -p /var/shared
|
||||
echo "$1" > /var/shared/onion-hostname
|
||||
local_addrs=$(ip -json addr | jq '[map(.addr_info) | flatten | .[] | select(.scope == "global") | .local]')
|
||||
jq -nc \
|
||||
--arg password "$(cat /var/shared/root-password)" \
|
||||
--arg onion_address "$(cat /var/shared/onion-hostname)" \
|
||||
--argjson local_addrs "$local_addrs" \
|
||||
'{ pass: $password, tor: $onion_address, addrs: $local_addrs }' \
|
||||
> /var/shared/login.json
|
||||
cat /var/shared/login.json | qrencode -s 2 -m 2 -t utf8 -o /var/shared/qrcode.utf8
|
||||
'';
|
||||
};
|
||||
|
||||
services.getty.autologinUser = lib.mkForce "root";
|
||||
|
||||
console.earlySetup = true;
|
||||
console.font = lib.mkDefault "${pkgs.terminus_font}/share/consolefonts/ter-u22n.psf.gz";
|
||||
|
||||
environment.systemPackages = [ network-status ];
|
||||
|
||||
# Less ipv6 addresses to reduce the noise
|
||||
networking.tempAddresses = "disabled";
|
||||
|
||||
# Tango theme: https://yayachiken.net/en/posts/tango-colors-in-terminal/
|
||||
console.colors = lib.mkDefault [
|
||||
"000000"
|
||||
"CC0000"
|
||||
"4E9A06"
|
||||
"C4A000"
|
||||
"3465A4"
|
||||
"75507B"
|
||||
"06989A"
|
||||
"D3D7CF"
|
||||
"555753"
|
||||
"EF2929"
|
||||
"8AE234"
|
||||
"FCE94F"
|
||||
"739FCF"
|
||||
"AD7FA8"
|
||||
"34E2E2"
|
||||
"EEEEEC"
|
||||
];
|
||||
|
||||
programs.bash.interactiveShellInit = ''
|
||||
if [[ "$(tty)" =~ /dev/(tty1|hvc0|ttyS0)$ ]]; then
|
||||
# workaround for https://github.com/NixOS/nixpkgs/issues/219239
|
||||
systemctl restart systemd-vconsole-setup.service
|
||||
|
||||
watch --no-title --color ${network-status}/bin/network-status
|
||||
fi
|
||||
'';
|
||||
|
||||
# No one got time for xz compression.
|
||||
isoImage.squashfsCompression = "zstd";
|
||||
isoImage.isoName = lib.mkForce "nixos-installer-${pkgs.system}.iso";
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
nixpkgs,
|
||||
nixos,
|
||||
nixosModules,
|
||||
}:
|
||||
|
||||
let
|
||||
testConfig = (
|
||||
nixos [
|
||||
(
|
||||
{ modulesPath, ... }:
|
||||
{
|
||||
imports = [
|
||||
nixosModules.image-installer
|
||||
"${modulesPath}/testing/test-instrumentation.nix"
|
||||
];
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
iso = testConfig.config.system.build.isoImage;
|
||||
mkStartCommand =
|
||||
{
|
||||
memory ? 2048,
|
||||
cdrom ? null,
|
||||
usb ? null,
|
||||
uefi ? false,
|
||||
extraFlags ? [ ],
|
||||
}:
|
||||
let
|
||||
qemu-common = import (nixpkgs + "/nixos/lib/qemu-common.nix") { inherit lib pkgs; };
|
||||
qemu = qemu-common.qemuBinary pkgs.qemu_test;
|
||||
|
||||
flags =
|
||||
[
|
||||
"-m"
|
||||
(toString memory)
|
||||
"-netdev"
|
||||
"user,id=net0"
|
||||
"-device"
|
||||
"virtio-net-pci,netdev=net0"
|
||||
]
|
||||
++ lib.optionals (cdrom != null) [
|
||||
"-cdrom"
|
||||
cdrom
|
||||
]
|
||||
++ lib.optionals (usb != null) [
|
||||
"-device"
|
||||
"usb-ehci"
|
||||
"-drive"
|
||||
"id=usbdisk,file=${usb},if=none,readonly"
|
||||
"-device"
|
||||
"usb-storage,drive=usbdisk"
|
||||
]
|
||||
++ lib.optionals uefi [
|
||||
"-drive"
|
||||
"if=pflash,format=raw,unit=0,readonly=on,file=${pkgs.OVMF.firmware}"
|
||||
"-drive"
|
||||
"if=pflash,format=raw,unit=1,readonly=on,file=${pkgs.OVMF.variables}"
|
||||
]
|
||||
++ extraFlags;
|
||||
|
||||
flagsStr = lib.concatStringsSep " " flags;
|
||||
in
|
||||
"${qemu} ${flagsStr}";
|
||||
|
||||
makeBootTest =
|
||||
name: config:
|
||||
let
|
||||
startCommand = mkStartCommand config;
|
||||
in
|
||||
pkgs.testers.runNixOSTest {
|
||||
name = "boot-${name}";
|
||||
nodes = { };
|
||||
testScript = ''
|
||||
machine = create_machine("${startCommand}")
|
||||
machine.start()
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
machine.succeed("nix store verify --no-trust -r --option experimental-features nix-command /run/current-system")
|
||||
|
||||
machine.shutdown()
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
uefi-cdrom = makeBootTest "uefi-cdrom" {
|
||||
uefi = true;
|
||||
cdrom = "${iso}/iso/nixos-installer-${pkgs.hostPlatform.system}.iso";
|
||||
};
|
||||
|
||||
uefi-usb = makeBootTest "uefi-usb" {
|
||||
uefi = true;
|
||||
usb = "${iso}/iso/nixos-installer-${pkgs.hostPlatform.system}.iso";
|
||||
};
|
||||
|
||||
bios-cdrom = makeBootTest "bios-cdrom" {
|
||||
cdrom = "${iso}/iso/nixos-installer-${pkgs.hostPlatform.system}.iso";
|
||||
};
|
||||
|
||||
bios-usb = makeBootTest "bios-usb" {
|
||||
usb = "${iso}/iso/nixos-installer-${pkgs.hostPlatform.system}.iso";
|
||||
};
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
imports = [ ../networkd.nix ];
|
||||
# use iwd instead of wpa_supplicant
|
||||
networking.wireless.enable = false;
|
||||
|
||||
# Use iwd instead of wpa_supplicant. It has a user friendly CLI
|
||||
networking.wireless.iwd = {
|
||||
enable = true;
|
||||
settings = {
|
||||
Network = {
|
||||
EnableIPv6 = true;
|
||||
RoutePriorityOffset = 300;
|
||||
};
|
||||
Settings.AutoConnect = true;
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
{
|
||||
imports = [
|
||||
# ./latest-zfs-kernel.nix
|
||||
./nix-settings.nix
|
||||
];
|
||||
# more descriptive hostname than just "nixos"
|
||||
networking.hostName = lib.mkDefault "nixos-installer";
|
||||
|
||||
# We are stateless, so just default to latest.
|
||||
system.stateVersion = config.system.nixos.version;
|
||||
|
||||
# Enable bcachefs support
|
||||
boot.supportedFilesystems.bcachefs = lib.mkDefault true;
|
||||
|
||||
# use latest kernel we can support to get more hardware support
|
||||
# boot.zfs.package = pkgs.zfsUnstable;
|
||||
|
||||
documentation.enable = false;
|
||||
documentation.man.man-db.enable = false;
|
||||
|
||||
# make it easier to debug boot failures
|
||||
boot.initrd.systemd.emergencyAccess = true;
|
||||
|
||||
environment.systemPackages = [
|
||||
pkgs.nixos-install-tools
|
||||
# for zapping of disko
|
||||
pkgs.jq
|
||||
# for copying extra files of nixos-anywhere
|
||||
pkgs.rsync
|
||||
# alternative to nixos-generate-config
|
||||
# TODO: use nixpkgs again after next nixos release
|
||||
(pkgs.callPackage ./nixos-facter.nix {})
|
||||
|
||||
pkgs.disko
|
||||
];
|
||||
|
||||
# Don't add nixpkgs to the image to save space, for our intended use case we don't need it
|
||||
system.installer.channel.enable = false;
|
||||
}
|
48
nix/kexec-installer/kexec-run.sh
Executable file → Normal file
48
nix/kexec-installer/kexec-run.sh
Executable file → Normal file
|
@ -1,19 +1,6 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -ex
|
||||
|
||||
kexec_extra_flags=""
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--kexec-extra-flags)
|
||||
kexec_extra_flags="$2"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# provided by nix
|
||||
init="@init@"
|
||||
kernelParams="@kernelParams@"
|
||||
|
@ -34,16 +21,12 @@ extractPubKeys() {
|
|||
key="$home/$file"
|
||||
if test -e "$key"; then
|
||||
# workaround for debian shenanigans
|
||||
grep -o '\(\(ssh\|ecdsa\|sk\)-[^ ]* .*\)' "$key" >> ssh/authorized_keys || true
|
||||
grep -o '\(ssh-[^ ]* .*\)' "$key" >> ssh/authorized_keys || true
|
||||
fi
|
||||
done
|
||||
}
|
||||
extractPubKeys /root
|
||||
|
||||
if test -n "${DOAS_USER-}"; then
|
||||
SUDO_USER="$DOAS_USER"
|
||||
fi
|
||||
|
||||
if test -n "${SUDO_USER-}"; then
|
||||
sudo_home=$(sh -c "echo ~$SUDO_USER")
|
||||
extractPubKeys "$sudo_home"
|
||||
|
@ -67,36 +50,21 @@ done
|
|||
"$SCRIPT_DIR/ip" -4 --json route > routes-v4.json
|
||||
"$SCRIPT_DIR/ip" -6 --json route > routes-v6.json
|
||||
|
||||
[ -f /etc/machine-id ] && cp /etc/machine-id machine-id
|
||||
|
||||
find . | cpio -o -H newc | gzip -9 >> "$SCRIPT_DIR/initrd"
|
||||
|
||||
kexecSyscallFlags=""
|
||||
# only do kexec-syscall-auto on kernels newer than 6.0.
|
||||
# On older kernel we often get errors like: https://github.com/nix-community/nixos-anywhere/issues/264
|
||||
if printf "%s\n" "6.1" "$(uname -r)" | sort -c -V 2>&1; then
|
||||
kexecSyscallFlags="--kexec-syscall-auto"
|
||||
fi
|
||||
|
||||
if ! sh -c "'$SCRIPT_DIR/kexec' --load '$SCRIPT_DIR/bzImage' \
|
||||
$kexecSyscallFlags \
|
||||
$kexec_extra_flags \
|
||||
--initrd='$SCRIPT_DIR/initrd' --no-checks \
|
||||
--command-line 'init=$init $kernelParams'"
|
||||
then
|
||||
echo "kexec failed, dumping dmesg"
|
||||
dmesg | tail -n 100
|
||||
exit 1
|
||||
fi
|
||||
# Dropped --kexec-syscall-auto because it broke on GCP...
|
||||
"$SCRIPT_DIR/kexec" --load "$SCRIPT_DIR/bzImage" \
|
||||
--initrd="$SCRIPT_DIR/initrd" --no-checks \
|
||||
--command-line "init=$init $kernelParams"
|
||||
|
||||
# Disconnect our background kexec from the terminal
|
||||
echo "machine will boot into nixos in 6s..."
|
||||
echo "machine will boot into nixos in in 6s..."
|
||||
if test -e /dev/kmsg; then
|
||||
# this makes logging visible in `dmesg`, or the system console or tools like journald
|
||||
# this makes logging visible in `dmesg`, or the system consol or tools like journald
|
||||
exec > /dev/kmsg 2>&1
|
||||
else
|
||||
exec > /dev/null 2>&1
|
||||
fi
|
||||
# We will kexec in background so we can cleanly finish the script before the hosts go down.
|
||||
# This makes integration with tools like terraform easier.
|
||||
nohup sh -c "sleep 6 && '$SCRIPT_DIR/kexec' -e ${kexec_extra_flags}" &
|
||||
nohup sh -c "sleep 6 && '$SCRIPT_DIR/kexec' -e" &
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
#!/usr/bin/env -S nix shell --inputs-from .# nixos-unstable#bash nixos-unstable#iproute2 nixos-unstable#findutils nixos-unstable#coreutils nixos-unstable#python3 nixos-unstable#jq --command bash
|
||||
|
||||
set -eu
|
||||
SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
|
||||
|
||||
# This script can be used to see what network configuration would be restored by the restore_routes.py script for the current system.
|
||||
|
||||
tmp=$(mktemp -d)
|
||||
trap "rm -rf $tmp" EXIT
|
||||
ip --json address >"$tmp/addrs.json"
|
||||
ip -6 --json route >"$tmp/routes-v6.json"
|
||||
ip -4 --json route >"$tmp/routes-v4.json"
|
||||
python3 "$SCRIPT_DIR/restore_routes.py" "$tmp/addrs.json" "$tmp/routes-v4.json" "$tmp/routes-v6.json" "$tmp"
|
||||
ls -la "$tmp"
|
||||
|
||||
find "$tmp" -type f -name "*.json" -print0 | while IFS= read -r -d '' file; do
|
||||
echo -e "\033[0;31m$(basename "$file")\033[0m"
|
||||
jq . "$file"
|
||||
echo ""
|
||||
done
|
||||
|
||||
find "$tmp" -type f -name "*.network" -print0 | while IFS= read -r -d '' file; do
|
||||
echo -e "\033[0;31m$(basename "$file")\033[0m"
|
||||
cat "$file"
|
||||
echo ""
|
||||
done
|
|
@ -1,27 +1,15 @@
|
|||
{ config, lib, modulesPath, pkgs, ... }:
|
||||
let
|
||||
writePython3 = pkgs.writers.makePythonWriter
|
||||
pkgs.python3Minimal pkgs.python3Packages pkgs.buildPackages.python3Packages;
|
||||
|
||||
# writePython3Bin takes the same arguments as writePython3 but outputs a directory (like writeScriptBin)
|
||||
writePython3Bin = name: writePython3 "/bin/${name}";
|
||||
|
||||
restore-network = writePython3Bin "restore-network" {
|
||||
flakeIgnore = [ "E501" ];
|
||||
restore-network = pkgs.writers.writePython3 "restore-network" {
|
||||
flakeIgnore = ["E501"];
|
||||
} ./restore_routes.py;
|
||||
|
||||
# does not link with iptables enabled
|
||||
iprouteStatic = pkgs.pkgsStatic.iproute2.override { iptables = null; };
|
||||
in
|
||||
{
|
||||
in {
|
||||
imports = [
|
||||
(modulesPath + "/installer/netboot/netboot-minimal.nix")
|
||||
../installer.nix
|
||||
../networkd.nix
|
||||
../serial.nix
|
||||
../restore-remote-access.nix
|
||||
];
|
||||
|
||||
options = {
|
||||
system.kexec-installer.name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
|
@ -33,10 +21,12 @@ in
|
|||
};
|
||||
|
||||
config = {
|
||||
boot.initrd.compressor = "xz";
|
||||
# We are stateless, so just default to latest.
|
||||
system.stateVersion = config.system.nixos.version;
|
||||
|
||||
# This is a variant of the upstream kexecScript that also allows embedding
|
||||
# a ssh key.
|
||||
system.build.kexecRun = pkgs.runCommand "kexec-run" { } ''
|
||||
system.build.kexecRun = pkgs.runCommand "kexec-run" {} ''
|
||||
install -D -m 0755 ${./kexec-run.sh} $out
|
||||
|
||||
sed -i \
|
||||
|
@ -47,23 +37,38 @@ in
|
|||
${pkgs.shellcheck}/bin/shellcheck $out
|
||||
'';
|
||||
|
||||
system.build.kexecTarball = pkgs.runCommand "kexec-tarball" { } ''
|
||||
system.build.kexecTarball = pkgs.runCommand "kexec-tarball" {} ''
|
||||
mkdir kexec $out
|
||||
cp "${config.system.build.netbootRamdisk}/initrd" kexec/initrd
|
||||
cp "${config.system.build.kernel}/${config.system.boot.loader.kernelFile}" kexec/bzImage
|
||||
cp "${config.system.build.kexecRun}" kexec/run
|
||||
cp "${pkgs.pkgsStatic.kexec-tools}/bin/kexec" kexec/kexec
|
||||
cp "${iprouteStatic}/bin/ip" kexec/ip
|
||||
${lib.optionalString (pkgs.hostPlatform == pkgs.buildPlatform) ''
|
||||
kexec/ip -V
|
||||
kexec/kexec --version
|
||||
''}
|
||||
tar -czvf $out/${config.system.kexec-installer.name}-${pkgs.stdenv.hostPlatform.system}.tar.gz kexec
|
||||
'';
|
||||
|
||||
# IPMI SOL console redirection stuff
|
||||
boot.kernelParams =
|
||||
[ "console=tty0" ] ++
|
||||
(lib.optional (pkgs.stdenv.hostPlatform.isAarch32 || pkgs.stdenv.hostPlatform.isAarch64) "console=ttyAMA0,115200") ++
|
||||
(lib.optional (pkgs.stdenv.hostPlatform.isRiscV) "console=ttySIF0,115200") ++
|
||||
[ "console=ttyS0,115200" ];
|
||||
|
||||
documentation.enable = false;
|
||||
# Not really needed. Saves a few bytes and the only service we are running is sshd, which we want to be reachable.
|
||||
networking.firewall.enable = false;
|
||||
|
||||
systemd.network.enable = true;
|
||||
networking.dhcpcd.enable = false;
|
||||
|
||||
# for detection if we are on kexec
|
||||
environment.etc.is_kexec.text = "true";
|
||||
|
||||
# for zapping of disko
|
||||
environment.systemPackages = [
|
||||
pkgs.jq
|
||||
];
|
||||
|
||||
systemd.services.restore-network = {
|
||||
before = [ "network-pre.target" ];
|
||||
wants = [ "network-pre.target" ];
|
||||
|
@ -73,7 +78,7 @@ in
|
|||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStart = [
|
||||
"${restore-network}/bin/restore-network /root/network/addrs.json /root/network/routes-v4.json /root/network/routes-v6.json /etc/systemd/network"
|
||||
"${restore-network} /root/network/addrs.json /root/network/routes-v4.json /root/network/routes-v6.json /etc/systemd/network"
|
||||
];
|
||||
};
|
||||
|
||||
|
@ -83,5 +88,37 @@ in
|
|||
"/root/network/routes-v6.json"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services.log-network-status = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
# No point in restarting this. We just need this after boot
|
||||
restartIfChanged = false;
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
StandardOutput = "journal+console";
|
||||
ExecStart = [
|
||||
# Allow failures, so it still prints what interfaces we have even if we
|
||||
# not get online
|
||||
"-${pkgs.systemd}/lib/systemd/systemd-networkd-wait-online"
|
||||
"${pkgs.iproute2}/bin/ip -c addr"
|
||||
"${pkgs.iproute2}/bin/ip -c -6 route"
|
||||
"${pkgs.iproute2}/bin/ip -c -4 route"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
# Restore ssh host and user keys if they are available.
|
||||
# This avoids warnings of unknown ssh keys.
|
||||
boot.initrd.postMountCommands = ''
|
||||
mkdir -m 700 -p /mnt-root/root/.ssh
|
||||
mkdir -m 755 -p /mnt-root/etc/ssh
|
||||
mkdir -m 755 -p /mnt-root/root/network
|
||||
if [[ -f ssh/authorized_keys ]]; then
|
||||
install -m 400 ssh/authorized_keys /mnt-root/root/.ssh
|
||||
fi
|
||||
install -m 400 ssh/ssh_host_* /mnt-root/etc/ssh
|
||||
cp *.json /mnt-root/root/network/
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,86 +1,40 @@
|
|||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any, Iterator
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class Address:
|
||||
address: str
|
||||
family: str
|
||||
prefixlen: int
|
||||
preferred_life_time: int = 0
|
||||
valid_life_time: int = 0
|
||||
|
||||
|
||||
@dataclass
|
||||
class Interface:
|
||||
name: str
|
||||
ifname: str | None
|
||||
mac_address: str
|
||||
dynamic_addresses: list[Address]
|
||||
static_addresses: list[Address]
|
||||
static_routes: list[dict[str, Any]]
|
||||
|
||||
|
||||
def filter_interfaces(network: list[dict[str, Any]]) -> list[Interface]:
|
||||
interfaces = []
|
||||
def filter_interfaces(network: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
output = []
|
||||
for net in network:
|
||||
if net.get("link_type") == "loopback":
|
||||
continue
|
||||
if not (mac_address := net.get("address")):
|
||||
if not net.get("address"):
|
||||
# We need a mac address to match devices reliable
|
||||
continue
|
||||
static_addresses = []
|
||||
dynamic_addresses = []
|
||||
for info in net.get("addr_info", []):
|
||||
addr_info = []
|
||||
has_dynamic_address = False
|
||||
for addr in net.get("addr_info", []):
|
||||
# no link-local ipv4/ipv6
|
||||
if info.get("scope") == "link":
|
||||
if addr.get("scope") == "link":
|
||||
continue
|
||||
if (preferred_life_time := info.get("preferred_life_time")) is None:
|
||||
# do not explicitly configure addresses from dhcp or router advertisment
|
||||
if addr.get("dynamic", False):
|
||||
has_dynamic_address = True
|
||||
continue
|
||||
if (valid_life_time := info.get("valid_life_time")) is None:
|
||||
continue
|
||||
if (prefixlen := info.get("prefixlen")) is None:
|
||||
continue
|
||||
if (family := info.get("family")) not in ["inet", "inet6"]:
|
||||
continue
|
||||
if (local := info.get("local")) is None:
|
||||
continue
|
||||
if (dynamic := info.get("dynamic", False)) is None:
|
||||
continue
|
||||
|
||||
address = Address(
|
||||
address=local,
|
||||
family=family,
|
||||
prefixlen=prefixlen,
|
||||
preferred_life_time=preferred_life_time,
|
||||
valid_life_time=valid_life_time,
|
||||
)
|
||||
|
||||
if dynamic:
|
||||
dynamic_addresses.append(address)
|
||||
else:
|
||||
static_addresses.append(address)
|
||||
interfaces.append(
|
||||
Interface(
|
||||
name=net.get("ifname", mac_address.replace(":", "-")),
|
||||
ifname=net.get("ifname"),
|
||||
mac_address=mac_address,
|
||||
dynamic_addresses=dynamic_addresses,
|
||||
static_addresses=static_addresses,
|
||||
static_routes=[],
|
||||
)
|
||||
)
|
||||
addr_info.append(addr)
|
||||
if addr_info != [] or has_dynamic_address:
|
||||
net["addr_info"] = addr_info
|
||||
output.append(net)
|
||||
|
||||
return interfaces
|
||||
return output
|
||||
|
||||
|
||||
def filter_routes(routes: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
filtered = []
|
||||
for route in routes:
|
||||
# Filter out routes set by addresses with subnets, dhcp and router advertisement
|
||||
# Filter out routes set by addresses with subnets, dhcp and router advertisment
|
||||
if route.get("protocol") in ["dhcp", "kernel", "ra"]:
|
||||
continue
|
||||
filtered.append(route)
|
||||
|
@ -88,81 +42,46 @@ def filter_routes(routes: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|||
return filtered
|
||||
|
||||
|
||||
def find_most_recent_v4_lease(addresses: list[Address]) -> Address | None:
|
||||
most_recent_address = None
|
||||
most_recent_lifetime = -1
|
||||
for addr in addresses:
|
||||
if addr.family == "inet6":
|
||||
continue
|
||||
lifetime = max(addr.preferred_life_time, addr.valid_life_time)
|
||||
if lifetime > most_recent_lifetime:
|
||||
most_recent_lifetime = lifetime
|
||||
most_recent_address = addr
|
||||
return most_recent_address
|
||||
|
||||
|
||||
def generate_routes(
|
||||
interface: Interface, routes: list[dict[str, Any]]
|
||||
) -> Iterator[str]:
|
||||
for route in routes:
|
||||
if interface.ifname is None or route.get("dev") != interface.ifname:
|
||||
continue
|
||||
|
||||
# we may ignore on-link default routes here, but I don't see how
|
||||
# they would be useful for internet connectivity anyway
|
||||
|
||||
yield "[Route]"
|
||||
if route.get("dst") != "default":
|
||||
# can be skipped for default routes
|
||||
yield f"Destination = {route['dst']}"
|
||||
gateway = route.get("gateway")
|
||||
# route v4 via v6
|
||||
route_via = route.get("via")
|
||||
if route_via and route_via.get("family") == "inet6":
|
||||
gateway = route_via.get("host")
|
||||
if route.get("dst") == "default":
|
||||
yield "Destination = 0.0.0.0/0"
|
||||
if gateway:
|
||||
yield f"Gateway = {gateway}"
|
||||
|
||||
|
||||
def generate_networkd_units(
|
||||
interfaces: list[Interface], routes: list[dict[str, Any]], directory: Path
|
||||
interfaces: list[dict[str, Any]], routes: list[dict[str, Any]], directory: Path
|
||||
) -> None:
|
||||
directory.mkdir(exist_ok=True)
|
||||
for interface in interfaces:
|
||||
# FIXME in some networks we might not want to trust dhcp or router advertisements
|
||||
unit_sections = [
|
||||
f"""
|
||||
name = f"{interface['ifname']}.network"
|
||||
addresses = [
|
||||
f"Address = {addr['local']}/{addr['prefixlen']}"
|
||||
for addr in interface.get("addr_info", [])
|
||||
]
|
||||
|
||||
route_sections = []
|
||||
for route in routes:
|
||||
if route.get("dev", "nodev") != interface.get("ifname", "noif"):
|
||||
continue
|
||||
|
||||
route_section = "[Route]\n"
|
||||
if route.get("dst") != "default":
|
||||
# can be skipped for default routes
|
||||
route_section += f"Destination = {route['dst']}\n"
|
||||
gateway = route.get("gateway")
|
||||
if gateway:
|
||||
route_section += f"Gateway = {gateway}\n"
|
||||
|
||||
# we may ignore on-link default routes here, but I don't see how
|
||||
# they would be useful for internet connectivity anyway
|
||||
route_sections.append(route_section)
|
||||
|
||||
# FIXME in some networks we might not want to trust dhcp or router advertisments
|
||||
unit = f"""
|
||||
[Match]
|
||||
MACAddress = {interface.mac_address}
|
||||
MACAddress = {interface["address"]}
|
||||
|
||||
[Network]
|
||||
# both ipv4 and ipv6
|
||||
DHCP = yes
|
||||
# lets us discover the switch port we're connected to
|
||||
LLDP = yes
|
||||
# ipv6 router advertisements
|
||||
IPv6AcceptRA = yes
|
||||
# allows us to ping "nixos.local"
|
||||
MulticastDNS = yes"""
|
||||
]
|
||||
unit_sections.extend(
|
||||
f"Address = {addr.address}/{addr.prefixlen}"
|
||||
for addr in interface.static_addresses
|
||||
)
|
||||
unit_sections.extend(generate_routes(interface, routes))
|
||||
most_recent_v4_lease = find_most_recent_v4_lease(interface.dynamic_addresses)
|
||||
if most_recent_v4_lease:
|
||||
unit_sections.append("[DHCPv4]")
|
||||
unit_sections.append(f"RequestAddress = {most_recent_v4_lease.address}")
|
||||
|
||||
# trailing newline at the end
|
||||
unit_sections.append("")
|
||||
|
||||
(directory / f"00-{interface.name}.network").write_text(
|
||||
"\n".join(unit_sections)
|
||||
)
|
||||
"""
|
||||
unit += "\n".join(addresses)
|
||||
unit += "\n" + "\n".join(route_sections)
|
||||
(directory / name).write_text(unit)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACA8wk9uIqPk7FZFhRs0ZQ4Q/b0Rd//Rpq2i9e3v33+WwgAAAJjeXdO33l3T
|
||||
twAAAAtzc2gtZWQyNTUxOQAAACA8wk9uIqPk7FZFhRs0ZQ4Q/b0Rd//Rpq2i9e3v33+Wwg
|
||||
AAAEBiNUp5mUe87gWrXbjd36dqt/6waDLdoYV1woR8in4ehDzCT24io+TsVkWFGzRlDhD9
|
||||
vRF3/9GmraL17e/ff5bCAAAAE2pvZXJnQHR1cmluZ21hY2hpbmUBAg==
|
||||
-----END OPENSSH PRIVATE KEY-----
|
|
@ -1 +0,0 @@
|
|||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDzCT24io+TsVkWFGzRlDhD9vRF3/9GmraL17e/ff5bC joerg@turingmachine
|
|
@ -1,36 +1,38 @@
|
|||
{ pkgs
|
||||
, lib
|
||||
, kexecTarball
|
||||
, nixos-facter ? null
|
||||
}:
|
||||
{ pkgs ? import <nixpkgs> {} }:
|
||||
|
||||
pkgs.testers.runNixOSTest {
|
||||
let
|
||||
makeTest = import (pkgs.path + "/nixos/tests/make-test-python.nix");
|
||||
makeTest' = args: makeTest args {
|
||||
inherit pkgs;
|
||||
inherit (pkgs) system;
|
||||
};
|
||||
|
||||
in makeTest' {
|
||||
name = "kexec-installer";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ mic92 ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
node1 = { modulesPath, pkgs, ... }: {
|
||||
virtualisation.vlans = [ ];
|
||||
node1 = { modulesPath, ... }: {
|
||||
virtualisation.vlans = [ 1 ];
|
||||
environment.noXlibs = false; # avoid recompilation
|
||||
imports = [
|
||||
(modulesPath + "/profiles/minimal.nix")
|
||||
];
|
||||
|
||||
system.extraDependencies = [ kexecTarball ];
|
||||
virtualisation.memorySize = 1 * 1024;
|
||||
virtualisation.memorySize = 1024 + 512;
|
||||
virtualisation.diskSize = 4 * 1024;
|
||||
virtualisation.forwardPorts = [{
|
||||
host.port = 2222;
|
||||
guest.port = 22;
|
||||
}];
|
||||
|
||||
virtualisation.useBootLoader = true;
|
||||
virtualisation.useEFIBoot = true;
|
||||
boot.loader.systemd-boot.enable = true;
|
||||
boot.loader.efi.canTouchEfiVariables = true;
|
||||
services.openssh.enable = true;
|
||||
|
||||
networking.useNetworkd = true;
|
||||
networking.useDHCP = false;
|
||||
|
||||
users.users.root.openssh.authorizedKeys.keyFiles = [ ./ssh-keys/id_ed25519.pub ];
|
||||
networking = {
|
||||
useNetworkd = true;
|
||||
useDHCP = false;
|
||||
};
|
||||
|
||||
systemd.network = {
|
||||
networks = {
|
||||
|
@ -39,173 +41,152 @@ pkgs.testers.runNixOSTest {
|
|||
# /etc/systemd/network/{40-eth1,99-main}.network already
|
||||
# exists. This network unit must be loaded for the test,
|
||||
# however, hence why this network is named such.
|
||||
|
||||
"01-eth0" = {
|
||||
name = "eth0";
|
||||
"01-eth1" = {
|
||||
name = "eth1";
|
||||
address = [
|
||||
# Some static addresses that we want to see in the kexeced image
|
||||
"192.168.42.1/24"
|
||||
"42::1/64"
|
||||
];
|
||||
routes = if pkgs.lib.versionAtLeast lib.version "24.11" then [
|
||||
{ Destination = "192.168.43.0/24"; }
|
||||
{ Destination = "192.168.44.0/24"; Gateway = "192.168.43.1"; }
|
||||
{ Destination = "192.168.45.0/24"; Gateway = "43::1"; }
|
||||
{ Destination = "43::0/64"; }
|
||||
{ Destination = "44::1/64"; Gateway = "43::1"; }
|
||||
] else [
|
||||
routes = [
|
||||
# Some static routes that we want to see in the kexeced image
|
||||
{ routeConfig = { Destination = "192.168.43.0/24"; }; }
|
||||
{ routeConfig = { Destination = "192.168.44.0/24"; Gateway = "192.168.43.1"; }; }
|
||||
{ routeConfig = { Destination = "192.168.45.0/24"; Gateway = "43::1"; }; }
|
||||
{ routeConfig = { Destination = "43::0/64"; }; }
|
||||
{ routeConfig = { Destination = "44::1/64"; Gateway = "43::1"; }; }
|
||||
];
|
||||
networkConfig = { DHCP = "yes"; IPv6AcceptRA = true; };
|
||||
networkConfig = {
|
||||
DHCP = "yes";
|
||||
IPv6AcceptRA = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
} // lib.optionalAttrs (lib.versionOlder lib.version "24.11pre") {
|
||||
# avoid second overlay
|
||||
environment.noXlibs = false;
|
||||
};
|
||||
};
|
||||
|
||||
testScript = /*python*/ ''
|
||||
import json
|
||||
import time
|
||||
import subprocess
|
||||
import socket
|
||||
import http.server
|
||||
from threading import Thread
|
||||
from typing import Optional
|
||||
node2 = { pkgs, modulesPath, ... }: {
|
||||
environment.systemPackages = [ pkgs.hello ];
|
||||
imports = [
|
||||
./module.nix
|
||||
../noninteractive.nix
|
||||
];
|
||||
};
|
||||
|
||||
start_all()
|
||||
router = { config, pkgs, ... }: {
|
||||
virtualisation.vlans = [ 1 ];
|
||||
networking = {
|
||||
useNetworkd = true;
|
||||
useDHCP = false;
|
||||
firewall.enable = false;
|
||||
};
|
||||
systemd.network = {
|
||||
networks = {
|
||||
# systemd-networkd will load the first network unit file
|
||||
# that matches, ordered lexiographically by filename.
|
||||
# /etc/systemd/network/{40-eth1,99-main}.network already
|
||||
# exists. This network unit must be loaded for the test,
|
||||
# however, hence why this network is named such.
|
||||
"01-eth1" = {
|
||||
name = "eth1";
|
||||
address = [
|
||||
"2001:db8::1/64"
|
||||
];
|
||||
ipv6Prefixes = [
|
||||
{ ipv6PrefixConfig = { Prefix = "2001:db8::/64"; AddressAutoconfiguration = true; OnLink = true; }; }
|
||||
];
|
||||
# does not work in 22.11
|
||||
#ipv6RoutePrefixes = [ { ipv6RoutePrefixConfig = { Route = "::/0"; LifetimeSec = 3600; }; }];
|
||||
extraConfig = ''
|
||||
[IPv6RoutePrefix]
|
||||
Route = ::/0
|
||||
LifetimeSec = 3600
|
||||
'';
|
||||
networkConfig = {
|
||||
DHCPServer = true;
|
||||
Address = "10.0.0.1/24";
|
||||
IPv6SendRA = true;
|
||||
};
|
||||
dhcpServerConfig = {
|
||||
PoolOffset = 100;
|
||||
PoolSize = 1;
|
||||
EmitRouter = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
class DualStackServer(http.server.HTTPServer):
|
||||
def server_bind(self):
|
||||
self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
|
||||
return super().server_bind()
|
||||
DualStackServer.address_family = socket.AF_INET6
|
||||
httpd = DualStackServer(("::", 0), http.server.SimpleHTTPRequestHandler)
|
||||
};
|
||||
|
||||
http.server.HTTPServer.address_family = socket.AF_INET6
|
||||
port = httpd.server_port
|
||||
def serve_forever(httpd):
|
||||
with httpd:
|
||||
httpd.serve_forever()
|
||||
thread = Thread(target=serve_forever, args=(httpd, ))
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
testScript = { nodes, ... }: ''
|
||||
# Test whether reboot via kexec works.
|
||||
|
||||
node1.wait_until_succeeds(f"curl -v -I http://10.0.2.2:{port}")
|
||||
node1.wait_until_succeeds(f"curl -v -I http://[fec0::2]:{port}")
|
||||
router.wait_for_unit("network-online.target")
|
||||
router.succeed("ip addr >&2")
|
||||
router.succeed("ip route >&2")
|
||||
router.succeed("ip -6 route >&2")
|
||||
router.succeed("networkctl status eth1 >&2")
|
||||
|
||||
node1.wait_until_succeeds("ping -c1 10.0.0.1")
|
||||
node1.wait_until_succeeds("ping -c1 2001:db8::1")
|
||||
node1.succeed("ip addr >&2")
|
||||
node1.succeed("ip route >&2")
|
||||
node1.succeed("ip -6 route >&2")
|
||||
node1.succeed("networkctl status eth1 >&2")
|
||||
|
||||
host_ed25519_before = node1.succeed("cat /etc/ssh/ssh_host_ed25519_key.pub")
|
||||
node1.succeed('ssh-keygen -t ed25519 -f /root/.ssh/id_ed25519 -q -N ""')
|
||||
root_ed25519_before = node1.succeed('tee /root/.ssh/authorized_keys < /root/.ssh/id_ed25519.pub')
|
||||
|
||||
# Kexec node1 to the toplevel of node2 via the kexec-boot script
|
||||
node1.succeed('touch /run/foo')
|
||||
node1.fail('hello')
|
||||
node1.succeed('tar -xf ${nodes.node2.system.build.kexecTarball}/nixos-kexec-installer-${pkgs.system}.tar.gz -C /root')
|
||||
node1.execute('/root/kexec/run')
|
||||
# wait for machine to kexec
|
||||
node1.execute('sleep 9999', check_return=False)
|
||||
node1.succeed('! test -e /run/foo')
|
||||
node1.succeed('hello')
|
||||
node1.succeed('[ "$(hostname)" = "node2" ]')
|
||||
node1.wait_for_unit("sshd.service")
|
||||
|
||||
host_ed25519_after = node1.succeed("cat /etc/ssh/ssh_host_ed25519_key.pub")
|
||||
assert host_ed25519_before == host_ed25519_after, f"{host_ed25519_before} != {host_ed25519_after}"
|
||||
|
||||
root_ed25519_after = node1.succeed("cat /root/.ssh/authorized_keys")
|
||||
assert root_ed25519_before == root_ed25519_after, f"{root_ed25519_before} != {root_ed25519_after}"
|
||||
|
||||
# See if we can reach the router after kexec
|
||||
node1.wait_for_unit("restore-network.service")
|
||||
node1.wait_until_succeeds("cat /etc/systemd/network/eth1.network >&2")
|
||||
node1.wait_until_succeeds("ping -c1 10.0.0.1")
|
||||
node1.wait_until_succeeds("ping -c1 2001:db8::1")
|
||||
|
||||
# Check if static addresses have been restored
|
||||
node1.wait_until_succeeds("ping -c1 42::1")
|
||||
node1.wait_until_succeeds("ping -c1 192.168.42.1")
|
||||
|
||||
out = node1.wait_until_succeeds("ip route get 192.168.43.2")
|
||||
print(out)
|
||||
assert "192.168.43.2 dev eth1" in out
|
||||
|
||||
out = node1.wait_until_succeeds("ip route get 192.168.44.2")
|
||||
print(out)
|
||||
assert "192.168.44.2 via 192.168.43.1" in out
|
||||
|
||||
out = node1.wait_until_succeeds("ip route get 43::2")
|
||||
print(out)
|
||||
assert "43::2 from :: dev eth1" in out
|
||||
|
||||
out = node1.wait_until_succeeds("ip route get 44::2")
|
||||
print(out)
|
||||
assert "44::2 from :: via 43::1" in out
|
||||
|
||||
node1.succeed("ip addr >&2")
|
||||
node1.succeed("ip route >&2")
|
||||
node1.succeed("ip -6 route >&2")
|
||||
node1.succeed("networkctl status eth0 >&2")
|
||||
node1.succeed("networkctl status eth1 >&2")
|
||||
|
||||
def ssh(cmd: list[str], check: bool = True, stdout: Optional[int] = None) -> subprocess.CompletedProcess[str]:
|
||||
ssh_cmd = [
|
||||
"${pkgs.openssh}/bin/ssh",
|
||||
"-o", "StrictHostKeyChecking=no",
|
||||
"-o", "ConnectTimeout=1",
|
||||
"-i", "${./ssh-keys/id_ed25519}",
|
||||
"-p", "2222",
|
||||
"root@127.0.0.1",
|
||||
"--"
|
||||
] + cmd
|
||||
print(" ".join(ssh_cmd))
|
||||
return subprocess.run(ssh_cmd,
|
||||
text=True,
|
||||
check=check,
|
||||
stdout=stdout)
|
||||
|
||||
|
||||
while not ssh(["true"], check=False).returncode == 0:
|
||||
time.sleep(1)
|
||||
ssh(["cp", "--version"])
|
||||
|
||||
host_ed25519_before = node1.succeed("cat /etc/ssh/ssh_host_ed25519_key.pub").strip()
|
||||
node1.succeed('ssh-keygen -t ed25519 -f /root/.ssh/id_ed25519 -q -N ""')
|
||||
root_ed25519_before = node1.succeed('tee /root/.ssh/authorized_keys < /root/.ssh/id_ed25519.pub').strip()
|
||||
|
||||
# Kexec node1 to the toplevel of node2 via the kexec-boot script
|
||||
node1.succeed('touch /run/foo')
|
||||
old_machine_id = node1.succeed("cat /etc/machine-id").strip()
|
||||
node1.fail('parted --version >&2')
|
||||
node1.succeed('tar -xf ${kexecTarball}/nixos-kexec-installer-noninteractive-${pkgs.system}.tar.gz -C /root')
|
||||
node1.succeed('/root/kexec/ip -V >&2')
|
||||
node1.succeed('/root/kexec/kexec --version >&2')
|
||||
node1.succeed('/root/kexec/run >&2')
|
||||
|
||||
# wait for kexec to finish
|
||||
while ssh(["true"], check=False).returncode == 0:
|
||||
print("Waiting for kexec to finish...")
|
||||
time.sleep(1)
|
||||
|
||||
while ssh(["true"], check=False).returncode != 0:
|
||||
print("Waiting for node2 to come up...")
|
||||
time.sleep(1)
|
||||
|
||||
while ssh(["systemctl is-active restore-network"], check=False).returncode != 0:
|
||||
print("Waiting for network to be restored...")
|
||||
time.sleep(1)
|
||||
ssh(["systemctl", "status", "restore-network"])
|
||||
|
||||
print(ssh(["ip", "addr"]))
|
||||
print(ssh(["ip", "route"]))
|
||||
print(ssh(["ip", "-6", "route"]))
|
||||
print(ssh(["networkctl", "status"]))
|
||||
|
||||
new_machine_id = ssh(["cat", "/etc/machine-id"], stdout=subprocess.PIPE).stdout.strip()
|
||||
assert old_machine_id == new_machine_id, f"{old_machine_id} != {new_machine_id}, machine-id changed"
|
||||
|
||||
assert ssh(["ls", "-la", "/run/foo"], check=False).returncode != 0, "kexeced node1 still has /run/foo"
|
||||
print(ssh(["parted", "--version"]))
|
||||
host = ssh(["hostname"], stdout=subprocess.PIPE).stdout.strip()
|
||||
assert host == "nixos-installer", f"hostname is {host}, not nixos-installer"
|
||||
|
||||
has_nixos_facter=${if nixos-facter != null then "True" else "False"}
|
||||
if has_nixos_facter == True:
|
||||
data = json.loads(ssh(["nixos-facter"], stdout=subprocess.PIPE).stdout)
|
||||
assert data["virtualisation"] == "kvm", f"virtualisation is {data['virtualisation']}, not kvm"
|
||||
|
||||
host_ed25519_after = ssh(["cat", "/etc/ssh/ssh_host_ed25519_key.pub"], stdout=subprocess.PIPE).stdout.strip()
|
||||
assert host_ed25519_before == host_ed25519_after, f"'{host_ed25519_before}' != '{host_ed25519_after}'"
|
||||
|
||||
root_ed25519_after = ssh(["cat", "/root/.ssh/authorized_keys"], stdout=subprocess.PIPE).stdout.strip()
|
||||
assert root_ed25519_before in root_ed25519_after, f"'{root_ed25519_before}' not included in '{root_ed25519_after}'"
|
||||
|
||||
print(ssh(["cat", "/etc/systemd/network/00-eth0.network"]))
|
||||
ssh(["curl", "-v", "-I", f"http://10.0.2.2:{port}"])
|
||||
ssh(["curl", "-v", "-I", f"http://[fec0::2]:{port}"])
|
||||
|
||||
## Check if static addresses have been restored
|
||||
ssh(["ping", "-c1", "42::1"])
|
||||
ssh(["ping", "-c1", "192.168.42.1"])
|
||||
|
||||
out = ssh(["ip", "route", "get", "192.168.43.2"], stdout=subprocess.PIPE).stdout
|
||||
print(out)
|
||||
assert "192.168.43.2 dev" in out, f"route to `192.168.43.2 dev` not found: {out}"
|
||||
|
||||
out = ssh(["ip", "route", "get", "192.168.44.2"], stdout=subprocess.PIPE).stdout
|
||||
print(out)
|
||||
assert "192.168.44.2 via 192.168.43.1" in out, f"route to `192.168.44.2 via 192.168.43.1` not found: {out}"
|
||||
|
||||
out = ssh(["ip", "route", "get", "192.168.45.2"], stdout=subprocess.PIPE).stdout
|
||||
print(out)
|
||||
assert "192.168.45.2 via inet6 43::1" in out, f"route to `192.168.45.2 via inet6 43::1` not found: {out}"
|
||||
|
||||
out = ssh(["ip", "route", "get", "43::2"], stdout=subprocess.PIPE).stdout
|
||||
print(out)
|
||||
assert "43::2 from :: dev" in out, f"route `43::2 from dev` not found: {out}"
|
||||
|
||||
out = ssh(["ip", "route", "get", "44::2"], stdout=subprocess.PIPE).stdout
|
||||
print(out)
|
||||
assert "44::2 from :: via 43::1" in out, f"route to `44::2 from :: via 43::1` not found: {out}"
|
||||
|
||||
node1.crash()
|
||||
node1.shutdown()
|
||||
'';
|
||||
}
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
{
|
||||
lib,
|
||||
pkgs,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
isUnstable = config.boot.zfs.package == pkgs.zfsUnstable;
|
||||
zfsCompatibleKernelPackages = lib.filterAttrs (
|
||||
name: kernelPackages:
|
||||
(builtins.match "linux_[0-9]+_[0-9]+" name) != null
|
||||
&& (builtins.tryEval kernelPackages).success
|
||||
&& (
|
||||
(!isUnstable && !kernelPackages.zfs.meta.broken)
|
||||
|| (isUnstable && !kernelPackages.zfs_unstable.meta.broken)
|
||||
)
|
||||
) pkgs.linuxKernel.packages;
|
||||
latestKernelPackage = lib.last (
|
||||
lib.sort (a: b: (lib.versionOlder a.kernel.version b.kernel.version)) (builtins.attrValues zfsCompatibleKernelPackages)
|
||||
);
|
||||
in
|
||||
{
|
||||
# Note this might jump back and worth as kernel get added or removed.
|
||||
boot.kernelPackages = latestKernelPackage;
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
{ pkgs, ... }:
|
||||
{
|
||||
systemd.services.log-network-status = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
# No point in restarting this. We just need this after boot
|
||||
restartIfChanged = false;
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
StandardOutput = "journal+console";
|
||||
ExecStart = [
|
||||
# Allow failures, so it still prints what interfaces we have even if we
|
||||
# not get online
|
||||
"-${pkgs.systemd}/lib/systemd/systemd-networkd-wait-online"
|
||||
"${pkgs.iproute2}/bin/ip -c addr"
|
||||
"${pkgs.iproute2}/bin/ip -c -6 route"
|
||||
"${pkgs.iproute2}/bin/ip -c -4 route"
|
||||
"${pkgs.systemd}/bin/networkctl status"
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
|
@ -2,10 +2,6 @@
|
|||
{
|
||||
imports = [
|
||||
(modulesPath + "/installer/netboot/netboot-minimal.nix")
|
||||
../installer.nix
|
||||
../networkd.nix
|
||||
../serial.nix
|
||||
../restore-remote-access.nix
|
||||
];
|
||||
|
||||
# We are stateless, so just default to latest.
|
||||
|
@ -16,7 +12,7 @@
|
|||
paths = with config.system.build; [
|
||||
netbootRamdisk
|
||||
kernel
|
||||
(pkgs.runCommand "kernel-params" { } ''
|
||||
(pkgs.runCommand "kernel-params" {} ''
|
||||
mkdir -p $out
|
||||
ln -s "${config.system.build.toplevel}/kernel-params" $out/kernel-params
|
||||
ln -s "${config.system.build.toplevel}/init" $out/init
|
||||
|
@ -24,12 +20,28 @@
|
|||
];
|
||||
preferLocalBuild = true;
|
||||
};
|
||||
|
||||
# IPMI SOL console redirection stuff
|
||||
boot.kernelParams =
|
||||
[ "console=tty0" ] ++
|
||||
(lib.optional (pkgs.stdenv.hostPlatform.isAarch32 || pkgs.stdenv.hostPlatform.isAarch64) "console=ttyAMA0,115200") ++
|
||||
(lib.optional (pkgs.stdenv.hostPlatform.isRiscV) "console=ttySIF0,115200") ++
|
||||
[ "console=ttyS0,115200" ];
|
||||
|
||||
documentation.enable = false;
|
||||
# Not really needed. Saves a few bytes and the only service we are running is sshd, which we want to be reachable.
|
||||
networking.firewall.enable = false;
|
||||
|
||||
systemd.network.enable = true;
|
||||
networking.dhcpcd.enable = false;
|
||||
|
||||
systemd.network.networks."10-uplink" = {
|
||||
matchConfig.Type = "ether";
|
||||
networkConfig = {
|
||||
DHCP = "yes";
|
||||
LLMNR = "yes";
|
||||
EmitLLDP = "yes";
|
||||
IPv6AcceptRA = "yes";
|
||||
IPv6AcceptRA = "no";
|
||||
MulticastDNS = "yes";
|
||||
LinkLocalAddressing = "yes";
|
||||
LLDP = "yes";
|
||||
|
@ -41,6 +53,29 @@
|
|||
};
|
||||
};
|
||||
|
||||
# for zapping of disko
|
||||
environment.systemPackages = [
|
||||
pkgs.jq
|
||||
];
|
||||
|
||||
systemd.services.log-network-status = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
# No point in restarting this. We just need this after boot
|
||||
restartIfChanged = false;
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
StandardOutput = "journal+console";
|
||||
ExecStart = [
|
||||
# Allow failures, so it still prints what interfaces we have even if we
|
||||
# not get online
|
||||
"-${pkgs.systemd}/lib/systemd/systemd-networkd-wait-online"
|
||||
"${pkgs.iproute2}/bin/ip -c addr"
|
||||
"${pkgs.iproute2}/bin/ip -c -6 route"
|
||||
"${pkgs.iproute2}/bin/ip -c -4 route"
|
||||
];
|
||||
};
|
||||
};
|
||||
networking.hostName = "";
|
||||
# overrides normal activation script for setting hostname
|
||||
system.activationScripts.hostname = lib.mkForce ''
|
||||
|
@ -54,4 +89,14 @@
|
|||
done
|
||||
hostname "''${hostParam[1]:-nixos}"
|
||||
'';
|
||||
|
||||
boot.initrd.postMountCommands = ''
|
||||
# add user keys if they are available.
|
||||
mkdir -m 700 -p /mnt-root/root/.ssh
|
||||
mkdir -m 755 -p /mnt-root/etc/ssh
|
||||
mkdir -m 755 -p /mnt-root/root/network
|
||||
if [[ -f ssh/authorized_keys ]]; then
|
||||
install -m 400 ssh/authorized_keys /mnt-root/root/.ssh
|
||||
fi
|
||||
'';
|
||||
}
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
{ lib, ... }:
|
||||
{
|
||||
# Not really needed. Saves a few bytes and the only service we are running is sshd, which we want to be reachable.
|
||||
networking.firewall.enable = false;
|
||||
|
||||
networking.useNetworkd = true;
|
||||
systemd.network.enable = true;
|
||||
|
||||
# mdns
|
||||
networking.firewall.allowedUDPPorts = [ 5353 ];
|
||||
systemd.network.networks."99-ethernet-default-dhcp".networkConfig.MulticastDNS = lib.mkDefault "yes";
|
||||
systemd.network.networks."99-wireless-client-dhcp".networkConfig.MulticastDNS = lib.mkDefault "yes";
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
# take from srvos
|
||||
{ lib, ... }:
|
||||
{
|
||||
# Fallback quickly if substituters are not available.
|
||||
nix.settings.connect-timeout = 5;
|
||||
|
||||
# Enable flakes
|
||||
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
||||
|
||||
# The default at 10 is rarely enough.
|
||||
nix.settings.log-lines = lib.mkDefault 25;
|
||||
|
||||
# Avoid disk full issues
|
||||
nix.settings.max-free = lib.mkDefault (3000 * 1024 * 1024);
|
||||
nix.settings.min-free = lib.mkDefault (512 * 1024 * 1024);
|
||||
|
||||
# TODO: cargo culted.
|
||||
nix.daemonCPUSchedPolicy = lib.mkDefault "batch";
|
||||
nix.daemonIOSchedClass = lib.mkDefault "idle";
|
||||
nix.daemonIOSchedPriority = lib.mkDefault 7;
|
||||
|
||||
# Avoid copying unnecessary stuff over SSH
|
||||
nix.settings.builders-use-substitutes = true;
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
{
|
||||
lib,
|
||||
buildGoModule,
|
||||
fetchFromGitHub,
|
||||
hwinfo,
|
||||
libusb1,
|
||||
gcc,
|
||||
pkg-config,
|
||||
util-linux,
|
||||
pciutils,
|
||||
stdenv,
|
||||
}:
|
||||
let
|
||||
# We are waiting on some changes to be merged upstream: https://github.com/openSUSE/hwinfo/pulls
|
||||
hwinfoOverride = hwinfo.overrideAttrs {
|
||||
src = fetchFromGitHub {
|
||||
owner = "numtide";
|
||||
repo = "hwinfo";
|
||||
rev = "a559f34934098d54096ed2078e750a8245ae4044";
|
||||
hash = "sha256-3abkWPr98qXXQ17r1Z43gh2M5hl/DHjW2hfeWl+GSAs=";
|
||||
};
|
||||
};
|
||||
in
|
||||
buildGoModule rec {
|
||||
pname = "nixos-facter";
|
||||
version = "0.1.1";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "numtide";
|
||||
repo = "nixos-facter";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-vlPmvCrgX64dcf//BPtQszBt7dkq35JpgQg+/LW0AqM=";
|
||||
};
|
||||
|
||||
vendorHash = "sha256-5leiTNp3FJmgFd0SKhu18hxYZ2G9SuQPhZJjki2SDVs=";
|
||||
|
||||
CGO_ENABLED = 1;
|
||||
|
||||
buildInputs = [
|
||||
libusb1
|
||||
hwinfoOverride
|
||||
];
|
||||
|
||||
nativeBuildInputs = [
|
||||
gcc
|
||||
pkg-config
|
||||
];
|
||||
|
||||
runtimeInputs = [
|
||||
libusb1
|
||||
util-linux
|
||||
pciutils
|
||||
];
|
||||
|
||||
ldflags = [
|
||||
"-s"
|
||||
"-w"
|
||||
"-X git.numtide.com/numtide/nixos-facter/build.Name=nixos-facter"
|
||||
"-X git.numtide.com/numtide/nixos-facter/build.Version=v${version}"
|
||||
"-X github.com/numtide/nixos-facter/pkg/build.System=${stdenv.hostPlatform.system}"
|
||||
];
|
||||
|
||||
meta = {
|
||||
description = "Declarative hardware configuration for NixOS";
|
||||
homepage = "https://github.com/numtide/nixos-facter";
|
||||
license = lib.licenses.gpl3Plus;
|
||||
maintainers = [ lib.maintainers.brianmcgee ];
|
||||
mainProgram = "nixos-facter";
|
||||
platforms = lib.platforms.linux;
|
||||
};
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
{ lib, ... }: {
|
||||
# HACK: Drop this, once we have 24.11 everywhere
|
||||
nixpkgs.overlays = lib.optionals (lib.versionOlder lib.version "24.11pre") [
|
||||
# Both syslinux and grub also reference perl
|
||||
(final: prev: {
|
||||
# we don't need grub: save ~ 60MB
|
||||
grub2 = prev.coreutils;
|
||||
grub2_efi = prev.coreutils;
|
||||
syslinux = prev.coreutils;
|
||||
})
|
||||
];
|
||||
}
|
|
@ -1,73 +1,31 @@
|
|||
# This module optimizes for non-interactive deployments by remove some store paths
|
||||
# which are primarily useful for interactive installations.
|
||||
|
||||
{ lib, pkgs, modulesPath, ... }:
|
||||
{
|
||||
{ config, lib, pkgs, ... }: {
|
||||
disabledModules = [
|
||||
# This module adds values to multiple lists (systemPackages, supportedFilesystems)
|
||||
# which are impossible/unpractical to remove, so we disable the entire module.
|
||||
"profiles/base.nix"
|
||||
];
|
||||
|
||||
imports = [
|
||||
# ./zfs-minimal.nix
|
||||
./no-bootloaders.nix
|
||||
./python-minimal.nix
|
||||
./noveau-workaround.nix
|
||||
# reduce closure size by removing perl
|
||||
"${modulesPath}/profiles/perlless.nix"
|
||||
# FIXME: we still are left with nixos-generate-config due to nixos-install-tools
|
||||
{ system.forbiddenDependenciesRegexes = lib.mkForce []; }
|
||||
];
|
||||
|
||||
# among others, this prevents carrying a stdenv with gcc in the image
|
||||
system.extraDependencies = lib.mkForce [ ];
|
||||
system.extraDependencies = lib.mkForce [];
|
||||
|
||||
# prevents shipping nixpkgs, unnecessary if system is evaluated externally
|
||||
nix.registry = lib.mkForce { };
|
||||
nix.registry = lib.mkForce {};
|
||||
|
||||
# would pull in nano
|
||||
programs.nano.enable = false;
|
||||
programs.nano.syntaxHighlight = lib.mkForce false;
|
||||
|
||||
# prevents strace
|
||||
environment.defaultPackages = lib.mkForce [
|
||||
pkgs.rsync
|
||||
pkgs.parted
|
||||
pkgs.gptfdisk
|
||||
pkgs.e2fsprogs
|
||||
];
|
||||
# prevents nano, strace
|
||||
environment.defaultPackages = lib.mkForce [ pkgs.rsync ];
|
||||
|
||||
# normal users are not allowed with sys-users
|
||||
# see https://github.com/NixOS/nixpkgs/pull/328926
|
||||
users.users.nixos = {
|
||||
isSystemUser = true;
|
||||
isNormalUser = lib.mkForce false;
|
||||
shell = "/run/current-system/sw/bin/bash";
|
||||
group = "nixos";
|
||||
# zfs support is accidentally disabled by excluding base.nix, re-enable it
|
||||
boot = {
|
||||
kernelModules = [ "zfs" ];
|
||||
extraModulePackages = [ config.boot.kernelPackages.zfs ];
|
||||
};
|
||||
users.groups.nixos = {};
|
||||
|
||||
# we prefer root as this is also what we use in nixos-anywhere
|
||||
services.getty.autologinUser = lib.mkForce "root";
|
||||
|
||||
# we are missing this from base.nix
|
||||
boot.supportedFilesystems = [
|
||||
"ext4"
|
||||
"btrfs"
|
||||
# probably not needed but does not seem to increase closure size
|
||||
"cifs"
|
||||
"f2fs"
|
||||
## anyone still using this over ext4?
|
||||
#"jfs"
|
||||
"ntfs"
|
||||
## no longer seems to be maintained, anyone still using it?
|
||||
#"reiserfs"
|
||||
"vfat"
|
||||
"xfs"
|
||||
];
|
||||
boot.kernelModules = [
|
||||
# we have to explicitly enable this, otherwise it is not loaded even when creating a raid:
|
||||
# https://github.com/nix-community/nixos-anywhere/issues/249
|
||||
"dm-raid"
|
||||
];
|
||||
# we can drop this after 23.05 has been released, which has this set by default
|
||||
hardware.enableRedistributableFirmware = lib.mkForce false;
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
# fixes blank screen on boot for some cards
|
||||
boot.kernelParams = [ "nouveau.modeset=0" ];
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
nixpkgs.overlays = [
|
||||
(final: prev: {
|
||||
bcachefs-tools = prev.bcachefs-tools.override { python3 = final.python3Minimal; };
|
||||
cifs-utils = prev.cifs-utils.override { python3 = final.python3Minimal; };
|
||||
nfs-utils = prev.nfs-utils.override { python3 = final.python3Minimal; };
|
||||
talloc = prev.talloc.override { python3 = final.python3Minimal; };
|
||||
})
|
||||
];
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
{
|
||||
# We have a bug in 23.11 in combination with netboot.
|
||||
boot.initrd.systemd.enable = true;
|
||||
boot.initrd.systemd.services.restore-state-from-initrd = {
|
||||
unitConfig = {
|
||||
DefaultDependencies = false;
|
||||
RequiresMountsFor = "/sysroot /dev";
|
||||
};
|
||||
wantedBy = [ "initrd.target" ];
|
||||
requiredBy = [ "rw-etc.service" ];
|
||||
before = [ "rw-etc.service" ];
|
||||
serviceConfig.Type = "oneshot";
|
||||
# Restore ssh host and user keys if they are available.
|
||||
# This avoids warnings of unknown ssh keys.
|
||||
script = ''
|
||||
mkdir -m 700 -p /sysroot/root/.ssh
|
||||
mkdir -m 755 -p /sysroot/etc/ssh
|
||||
mkdir -m 755 -p /sysroot/root/network
|
||||
if [[ -f ssh/authorized_keys ]]; then
|
||||
install -m 400 ssh/authorized_keys /sysroot/root/.ssh
|
||||
fi
|
||||
install -m 400 ssh/ssh_host_* /sysroot/etc/ssh
|
||||
cp *.json /sysroot/root/network/
|
||||
if [[ -f machine-id ]]; then
|
||||
cp machine-id /sysroot/etc/machine-id
|
||||
fi
|
||||
'';
|
||||
};
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
{ pkgs, lib, ... }:
|
||||
{
|
||||
# IPMI SOL console redirection stuff
|
||||
boot.kernelParams =
|
||||
[ "console=tty0" ]
|
||||
++ (lib.optional (
|
||||
pkgs.stdenv.hostPlatform.isAarch32 || pkgs.stdenv.hostPlatform.isAarch64
|
||||
) "console=ttyAMA0,115200")
|
||||
++ (lib.optional (pkgs.stdenv.hostPlatform.isRiscV) "console=ttySIF0,115200")
|
||||
++ [ "console=ttyS0,115200" ];
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
# incorperate a space-optimized version of zfs
|
||||
let
|
||||
zfs = pkgs.zfsUnstable.override {
|
||||
# this overrides saves 10MB
|
||||
samba = pkgs.coreutils;
|
||||
|
||||
python3 = pkgs.python3Minimal;
|
||||
};
|
||||
in
|
||||
{
|
||||
services.udev.packages = [ zfs ]; # to hook zvol naming, etc.
|
||||
# unsure if need this, but in future udev rules could potentially point to systemd services.
|
||||
systemd.packages = [ zfs ];
|
||||
environment.defaultPackages = lib.mkForce [ zfs ]; # this merges with outer noninteractive module.
|
||||
|
||||
boot.kernelModules = [ "zfs" ];
|
||||
boot.extraModulePackages = [ config.boot.kernelPackages.zfs_unstable ];
|
||||
|
||||
networking.hostId = lib.mkDefault "8425e349";
|
||||
}
|
Loading…
Reference in a new issue