STATUS: OPERATIONAL

Building and Securing a Host OS

Table of Contents

1. Introduction

Before we confront the sealed firmware, we must first establish a foothold with a trusted environment we fully control. That begins with a bootable USB drive loaded with a stable, security-oriented, and —crucially— community-driven distribution: Debian.

This distribution choice is a deliberate declaration. You are choosing the software that runs on your hardware. You are replacing opaque vendor code with something inspectable, modifiable, and community-audited. This is the first brick in your fortress of sovereignty.

Whether you create boot drive on your current laptop, a borrowed machine, or even a library computer — this USB is your single-factor, physical root-of-trust transfer token.

To install Debian on the target laptop, we need to create a bootable USB drive from an installer image file (.iso). We will use the Debian "net-installer," a minimal image that downloads the necessary packages during installation.

This Operating System will be our sovereign host, Dom0. In virtualization, Dom0 (“Domain 0”) is the privileged control domain — the first and most trusted operating system started by the hypervisor. It has direct access to hardware and is responsible for managing all other virtual machines, these are called DomU (“Unprivileged Domain”).

In our architecture, the Debian system we’re about to install will become Dom0 — the sovereign host that controls, monitors, and isolates everything else. It is the new “root of trust” — not because the firmware beneath it is trustworthy, but because it is auditable, modifiable, and under our control.

The Dom0 is the seat of governance in our reclaimed machine. All other software from browsers, email clients, and even entire alternate operating systems, will run as guests, contained and observed.

We begin by installing Debian not as an “OS replacement,” but as the first layer of the sovereignty stack — built on freedom where you, not the vendor, exercise control and ownership.

2. Media Preparation

First, I need to collect the download link of not just the software installer image, but also proof of its integrity and proof of the proof’s authenticity.

At time of writing, the most transparent method of locating is to;

  1. Use an internet browser and navigate to the official website, https://debian.org.
  2. Follow the Other downloads{:target=_"blank"} link.
  3. Here, you can select the first link, Download mirrors{:target=_"blank"} or the complete installation image{:target=_"blank"} lower down the page.

    A. From the "complete installation image" redirect, choose the first link, Download USB/CD/DVD images using HTTPS

    • If using an Intel or AMD Processor, select amd64 for standard 64-bit computers under the CD/USB header.

    B. From the "Download mirrors" redirect, scroll up to select amd64 under the CD/USB header.

  4. Finally, we have reached the repository, review the SHA512SUMS, SHA512SUMS.sign and debian-XX.X.X-amd64-netinst.iso URLS, compare the URLS with the provided command:

Automated command set (Regex pattern works at time of publishing):

# Evergreen Debian download (always gets current stable)
Set-Location $Home\Desktop\debian-netinst
$IsoName = (Invoke-WebRequest -Uri "https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/" -UseBasicParsing).Content | Select-String -Pattern 'debian-[0-9.]*-amd64-netinst\.iso' | Select-Object -First 1 | ForEach-Object { $_.Matches[0].Value }
Invoke-WebRequest -Uri "https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/$IsoName" -OutFile $IsoName
Invoke-WebRequest -Uri "https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/SHA512SUMS" -OutFile "SHA512SUMS"
# You may need to press Enter again for the signature file
Invoke-WebRequest -Uri "https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/SHA512SUMS.sign" -OutFile "SHA512SUMS.sign"

Want to roll your own? Right-click > "Copy link address" and paste fresh URLs below and build the command live. Always eyeball the result before you paste into a terminal.

Dynamic command set (updates live as you type):

# Download Debian installer and verification files  
Invoke-WebRequest -Uri "__ISOURL__" -OutFile (Split-Path "__ISOURL__" -Leaf)
Invoke-WebRequest -Uri "__CHECKSUMURL__" -OutFile (Split-Path "__CHECKSUMURL__" -Leaf)
Invoke-WebRequest -Uri "__SIGURL__" -OutFile (Split-Path "__SIGURL__" -Leaf)




Security note: These inputs are evaluated in your browser only. A malicious browser extension could alter them. Verify the command visually before execution.

2.1. Audit the Downloaded Image

Before writing anything to USB, it's important to verify the image is genuinely from Debian and has not been tampered with or corrupted.

This is layered verification: the ISO is checked against the checksum, and the checksum is checked against the signature. No single file is trusted in isolation. This is how sovereignty is built — brick by brick, hash by hash.

Checksum
Cryptographically calculated hash from a block of data, such as a file, that acts like a digital fingerprint. If even a single bit of data is different, the checksum will not match.

2.1.1. Verify the Integrity

