Dual boot NixOS with Windows. Careful and convenient handling of EFI
This commit is contained in:
parent
ebc3fda1d3
commit
be16241253
@ -332,9 +332,14 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
sunshine.enable = true;
|
sunshine.enable = true;
|
||||||
|
# sunshine.capSysAdmin = true;
|
||||||
displayManager = {
|
displayManager = {
|
||||||
sddm.enable = true;
|
sddm.enable = true;
|
||||||
#defaultSession = "plasmawayland";
|
#defaultSession = "plasmawayland";
|
||||||
|
autoLogin = {
|
||||||
|
enable = true;
|
||||||
|
user = "blee";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
xserver = {
|
xserver = {
|
||||||
enable = true;
|
enable = true;
|
||||||
@ -351,13 +356,26 @@ in
|
|||||||
systemd = {
|
systemd = {
|
||||||
services = {
|
services = {
|
||||||
|
|
||||||
playground = {
|
# BUG: unable to attach to the tmux session. LLMs can't figure out why 1/9/2025
|
||||||
|
# playground = {
|
||||||
|
# wantedBy = [ "multi-user.target" ];
|
||||||
|
# after = [ "network.target" ];
|
||||||
|
# serviceConfig = {
|
||||||
|
# Type = "forking";
|
||||||
|
# User = "blee";
|
||||||
|
# WorkingDirectory = "/opt/playground";
|
||||||
|
# Environment = "NIX_PATH=nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos:nixos-config=/etc/nixos/configuration.nix:/nix/var/nix/profiles/per-user/root/channels";
|
||||||
|
# };
|
||||||
|
# script = "${pkgs.nix}/bin/nix-shell";
|
||||||
|
# };
|
||||||
|
|
||||||
|
ollama = {
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
after = [ "network.target" ];
|
after = [ "network.target" ];
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Type = "forking";
|
Type = "forking";
|
||||||
User = "blee";
|
User = "blee";
|
||||||
WorkingDirectory = "/opt/playground";
|
WorkingDirectory = "/opt/ollama";
|
||||||
Environment = "NIX_PATH=nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos:nixos-config=/etc/nixos/configuration.nix:/nix/var/nix/profiles/per-user/root/channels";
|
Environment = "NIX_PATH=nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos:nixos-config=/etc/nixos/configuration.nix:/nix/var/nix/profiles/per-user/root/channels";
|
||||||
};
|
};
|
||||||
script = "${pkgs.nix}/bin/nix-shell";
|
script = "${pkgs.nix}/bin/nix-shell";
|
||||||
@ -399,5 +417,5 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
system.stateVersion = "23.11";
|
system.stateVersion = "24.11";
|
||||||
}
|
}
|
||||||
|
@ -29,51 +29,51 @@ function FORMAT_DISK ()
|
|||||||
|
|
||||||
nixos-generate-config --root /mnt
|
nixos-generate-config --root /mnt
|
||||||
}
|
}
|
||||||
ping -c1 ${TARGET} 2>&1 > /dev/null || (echo "Target not found. Exiting." && exit 1)
|
ping -c1 $TARGET 2>&1 > /dev/null || (echo "Target not found. Exiting." && exit 1)
|
||||||
if ! arp -n | grep $TARGET_MAC; then
|
if ! arp -n | grep $TARGET_MAC; then
|
||||||
echo "Target not found in ARP table. Exiting."
|
echo "Target not found in ARP table. Exiting."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Install NixOS on ${TARGET}? You must set a password on the target before running this."
|
echo "Install NixOS on $TARGET? You must set a password on the target before running this."
|
||||||
echo "Press enter to continue or ctrl+c to quit."
|
echo "Press enter to continue or ctrl+c to quit."
|
||||||
read
|
read
|
||||||
|
|
||||||
ssh-keygen -R ${TARGET}
|
ssh-keygen -R $TARGET
|
||||||
ssh-copy-id nixos@${TARGET}
|
ssh-copy-id nixos@$TARGET
|
||||||
|
|
||||||
COMMANDS="
|
COMMANDS="
|
||||||
sudo cp -r /home/nixos/.ssh /root/.;
|
sudo cp -r /home/nixos/.ssh /root/.;
|
||||||
sudo chown -R root:root /root/.ssh;
|
sudo chown -R root:root /root/.ssh;
|
||||||
"
|
"
|
||||||
ssh -t nixos@${TARGET} "${COMMANDS}"
|
ssh -t nixos@$TARGET "${COMMANDS}"
|
||||||
|
|
||||||
ssh root@${TARGET} "$(typeset -f FORMAT_DISK); FORMAT_DISK"
|
ssh root@$TARGET "$(typeset -f FORMAT_DISK); FORMAT_DISK"
|
||||||
|
|
||||||
scp configuration.nix root@${TARGET}:/mnt/etc/nixos/
|
scp configuration.nix root@$TARGET:/mnt/etc/nixos/
|
||||||
|
|
||||||
# copy authorized keys to both the target and the target's chroot, because nixos-install runs outside the chroot
|
# copy authorized keys to both the target and the target's chroot, because nixos-install runs outside the chroot
|
||||||
ssh root@${TARGET} mkdir -p /etc/nixos/ssh /mnt/etc/nixos/ssh
|
ssh root@$TARGET mkdir -p /etc/nixos/ssh /mnt/etc/nixos/ssh
|
||||||
if [ -f ~/.ssh/ansible_root_keys ]; then
|
if [ -f ~/.ssh/ansible_root_keys ]; then
|
||||||
scp ~/.ssh/ansible_root_keys root@$TARGET:/mnt/etc/nixos/ssh/authorized_keys
|
scp ~/.ssh/ansible_root_keys root@$TARGET:/mnt/etc/nixos/ssh/authorized_keys
|
||||||
scp ~/.ssh/ansible_root_keys root@$TARGET:/etc/nixos/ssh/authorized_keys
|
scp ~/.ssh/ansible_root_keys root@$TARGET:/etc/nixos/ssh/authorized_keys
|
||||||
scp ~/.ssh/ansible_timburr_keys root@$TARGET:/mnt/etc/nixos/ssh/authorized_timburr_keys
|
scp ~/.ssh/ansible_timburr_keys root@$TARGET:/mnt/etc/nixos/ssh/authorized_timburr_keys
|
||||||
scp ~/.ssh/ansible_timburr_keys root@$TARGET:/etc/nixos/ssh/authorized_timburr_keys
|
scp ~/.ssh/ansible_timburr_keys root@$TARGET:/etc/nixos/ssh/authorized_timburr_keys
|
||||||
else
|
else
|
||||||
scp ~/.ssh/authorized_keys root@${TARGET}:/etc/nixos/ssh/authorized_keys
|
scp ~/.ssh/authorized_keys root@$TARGET:/etc/nixos/ssh/authorized_keys
|
||||||
scp ~/.ssh/authorized_keys root@${TARGET}:/mnt/etc/nixos/ssh/authorized_keys
|
scp ~/.ssh/authorized_keys root@$TARGET:/mnt/etc/nixos/ssh/authorized_keys
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Press [Enter] to run nixos-install on the target, or press ctrl+c to stop and do it manually."
|
echo "Press [Enter] to run nixos-install on the target, or press ctrl+c to stop and do it manually."
|
||||||
read
|
read
|
||||||
ssh root@${TARGET} nixos-install
|
ssh root@$TARGET nixos-install
|
||||||
#ssh root@${TARGET} openssl dhparam -out /etc/ssl/dhparams.pem 3072
|
#ssh root@$TARGET openssl dhparam -out /etc/ssl/dhparams.pem 3072
|
||||||
|
|
||||||
ssh-keygen -R ${TARGET}
|
ssh-keygen -R $TARGET
|
||||||
echo "Done."
|
echo "Done."
|
||||||
echo
|
echo
|
||||||
echo "You should set a password before restarting in case networking doesn't come up on first boot. To chroot run this:"
|
echo "You should set a password before restarting in case networking doesn't come up on first boot. To chroot run this:"
|
||||||
echo "nixos-enter --root /mnt"
|
echo "nixos-enter --root /mnt"
|
||||||
echo "passwd"
|
echo "passwd"
|
||||||
|
|
||||||
ssh-keygen -R ${TARGET}
|
ssh-keygen -R $TARGET
|
111
incineroar.brenise.dev/notes/efi.md
Normal file
111
incineroar.brenise.dev/notes/efi.md
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
# EFI notes
|
||||||
|
|
||||||
|
## Dual Boot
|
||||||
|
|
||||||
|
We can use the EFI `NextBoot` feature to signal that the system should boot the 2nd OS.
|
||||||
|
|
||||||
|
For initial setup, find the 2nd OS boot entry:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
PS C:\Users\pleb\Documents\windows-scripts> PowerShell.exe -ExecutionPolicy Bypass -File .\List-BootEntries.ps1
|
||||||
|
Found Boot0000
|
||||||
|
Found Boot0002
|
||||||
|
```
|
||||||
|
|
||||||
|
Hard-code the boot entry in the `Set-BootNext.ps1` script:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# NixOS boot entry ID
|
||||||
|
$NixOSBootEntryID = "0002"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then whenever you want to run NixOS, run the script:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
PowerShell.exe -ExecutionPolicy Bypass -File \Users\pleb\Documents\windows-scripts\Set-BootNext.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
## EFI Recovery
|
||||||
|
|
||||||
|
Sometimes Windows Update will trample the EFI boot entry for NixOS.
|
||||||
|
|
||||||
|
## Linux
|
||||||
|
|
||||||
|
* Boot into a NixOS live environment (USB or CD).
|
||||||
|
|
||||||
|
* Mount your NixOS root partition and the ESP (EFI System Partition):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mount /dev/nvme0n1p4 /mnt
|
||||||
|
mount /dev/nvme0n1p1 /mnt/boot
|
||||||
|
```
|
||||||
|
|
||||||
|
* Chroot into your NixOS installation:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
nixos-enter --root /mnt
|
||||||
|
```
|
||||||
|
|
||||||
|
* Manually reinstall the bootloader:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bootctl install --path /boot
|
||||||
|
```
|
||||||
|
|
||||||
|
* Exit the chroot environment:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
exit
|
||||||
|
```
|
||||||
|
|
||||||
|
* Unmount the partitions:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
umount /mnt/boot
|
||||||
|
umount /mnt
|
||||||
|
```
|
||||||
|
|
||||||
|
8. Ensure the boot order is to your satisfaction
|
||||||
|
|
||||||
|
```sh
|
||||||
|
efibootmgr -v
|
||||||
|
efibootmgr -o XXXX,YYYY
|
||||||
|
```
|
||||||
|
|
||||||
|
9. Reboot your system.
|
||||||
|
|
||||||
|
By following these steps, you should be able to recover your NixOS boot option if it disappears after a Windows update.
|
||||||
|
|
||||||
|
## Windows
|
||||||
|
|
||||||
|
In the unlikely scenario that the Windows boot entry is removed, here's how to recover from that:
|
||||||
|
|
||||||
|
* Mount the EFI System Partition if it's not already mounted:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mount /dev/nvme0n1p1 /mnt/boot
|
||||||
|
```
|
||||||
|
|
||||||
|
(Replace nvme0n1p1 with the correct partition if different)
|
||||||
|
|
||||||
|
* Verify that the Windows bootloader files exist:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ls /mnt/boot/EFI/Microsoft/Boot/
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see a file named `bootmgfw.efi`.
|
||||||
|
|
||||||
|
* If the files are present, you can create a new boot entry for Windows using `efibootmgr`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
efibootmgr -c -d /dev/nvme0n1 -p 1 -L "Windows" -l '\EFI\Microsoft\Boot\bootmgfw.efi'
|
||||||
|
```
|
||||||
|
|
||||||
|
This command creates a new boot entry labeled "Windows" that points to the Windows bootloader file.
|
||||||
|
|
||||||
|
* Verify that the new entry was created:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
efibootmgr -v
|
||||||
|
```
|
56
incineroar.brenise.dev/notes/installing-windows.md
Normal file
56
incineroar.brenise.dev/notes/installing-windows.md
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Dual booting Windows 11
|
||||||
|
|
||||||
|
- Before the install drop files onto the flash drive
|
||||||
|
- autologin.reg
|
||||||
|
```toml
|
||||||
|
Windows Registry Editor Version 5.00
|
||||||
|
|
||||||
|
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon]
|
||||||
|
"DefaultUserName"="pleb"
|
||||||
|
"DefaultPassword"="PASSWORD_HERE"
|
||||||
|
"AutoAdminLogon"="1"
|
||||||
|
```
|
||||||
|
- setup ssh
|
||||||
|
```powershell
|
||||||
|
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
|
||||||
|
Start-Service sshd
|
||||||
|
Set-Service -Name sshd -StartupType 'Automatic'
|
||||||
|
Get-Service -Name sshd | Select-Object -Property Name, DisplayName, Status, StartType
|
||||||
|
|
||||||
|
```
|
||||||
|
- ssh network access
|
||||||
|
- The default firewall policy is the in "private" group
|
||||||
|
- Settings > Network & internet > select Private Network
|
||||||
|
- ssh access
|
||||||
|
```powershell
|
||||||
|
scp ~/.ssh/ansible_timburr_keys winroar:/ProgramData/ssh/administrators_authorized_keys
|
||||||
|
ssh winroar
|
||||||
|
powershell
|
||||||
|
Get-Content -Path $env:ProgramData\ssh\administrators_authorized_keys
|
||||||
|
icacls.exe ""$env:ProgramData\ssh\administrators_authorized_keys"" /inheritance:r /grant ""Administrators:F"" /grant ""SYSTEM:F""
|
||||||
|
```
|
||||||
|
- Once it asks you to connect to a network (which it does even if you're on one)
|
||||||
|
- avoid creating a microsoft account
|
||||||
|
- unplug the network
|
||||||
|
- When you reach the network connection screen, press Shift + F10 to open Command Prompt.
|
||||||
|
- Type `oobe\bypassnro` and press Enter.
|
||||||
|
- The system will restart, and you should be able to create a local account without a network connection
|
||||||
|
- It forces you to enter security questions
|
||||||
|
- After hitting next its safe to restore the network (although the NIC driver is missing anyway, until updates get installed)
|
||||||
|
- First install steps to get out of Windows
|
||||||
|
- connect to 2GHz wifi because win11 installer doesn't have the NIC driver for #incineroar and out-of-box wireless driver can't negotiate with the 5GHz network
|
||||||
|
- Install sunshine `winget install lizardbyte.sunshine`
|
||||||
|
- open edge using incognito mode and go to sunshine: `localhost:47990`
|
||||||
|
- set sunshine credentials and then pair it to moonlight on litten
|
||||||
|
- go to taskbar settings and disable everything
|
||||||
|
- Setup autologin
|
||||||
|
- Run `netplwiz`
|
||||||
|
- The machine can now be operated headlessly, continue setup from a more comfortable Linux environment
|
||||||
|
|
||||||
|
## Windows Scripts
|
||||||
|
|
||||||
|
A script to minimize dark patterns can be copied via the following copypasta:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
scp -r ~/ops/brenise.dev/nix/incineroar.brenise.dev/windows-scripts winroar:Documents/
|
||||||
|
```
|
@ -57,7 +57,7 @@ if ! tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
|
|||||||
|
|
||||||
# Start ollama in a new window
|
# Start ollama in a new window
|
||||||
tmux new-window -t "$SESSION_NAME" -n "ollama"
|
tmux new-window -t "$SESSION_NAME" -n "ollama"
|
||||||
tmux send-keys -t "$SESSION_NAME":1 "cd /opt/ollama && ./bin/ollama serve" C-m
|
tmux send-keys -t "$SESSION_NAME":1 "cd /opt/ollama && source .env && ./bin/ollama serve" C-m
|
||||||
|
|
||||||
# Prepare the ComfyUI launch command
|
# Prepare the ComfyUI launch command
|
||||||
COMFYUI_BASE_COMMAND="python main.py --port $COMFYUI_PORT --listen 127.0.0.1"
|
COMFYUI_BASE_COMMAND="python main.py --port $COMFYUI_PORT --listen 127.0.0.1"
|
||||||
|
48
incineroar.brenise.dev/windows-scripts/List-BootEntries.ps1
Normal file
48
incineroar.brenise.dev/windows-scripts/List-BootEntries.ps1
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# Requires running PowerShell as Administrator
|
||||||
|
|
||||||
|
# GUID for the EFI global variable
|
||||||
|
$EFI_GLOBAL_GUID = "{8BE4DF61-93CA-11D2-AA0D-00E098032B8C}"
|
||||||
|
|
||||||
|
# Define the GetFirmwareEnvironmentVariable function
|
||||||
|
$GetFirmwareEnvironmentVariable = @"
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
public class UEFI
|
||||||
|
{
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||||
|
public static extern uint GetFirmwareEnvironmentVariable(
|
||||||
|
string lpName,
|
||||||
|
string lpGuid,
|
||||||
|
byte[] pBuffer,
|
||||||
|
uint nSize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
"@
|
||||||
|
|
||||||
|
Add-Type -TypeDefinition $GetFirmwareEnvironmentVariable
|
||||||
|
|
||||||
|
# Function to read a boot entry
|
||||||
|
function Read-BootEntry {
|
||||||
|
param([string]$BootEntryID)
|
||||||
|
|
||||||
|
$buffer = New-Object byte[] 4096 # Adjust size if needed
|
||||||
|
$size = [UEFI]::GetFirmwareEnvironmentVariable("Boot$BootEntryID", $EFI_GLOBAL_GUID, $buffer, $buffer.Length)
|
||||||
|
|
||||||
|
if ($size -ne 0) {
|
||||||
|
$data = $buffer[0..($size - 1)]
|
||||||
|
[string]::Join("", ($data | ForEach-Object { "{0:X2}" -f $_ }))
|
||||||
|
} else {
|
||||||
|
$null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Enumerate boot entries from 0000 to FFFF
|
||||||
|
for ($i = 0; $i -le 0xFFFF; $i++) {
|
||||||
|
$id = "{0:X4}" -f $i
|
||||||
|
$entry = Read-BootEntry $id
|
||||||
|
if ($entry) {
|
||||||
|
Write-Host "Found Boot$id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Note: Parsing the boot entry data to extract the description requires more complex parsing
|
47
incineroar.brenise.dev/windows-scripts/Set-BootNext.ps1
Normal file
47
incineroar.brenise.dev/windows-scripts/Set-BootNext.ps1
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# Requires running PowerShell as Administrator
|
||||||
|
|
||||||
|
# Define the GUID for the EFI global variable
|
||||||
|
$EFI_GLOBAL_GUID = "{8BE4DF61-93CA-11D2-AA0D-00E098032B8C}"
|
||||||
|
|
||||||
|
# NixOS boot entry ID
|
||||||
|
$NixOSBootEntryID = "0002"
|
||||||
|
|
||||||
|
# Convert the Boot Entry ID to a byte array
|
||||||
|
$BootNextValue = [UInt16]::Parse($NixOSBootEntryID, "AllowHexSpecifier")
|
||||||
|
$BootNextBytes = [BitConverter]::GetBytes($BootNextValue)
|
||||||
|
|
||||||
|
# Define the SetFirmwareEnvironmentVariable function
|
||||||
|
$SetFirmwareEnvironmentVariable = @"
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
public class UEFI
|
||||||
|
{
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||||
|
public static extern bool SetFirmwareEnvironmentVariable(
|
||||||
|
string lpName,
|
||||||
|
string lpGuid,
|
||||||
|
byte[] pValue,
|
||||||
|
int nSize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
"@
|
||||||
|
|
||||||
|
Add-Type -TypeDefinition $SetFirmwareEnvironmentVariable
|
||||||
|
|
||||||
|
# Set the BootNext variable
|
||||||
|
$success = [UEFI]::SetFirmwareEnvironmentVariable(
|
||||||
|
"BootNext",
|
||||||
|
$EFI_GLOBAL_GUID,
|
||||||
|
$BootNextBytes,
|
||||||
|
$BootNextBytes.Length
|
||||||
|
)
|
||||||
|
|
||||||
|
if ($success) {
|
||||||
|
Write-Host "BootNext variable set successfully to Boot$NixOSBootEntryID."
|
||||||
|
# reboot to nixos
|
||||||
|
Write-Host "Rebooting to NixOS..."
|
||||||
|
Restart-Computer -Force
|
||||||
|
} else {
|
||||||
|
$errorCode = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
|
||||||
|
Write-Host "Failed to set BootNext variable. Error code: $errorCode"
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
#Requires AutoHotkey v2.0
|
||||||
|
|
||||||
|
; Define the function that checks for specific processes and minimizes a window
|
||||||
|
CheckAndMinimize() {
|
||||||
|
; Check if either "BSManager.exe" or "vrmonitor.exe" processes are running
|
||||||
|
if ProcessExist("BSManager.exe") or ProcessExist("vrmonitor.exe") {
|
||||||
|
; Check if the Oculus Client window exists
|
||||||
|
oculusClient := WinExist("ahk_exe OculusClient.exe")
|
||||||
|
|
||||||
|
; If the Oculus Client window exists, minimize it
|
||||||
|
if oculusClient
|
||||||
|
WinMinimize("ahk_id " . oculusClient)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
; Set a timer to call the CheckAndMinimize function every 5000 milliseconds (5 seconds)
|
||||||
|
SetTimer(CheckAndMinimize, 5000)
|
Loading…
x
Reference in New Issue
Block a user