For minimal dependencies, we can use built-in the PowerShell CMDlet, Get-FileHash. However, this only verifies the hash matches what was downloaded, it does not ensure the orginal download is not malicious.

  1. Navigate to where the image was downloaded:

    cd $Home\Downloads
    
  2. Locate the specific downloaded ISO file:

    $IsoFile = Get-Item $Home\Downloads\debian-*-amd64-netinst.iso | Select-Object -ExpandProperty Name
    
  3. Next, extract the expected hash for your specific ISO from the SHA512SUMS file:

    $Expected = (Get-Content SHA512SUMS | Where-Object { $_ -match $IsoFile }) -split '\s+' | Select-Object -First 1
    
  4. Calculate the actual hash of your downloaded file:

    $Observed = (Get-FileHash -Algorithm SHA512 $IsoFile).Hash
    
  5. Finally, compare the hashes:

    if ($Expected.ToUpper() -eq $Observed.ToUpper()) {
        Write-Host "✅ SHA-512 MATCH" -ForegroundColor Green
        Write-Host "ISO SHA-512: $Observed" -ForegroundColor Cyan
    } else {
        Write-Host "❌ SHA-512 MISMATCH" -ForegroundColor Red
        exit 1
    }
    

If they do not match; delete the file and re-download.

2.1.2. (Optional) Export a JSON manifest for documentation and verification auditing.

This must be run in the same PowerShell Session as steps 1-4, if you are returning in a new terminal, return to those steps.

  1. Build an audit trail for documentation:

    @{
        IsoFile      = $IsoFile
        IsoSha512    = $Observed
        ChecksumFile = "SHA512SUMS"
        SigFile      = "SHA512SUMS.sign"
        Timestamp    = (Get-Date -Format "yyyy-MM-ddTHH:mm:ssK")
    } | ConvertTo-Json |
        Out-File -Encoding utf8 "$Home\Downloads\debian-integrity.json"
    
    Write-Host "Manifest saved to $Home\Downloads\debian-integrity.json" -ForegroundColor Cyan
    

This is the debian-integrity.json output I generated at time of publishing:

{
    "IsoSha512":  "873E9AA09A913660B4780E29C02419F8FB91012C8092E49DCFE90EA802E60C82DCD6D7D2BEEB92EBCA0570C49244EEE57A37170F178A27FE1F64A334EE357332",
    "IsoFile":  "debian-13.1.0-amd64-netinst.iso",
    "Timestamp":  "2025-09-13T10:23:33-06:00",
    "ChecksumFile":  "SHA512SUMS",
    "SigFile":  "SHA512SUMS.sign"
}

2.1.3. (Optional, recommended) Verify the Authenticity

I began by using Gnu Privacy Guard (GPG) to verify the Developer of our tools, establishing a critical first act of trust. Later, we’ll transform GPG into our personal keychain, managing our digital identity across the entire stack.

Gnu Privacy Guard
A free and open-source implementation of the OpenPGP standard, which is based on the original PGP (Pretty Good Privacy) software. A foundational tool that enables the use of digital signatures and public-key cryptography to ensure the integrity and authenticity of files
  1. Install GPG using Windows Package Manager, winget:

    First, we want to ensure we are protecting ourselves against Typo Squatting, where a bad actor will use a common mis-spelling of a package to distribute malware.

    Package Manager
    Handles repository management and automatically resolves package dependencies. Think of it as an app store for your terminal; it handles finding, installing, and updating software.

    We will search for the software by name;

    winget search "Gnu Privacy Guard"
    

    It should return something similar to:

    Name              Id          Version Source
    ---------------------------------------------
    GNU Privacy Guard GnuPG.GnuPG 2.4.8   winget
    

    Any results cannot be blindly trusted and must be independently verified, we will show to get a detailed report of the search results.

    winget show GnuPG.GnuPG
    

    This search will yield multiple results. Before installing anything, it is critical to vet the package to avoid security risks like typo squatting, where malicious actors upload packages with names similar to legitimate ones. We can inspect the full details of a package using winget show to find the Id, Publisher, and Homepage fields.

    For each of the fields, these are unverified claims until independantly vetting using the following process:

    1. Corroborate: Do not blindly trust and navigate to the Homepage URL listed in the manifest. Instead, use an independent search engine to find the software's official project website. This is the non-negotiable step to establish a ground truth.
    2. Verify: Compare the Publisher name and package Id from the manifest against the official developer or organization name found on the official website. Any discrepancy, however minor, is a red flag that should halt the installation.
    3. Execute: Only after you have independently verified that the manifest's claims align with the official source, proceed with the installation using the precise command: winget install --id <PackageId> --exact.

    Once verified, we can safely install;

    winget install GnuPG.GnuPG
    

    This time, since we used a package manager, we will not need to verify the integrity of the download using the hashes, as this is done automatically for us, and you have already authenticated the package in the previous step.

    After installing a package, winget hands control over to that package installer. The installer itself is responsible for everything from where the program is placed on your hard drive to whether it adds shortcuts, registers file associations, and modifies the system's PATH environment variable. Some installers, especially for developer tools like GnuPG, assume you will handle this part yourself, as it can be a source of problems if not done correctly.

    To do this, we will need to exit our existing PowerShell Session (type exit, then "Enter\"), then open an additional PowerShell Terminal as an Administrator using: Win+x a. This ensures we have the permissions needed to modify the system PATH.

    First, verify if the package was added to PATH:

    Get-Command gpg
    

    If you get an error that gpg is not found this does not mean the installation failed, but rather that we haven't told the system where to find it.

    If it isn't found, paste in this script,

    $GpgExe = Get-ChildItem -Path "C:\Program Files*" -Filter "gpg.exe" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1
    if ($GpgExe) {
        $GpgDir = Split-Path -Parent $GpgExe.FullName
        [Environment]::SetEnvironmentVariable("PATH", $Env:PATH + ";" + $GpgDir, [EnvironmentVariableTarget]::Machine)
        Write-Host "GPG added to PATH: $GpgDir" -ForegroundColor Green
    } else {
        Write-Host "GPG installation not found" -ForegroundColor Red
        exit 1
    }
    

    Or; Want to roll your own? Use these instructions. Always eyeball the result before you paste into a terminal. We need to locate where the installer downloaded the application to:

    Get-ChildItem -Path "C:\Program Files*" -Filter "gpg.exe" -Recurse -ErrorAction SilentlyContinue
    

    The -ErrorAction SilentlyContinue part just tells PowerShell not to show you any "Access Denied" errors it might encounter when trying to look inside protected system folders. The result should list a Directory location, such as:

        Directory: C:\Program Files (x86)\gnupg\bin
    
    Mode                 LastWriteTime         Length Name
    ----                 -------------         ------ ----
    -a---          9/13/2025  10:23 AM         123456 gpg.exe
    

    Copy the Directory path (in this case: `C:\Program Files (x86)\gnupg\bin`) for the next step, adding the Directory path to the system PATH:

    [System.Environment]::SetEnvironmentVariable(
        "PATH", $env:PATH + ";" + "__GPGPATH__", [EnvironmentVariableTarget]::Machine)
    


    Refresh the environment variables for the PATH modification to take effect:

    refreshenv
    

    Now, we can confirm the installation:

    gpg --version
    
  2. Verification Using GPG

    Then, import Debian’s official signing keys. The provider key IDs we're importing are Debian’s official signing keys but you should never trust them just because a tutorial says so.

    Verify them yourself on Debian’s official site.

    We’ll use the 3 CD release signing keys listed:

    gpg --keyserver keyserver.ubuntu.com --recv-keys 988021A964E6EA7D DA87E80D6294BE9B 42468F4009EA8AC3
    
    gpg: C:\\Users\\caleb\\AppData\\Roaming\\gnupg\\trustdb.gpg: trustdb created
    gpg: key 42468F4009EA8AC3: public key "Debian Testing CDs Automatic Signing Key <[email protected]>" imported
    gpg: key DA87E80D6294BE9B: public key "Debian CD signing key <[email protected]>" imported
    gpg: key 988021A964E6EA7D: public key "Debian CD signing key <[email protected]>" imported
    gpg: Total number processed: 3
    gpg:               imported: 3
    

    After importing the keys, verify the signature:

    gpg --verify SHA512SUMS.sign SHA512SUMS
    

    The output will likely look like this:

    gpg: Signature made 09/06/25 15:54:29 Mountain Daylight Time
    gpg:                using RSA key DF9B9C49EAA9298432589D76DA87E80D6294BE9B
    gpg: Good signature from "Debian CD signing key <[email protected]>" [unknown]
    gpg: WARNING: This key is not certified with a trusted signature!
    gpg:          There is no indication that the signature belongs to the owner.
    Primary key fingerprint: DF9B 9C49 EAA9 2984 3258  9D76 DA87 E80D 6294 BE9B
    

    This is not an error — it’s GPG doing its job. This warning exists to protect you from “key substitution” attacks — where an attacker replaces a legitimate key on the keyserver with their own. By forcing you to verify the fingerprint against an official source (like debian.org over HTTPS), GPG ensures you’re not trusting a malicious imposter — even if their signature is mathematically valid.

    Finally, check the key fingerprints:

    gpg --fingerprint 988021A964E6EA7D DA87E80D6294BE9B 42468F4009EA8AC3
    

    You should see output like:

    pub   rsa4096 2009-10-03 [SC]
          1046 0DAD 7616 5AD8 1FBC  0CE9 9880 21A9 64E6 EA7D
    uid           [ unknown] Debian CD signing key <[email protected]>
    
    pub   rsa4096 2011-01-05 [SC]
          DF9B 9C49 EAA9 2984 3258  9D76 DA87 E80D 6294 BE9B
    uid           [ unknown] Debian CD signing key <[email protected]>
    sub   rsa4096 2011-01-05 [E]
    
    pub   rsa4096 2014-04-15 [SC]
          F41D 3034 2F35 4669 5F65  C669 4246 8F40 09EA 8AC3
    uid           [ unknown] Debian Testing CDs Automatic Signing Key <[email protected]>
    sub   rsa4096 2014-04-15 [E]
    

    The [unknown] indicates the trust level of your local trust database. You can reference the Cryptography with GNU Privacy Guard to update this information.

    Cross-reference the fingerprints on:

    If you find a match then you’ve successfully cryptographically and visually confirmed the key’s authenticity. This is the gold standard of verification.

3. Create the Bootable USB Drive

3.1. Installing WSL2 Debian

This guide uses a primary workstation to remotely manage a secondary laptop that will host the 'Sovereignty Stack'. This provides a fundamental layer of isolation, preventing a compromise of the experimental system from immediately impacting the primary workstation.

From the primary workstation we will initialize and configure all of the tools needed for the project. As an accessible entry point, we will be using Windows 11. From the Desktop, use the following key-chord to open a PowerShell terminal as an Administrator: Win + x a

Using Admin PowerShell terminal, run the following command to install the Windows Subsystem for Linux with the Debian distribution:

wsl --install -d Debian
  • Command Explanations:
    wsl - (Windows Subsystm for Linux):
    A compatibility layer that allows Linux binary executables to interact with Kernel level processes without requiring disk-partitioning for a dual-boot set-up.
    --install:
    An option that initiates the WSL setup process and installs a Linux distribution.
    -d / --distribution:
    A switch that flags the command we are specifying a non-default distribution. We are choosing Debian, but you can see other options with wsl --list --online.

We will next follow the on-screen instructions for standard Linux user creation.

  • Create the default UNIX user account.

    Enter new UNIX username:
    

    Enter

    New password:
    

    Enter

    Retype new password:
    

    As you enter the password, no visual feedback will be returned, not even a placeholder such as ****, this is an intentional security design.

    The terminal will print the following and drop you into the directory you were in when wsl.exe was executed for creating the UNIX User:

    passwd: password updated successfully
    usermod: no changes
    $USERNAME@$HOSTNAME:/mnt/c/Users/%USERNAME%
    

Once you are at the Debian command-line interface, change your location from the Windows User directory to the UNIX User directory.

cd ~
  • Command Explanations:
    cd - (Change Directory):
    Standard POSIX method for navigating the drive.
    ~ - (Tilde):
    Shorthand for /home/$user.

3.2. Preparing WSL2 to Access Your USB Drive

To use dd, we need WSL2 to see your USB drive as a raw block device (like /dev/sdi). By default, WSL2 only mounts Windows drives (C:, D:, etc.).

Plug in the USB, preferably using a rear I/O USB 2.0 port if available, as USB 3.0 devices sometimes have issues with passthrough

We’ll use usbipd-win, a Microsoft-supported open-source tool, to expose your USB to WSL2.

  1. Step 1: Install usbipd-win

    From PowerShell (Admin):

    winget install usbipd
    

    Restart the session to add it to PATH. Review the earlier steps to add it manually if needed.

  2. Step 2: List USB Devices
    usbipd list
    

    You’ll see output like:

    Connected:
    BUSID  VID:PID    DEVICE                                                        STATE
    1-3    8087:0032  Intel(R) Wireless Bluetooth(R)                                Not shared
    1-5    1bcf:2283  NexiGo N930AF FHD webcam, NexiGo N930AF FHD webcam Audio      Not shared
    1-7    1532:0287  USB Input Device, Razer BlackWidow V4                         Not shared
    1-10   0781:5581  USB Mass Storage Device                                       Not Shared
    1-12   1b1c:0c0c  USB Input Device                                              Not shared
    1-13   26ce:01a2  USB Input Device                                              Not shared
    2-3    1532:0067  Razer Naga Trinity                                            Not shared
    9-1    1997:2466  USB Input Device                                              Not shared
    10-2   0bda:8153  Realtek USB GbE Family Controller #2                          Not shared
    
    Persisted:
    GUID                                  DEVICE
    

    The only storage device in this list is

    1-10    0781:5581  USB Mass Storage Device
    

    Which means this is what I am looking for.

    If you have multiple USB Mass Storage devices, identify yours by:

    1. Unplug the target USB drive and run usbipd list - note which device disappears
    2. Plug it back in and run usbipd list again - the device that reappeared is your target
    3. Check the VID:PID against known vendors:
      • `0781` = SanDisk
      • `090c` = Silicon Motion (common in generic drives)
      • `8564` = Transcend
      • `13fe` = Kingston
  3. Step 3: Bind and Attach to WSL2

    From an Administrator PowerShell Session:

    # Replace 1-10 with your USB's BUSID
    usbipd bind --busid 1-10
    usbipd attach --wsl --busid 1-10
    

    You'll see some output such as:

    usbipd: info: Using WSL distribution 'Debian' to attach; the device will be available in all WSL 2 distributions.
    usbipd: info: Loading vhci_hcd module.
    usbipd: info: Detected networking mode 'mirrored'.
    usbipd: info: Using IP address 127.0.0.1 to reach the host.
    

    Run:

    usbipd list
    

    and the output should be updated to:

    Connected:
    BUSID  VID:PID    DEVICE                                                        STATE
    1-3    8087:0032  Intel(R) Wireless Bluetooth(R)                                Not shared
    1-5    1bcf:2283  NexiGo N930AF FHD webcam, NexiGo N930AF FHD webcam Audio      Not shared
    1-7    1532:0287  USB Input Device, Razer BlackWidow V4                         Not shared
    1-10   0781:5581  USB Mass Storage Device                                       Attached
    1-12   1b1c:0c0c  USB Input Device                                              Not shared
    1-13   26ce:01a2  USB Input Device                                              Not shared
    2-3    1532:0067  Razer Naga Trinity                                            Not shared
    6-1    10d6:b00d  HiDock_H1E                                                    Not shared
    6-2    2ec2:0004  USB Serial Device (COM9), Loupedeck Live                      Not shared
    9-1    1997:2466  USB Input Device                                              Not shared
    9-4    1395:005c  HiDock H1E, USB Input Device                                  Not shared
    10-2   0bda:8153  Realtek USB GbE Family Controller #2                          Not shared
    
    Persisted:
    GUID                                  DEVICE
    
  4. Step 4: Verify in WSL2

    Open WSL2 Debian and run:

    wsl
    
    lsblk -d -o NAME,SIZE,MODEL,TRAN,RM | grep -E "usb|USB"
    

    You should see an output similar to:

    sdi    28.7G SanDisk 3.2Gen1 usb   1
    

    Always verify your target device shows:

    • Correct size (your USB capacity)
    • "usb" in TRAN column
    • "1" in RM (removable) column

    Double-check this is your USB as writing to the wrong device will erase it.

    Now you’re ready to use dd.

  • Write the Verified ISO to USB

    Now that we’ve cryptographically verified our ISO, we must write it to USB and then verify that the write was bit-for-bit perfect. A corrupted or miswritten USB will fail to boot, or worse, behave unpredictably during installation.

    NEVER run dd on these devices (they're your system): /dev/sda, =/dev/sdb, /dev/sdc, etc. (These are typically WSL2 virtual disks) Only write to the device identified in Step 4, /dev/sdi in this case.

    Set the paths for the variables; The WSL username may differ from the Windows username. Verify the correct path with:

    ls /mnt/c/Users/*/Downloads/debian*
    

    Then set the user using your actual Windows username. Assuming the user is "user"

    WIN_USER="user"
    

    Assuming your USB is "/dev/sdi" and your ISO is in "/mnt/c/Users/user/Downloads\"/

    ISO="/mnt/c/Users/$WIN_USER/Downloads/debian-13.1.0-amd64-netinst.iso"
    USB_DEVICE="/dev/sdi" # or /dev/ + the lsblk output
    

    Run the command:

    sudo dd if="$ISO" of="$USB_DEVICE" bs=4M status=progress oflag=sync
    sudo sync
    

    Command Explanations:

    dd
    “data duplicator,” a low-level block copy tool.
    if=...
    input file (your verified ISO).
    of=...
    output file (your USB device).
    bs=4M
    block size for faster writes.
    status=progress
    show progress.
    oflag=sync
    ensure all data is written before completion.

    Want to insert your own USB path? Use these instructions. Always eyeball the result before you paste into a terminal.

    sudo dd if="__ISOPATH__" of="__USBDEV__" bs=4M status=progress oflag=sync
    sudo sync
    

    #+beginexport html <input type="text" id="ISOPath" class="dynamic-input" data-placeholder="ISOPATH" value="ISO Download path"> #+endexport html #+beginexport html <input type="text" id="usbDevice" class="dynamic-input" data-placeholder="USBDEV" size="10" value="/dev/sdi"> #+endexport html

    The output should be similiar to:

    821035008 bytes (821 MB, 783 MiB) copied, 104 s, 7.9 MB/s
    195+1 records in
    195+1 records out
    821035008 bytes (821 MB, 783 MiB) copied, 104.264 s, 7.9 MB/s
    

    Now we will verify the etching was completed successfully by performing a final checksum on the USB Drive.

    # Get the original ISO hash from the JSON file
    ISO_HASH_FROM_FILE=$(grep -o '"IsoSha512":\s*"[^"]*"' /mnt/c/Users/$WIN_USER/Downloads/debian-integrity.json | cut -d'"' -f4)
    
    # Calculate USB hash
    USB_HASH=$(sudo head -c "$BYTES" "$USB_DEVICE" | sha512sum | awk '{print $1}')
    
    # Compare (case-insensitive since JSON might be uppercase)
    if [ "${USB_HASH^^}" = "${ISO_HASH_FROM_FILE^^}" ]; then
        echo "✓ USB write verified successfully - hashes match"
        echo "Original: $ISO_HASH_FROM_FILE"
        echo "USB:      ${USB_HASH^^}"
    else
        echo "✗ CRITICAL: Hash mismatch - USB is corrupted!"
        echo "Original: $ISO_HASH_FROM_FILE"
        echo "USB:      ${USB_HASH^^}"
    fi
    

    Here's the output I received:

    ✓ USB write verified successfully - hashes match
    Original: 873E9AA09A913660B4780E29C02419F8FB91012C8092E49DCFE90EA802E60C82DCD6D7D2BEEB92EBCA0570C49244EEE57A37170F178A27FE1F64A334EE357332
    USB:      873E9AA09A913660B4780E29C02419F8FB91012C8092E49DCFE90EA802E60C82DCD6D7D2BEEB92EBCA0570C49244EEE57A37170F178A27FE1F64A334EE357332
    

    The two hashes must match exactly.

    If they don’t:

    • Try re-writing the USB.
    • Try a different USB port or drive.
    • Never proceed with a mismatched hash — your USB is corrupted.

    This is the final checkpoint before booting. Sovereignty means verifying every link in the chain — download → signature → write → boot.

  • 4. OS Installation

    Before we install, we need 2 last peices of information from the machine: We'll need to know two things from your local network:

    • Your router's IP address (e.g., 192.168.1.1).
    • A free IP address on that network (e.g., 192.168.1.123).

    From a Windows machine - Discover usable static-IP triple (IP/mask, gateway, next-free)

    $Route = Get-NetRoute -DestinationPrefix "0.0.0.0/0" -ErrorAction Stop
    $GW    = $Route.NextHop
    $Adapter = Get-NetAdapter -InterfaceIndex $Route.ifIndex -ErrorAction Stop
    $IPconf  = Get-NetIPAddress -InterfaceIndex $Adapter.ifIndex -AddressFamily IPv4 -ErrorAction SilentlyContinue
    if (-not $IPconf) { Write-Error "No IPv4 on adapter $($Adapter.Name)"; exit 1 }
    
    # Build subnet base (192.168.1.0/24 → 192.168.1)
    $Base  = $IPconf.IPAddress -replace '\.\d+$',''
    $Mask  = $IPconf.PrefixLength
    
    # Find first free host address (x.x.x.200 → x.x.x.250)
    $Start = 200
    $End   = 250
    for ($i = $Start; $i -le $End; $i++) {
        $Candidate = "$Base.$i"
        if (-not (Test-Connection $Candidate -Count 1 -Quiet -TimeoutSeconds 100)) {
            Write-Host "Use these in the installer:"
            Write-Host "IP address : $Candidate/$Mask"
            Write-Host "Gateway    : $GW"
            break
        }
    }
    

    Boot the laptop from the USB stick, attach the USB-Ethernet adapter, plug into the router and begin the installation.

    Since the laptop has a built in Network adapter, the installer may ask to load non-free firmware for the built-in Wi-Fi.

    • (n) keeps the installation 100% open-source
    • (y) enables wireless networking.

    Using a USB-to-Ethernet adapter, I was presented with the following error:

    Network autoconfiguration failed
    
    Your network is probably not using the DHCP protocol. Alternatively, the DHCP server may be slow or some network hardware is not working properly.
    

    Skip network autoconfiguration; we’ll set a static address from Dom0 where we have better debugging tools.

    On the partition screen, we will select

    Guided - use entire disk and setup LVM with encryption.
    

    The best option for a stable Xen hypervisor is the classic server setup

    Separate /home, /var, and /tmp partitions.
    

    This scheme provides the isolation needed to run a stable hypervisor. When the installer asks you how to allocate the space, Allocate ~70 % of available space to /var; exact GiB numbers aren’t critical because LVM lets you resize later, as this is where your VMs will live. A good starting point might be 20% for /, 8% for /home, 2% for /tmp, and the rest for /var.

    This will seperate the core Debian operating system, protecting it from runaway processes. By default, the virtual disk images of your Xen VMs (/var/lib/xen/images). By giving /var its own large partition, you ensure that even if a VM's disk grows unexpectedly or a log file gets huge, it can only fill up the /var partition and will not crash the core OS.

    Since we are using LVM, these sizes aren't set in stone and can be adjusted later, but this layout provides the right structure from the start.

    For the package mangager, we will continue without a network mirror.

    On the Software Selection screen: Deselect everything except SSH server and standard system utilities.

    Finish the install. Before you reboot, capture the LUKS parameters shown below—needed for the sovereignty audit in P1.

    ⚠️ Sovereignty checkpoint – copy once, audit later On the partition summary screen, open a second virtual console: Alt-F2 → Enter → run:

    cryptsetup luksDump /dev/nvme0n1p2 | grep -E 'Cipher|PBKDF|Iterations|Key:|UUID'
    

    Example output (yours will differ slightly):

    Cipher:      aes-xts-plain64
    Key Size:    512 bits
    PBKDF:       pbkdf2
    Iterations:  506481
    UUID:        12345678-1234-1234-1234-123456789abc
    

    Write those lines into the installer’s “Go Back” → “Save Debug Logs” prompt, or snap a phone photo.

    5. Server Configuration

    Now that we are officially booted into the minimal host OS, there are a few configuration steps before moving on to the Virtualization. First, you need to get your USB Ethernet adapter working and set up your package manager's sources.

    All of these commands require administrator privileges.

    su -
    
    su
    This stands for "substitute user." By default, it switches you to the root user if you don't specify another username.
    -
    This is a flag that tells su to start a login shell. This means it simulates a full login as the new user.
    1. Initial Network Bridge Configuration

      For guest VMs (DomUs) to access the network, we must create a network bridge. The bridge acts like a virtual switch, sharing the physical network card with the VMs.

      First, identify your primary physical network interface: Find your network interface name.

      ip a
      

      Look for an interface that isn't `lo`. It will have a name like `enp3s0` or `enx…` for a USB adapter.

      1: lo: <LOOPBACK, UP, LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
      ...
      ...
      ...
      2: enx00051bde17f2: <BROADCAST, MULTICAST, DOWN, LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
      ...
      ...
      ...
      3: wlp1se: <BROADCAST, MULTICAST> mtu 1500 qdisc noop state DOHN group default qlen 1000
      ...
      ...
      ...
      

      The USB Ethernet adapter (enx…) is recognized by the system, but it's not active state DOWN. Because it's down, it never asked your router for an IP address, so you have no network connection and DNS lookups are failing.

      ip link set enx00051bde17f2 up
      
      ip a
      
      1: 10: <LOOPBACK, UP, LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
      link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
      inet 127.0.0.1/8 scope host lo
      valid_ift forever preferred_1ft forever
      inet6 /128 scope host noprefixroute
      valid_ift forever preferred_lft forever
      2: enx00051bde17f2: <BROADCAST, MULTICAST, UP, LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
      link/ether 00:05:1b:de:17:12 brd ff:ff:ff:ff:ff:ff
      inet6
      /64 scope global dynamic mngtmpaddr proto kernel_ra
      valid_lft 85456sec preferred_1ft 63856sec
      inet6
      /64 scope link proto kernel_11
      valid_lft forever preferred_lft forever
      3: wlp1se: <BROADCAST, MULTICAST> mtu 1500 qdisc noop state DOHN group default qlen 1000
      link/ether 30:95:09:19:f9:cb brd ff:ff:ff:ff:ff:ff
      altname wlx3c9509f9f9cb
      

      The ip a output shows the adapter and cable are working perfectly. The inet6 addresses mean your laptop is already communicating with your router over IPv6. The problem is strictly with IPv4.

      We'll manually assign one temporarily. This will get us online so we can install the proper tools.

      Here are the steps to take as root:

      Assign a static IP address. Replace 192.168.1.123/24 with a free IP address on your network.

      ip addr add 192.168.1.123/24 dev enx00051bde17f2
      

      The /24 is the subnet mask and is standard for home networks.

      Set the default gateway. This tells your laptop how to reach the internet. Replace 192.168.1.1 with your router's IP address.

      ip route add default via 192.168.1.1
      

      Configure DNS. You need to tell the system where to look up domain names.

      cat > /etc/systemd/network/10-usb0.network <<EOF
      [Match]
      Name=enx*
      
      [Network]
      DHCP=no
      Address=192.168.1.123/24     # <-- replace with verified open IP address
      Gateway=192.168.1.1
      DNS=1.1.1.1
      EOF
      

      Unplug and replug your USB Ethernet adapter. You should now have a network connection. You can test it by running ping 1.1.1.1. You'll need to end the ping test with a keyboard interruption, Ctrl+c.

      By default, Debian has a network interface (like enx...) that connects directly to the internet. For future guest VMs (DomUs) to access the network, we need to create a network bridge.

      The bridge acts like a virtual network switch. Your physical network card will be attached to the bridge, and all your VMs will also connect their virtual network cards to this same bridge, allowing them to share the network connection seamlessly.

      I had to edit my network configuration file, this is typically located at /etc/network/interfaces.

      # Example /etc/network/interfaces for bridging
      
      # The loopback network interface
      auto lo
      iface lo inet loopback
      
      # The primary network interface
      # This line makes the physical NIC part of the bridge
      allow-hotplug enx00051bde17f2
      iface enx00051bde17f2 inet manual
      
      # The bridge setup
      auto br0
      iface br0 inet dhcp
      bridge_ports enx00051bde17f2
      bridge_stp off
      bridge_fd 0
      

      Verify the bridge is working:

      ip a show br0
      

      My output was:

      4: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
          link/ether 06:8d:4f:d1:17:71 brd ff:ff:ff:ff:ff:ff
          inet 192.168.1.109/24 brd 192.168.1.255 scope global dynamic br0
             valid_lft 67363sec preferred_lft 67363sec
          inet6 2605:a601:8012:cd00:48d:4fff:fed1:1771/64 scope global dynamic mngtmpaddr proto kernel_ra
             valid_lft 86394sec preferred_lft 64794sec
          inet6 fe80::48d:4fff:fed1:1771/64 scope link proto kernel_ll
             valid_lft forever preferred_lft forever
      

      Configure the APT sources to ensure the package manager can find all necessary software.

      nano /etc/apt/sources.list
      
      #deb cdrom: [Debian GNU/Linux 13.1.0_Trixie_ - Official amd64 NETINST with firmware 20250906-10:22]/ trixie contrib main non-free-firmware
      
      # This system was installed using removable media other than
      # CD/DVD/BD (e.g. USB stick, SD card, ISO image file).
      # The matching "deb cdrom" entries were disabled at the end
      # of the Installation process.
      # For information about how to configure apt package sources.
      # see the sources.list (5) manual.
      deb http://deb.debian.org/debian/ trixie main non-free-firmware contrib
      deb-src http://deb.debian.org/debian/ trixie main non-free-firmware contrib
      
      deb http://security.debian.org/debian-security trixie-security main non-free-firmware contrib
      deb-src http://security.debian.org/debian-security trixie-security main non-free-firmware contrib
      
      deb http://deb.debian.org/debian/ trixie-updates main non-free-firmware contrib
      deb-src http://deb.debian.org/debian/ trixie-updates main non-free-firmware contrib
      
      apt update
      

      Now, you can install the remote access server, OpenSSH.

      apt install openssh-server
      

      Back at the workstation, install the Secure Shell software.

      sudo apt install ssh
      

      Generate a key pair if you don't have one:

      ssh-keygen -t ed25519 -C "[email protected]"
      

      The email is for pure convenience, SSH ignores it cryptographically. when you later open authorizedkeys on some server you can see whose key that line is without matching long fingerprints.

      Copy the public key to your Xen laptop:

      ssh-copy-id user@laptop_ip
      

      Now, use the key-pair to access the laptop remotely:

      ssh user@laptop_ip
      

      This setting only allows log in using an SSH key, which is secure.

      sudo mkdir -p /root/.ssh
      

      Set the permissions

      chmod 700 /root/.ssh
      

      Now, we will also add the key to the root:

      cat >> /etc/ssh/sshd_config <<EOF
      
      # Sovereignty stack hardening
      PermitRootLogin prohibit-password
      PasswordAuthentication no
      AuthenticationMethods publickey
      EOF
      systemctl restart ssh
      

      From your desktop, make sure you can SSH into the Xen host without a password. This is the ultimate test.

      You can see how to headlessly reboot a server using LVM encryption with an SSH connection here: Headless LUKS unlock using SSH