In order to install Windows 11, my laptop’s Trusted Platform Module firmware needs upgrading to support TPM 2.0. Dell provide a firmware update for some models, including mine. However installing it is complicated by Windows 10, which it is currently running, re-initialising the TPM on shutdown; breaking the firmware update process, which detects the TPM has data and aborts the update when it goes to apply it on next reboot.

Updating TMP to 2.0

Dell provide instructions for disabling this behaviour by opening PowerShell as an administration (right-click and select Run as Administrator) and running:


After this, the TPM must be cleared by running tpm.msc (click start and type that command in) and choosing Clear TPM... from the “Actions” list on the right-hand side. Windows will prompt to reboot, which must be done to clear the TPM, after which the firmware will finally update.

Installing Windows 11

I also had to use the media creation tool in a Windows VM on macOS to create a bootable USB drive, to install on a new hard disk in my laptop (so no bare-metal Windows system available to me) as I couldn’t find a way to create a bootable USB from the ISO on macOS - despite following several guides and trying 3 different tools that claimed to be able to do it.

After the first install I discovered that, although the old hard disk had Windows 10 Professional installed, because the laptop shipped with Windows 10 Home it was not possible to do a clean install of Windows 11 Professional - it insisted on installing Home and would not accept the Windows 10 Professional product key to “upgrade” to 11 Professional.

In the end, I had to reinstall Windows 10 Professional, activate it then do an in-place upgrade to 11 Professional (which was also easier said than done). Again, because of the embedded digital licence, both Windows 10 and 11 install media default to installing the “Home” version despite my having a fully-licences Professional key linked to this machine and my Microsoft account. To get around this, I had to add a file called ei.cfg to the sources folder on the USB install disk with this content:


The installer will then stop and prompt for the version (Home/Professional/Education etc.) to install. Doing this with the Windows 10 media, I was able to reinstall Windows 10 Professional that immediately activated once I logged into my Microsoft Account (as well it should, this is what was on the hard disk I removed). I then did an in-place upgrade to Windows 11, using the USB drive I had already created, which upgraded to Windows 11 Professional. Once I checked it was reporting itself as activated, I re-installed (doing a clean install this time) from the Windows 11 USB disk (again, with the above ei.cfg file to force it to give me the option to install Professional instead of Home). This then activated from the digital licence now linked to my Microsoft Account from the upgrade of Windows 10 on the laptop.

However, to create space for Linux I ended up re-installing again, this time disabling Intel Rapid Storage Technology by setting the internal disk mode to AHCI instead of Intel RAID and after using a Linux Live CD to create partition over last 50% of disk to reserve space for Linux to be installed later.

Post install Windows configuration

Once I finally had a clean install of Windows 11 Professional, I installed the Dell SupportAssist and allowed it to update the drivers etc. While I was using SupportAssist, Logitech Options also popped up to be installed (I presume Windows went off and fetched it due to having a Logitech keyboard and mouse plugged in).

In SupportAssist, it pops up prompting me to setup “OS Recovery”, I let it do this a couple of times but it always failed (with no message to indicate why) leaving 2 new useless partitions (since the OS Recovery pre-boot option will not start - laptop goes to a blue screen with no text or graphics then turns off if selected) and all of the OS Recovery options disappear from the troubleshooting pane of SupportAssist after the install goes to the “failed” state. In the end, I repartitioned back to how I started and reinstalled to recover the ~17GB of disk space it had stolen.

I also had to manually download and install the Intel Chipset Drivers from Dell’s Drivers and Downloads page to resolve 3 unknown devices showing up in Device Manager. I also had to manually install the Realtek IR Camera Driver for the webcam to be detected at all (did not even show in device manager) and the Intel Dynamic Platform and Thermal Framework Driver and Intel Processor Power Management Utility, to stop the fans spinning all the time. I also installed the Realtek Memory Card Reader Driver, to pre-empt discovering that device does nto work. Little annoyed SupportAssist did not recognise these were needed and install them, as they come up with searching by my laptop’s service tag which is what SupportAssist appears to use to determine what drivers apply to the system. At some-point (when I’m less rushed) I will have to go down the full list of drivers and see what, if any, others are missing.

Post install, I installed Office 2019, NextCloud client, KeePassXC, 7-zip, Visual Studio Code, Git and OpenVPN to get a set of basic productivity tools available. As Windows 11 came with Microsoft Terminal and an ssh client out-of-the-box, I opted not to install PuTTY. I would like to automate this (looking at Ansible) here but I needed to get this done quickly so just did it the “old fashioned way”.

During the Visual Studio Code install, I ticked the Create a desktop icon, Add "Open with Code" action to Windows Explorer file context menu and Add "Open with Code" action to Windows Explorer directory context menu.

During the Git install, I ticked the Check daily for Git for windows updates and (NEW) Add a Git Bash Profile to Windows Terminal options. I also selected Override the default branch name for new repositories to use main instead of master (which has connotations relating to slavery and servitude, so is being phased out in favour of a more inclusive default).

Annoyingly the new Windows 11 does not show 7-zip context menu by default, you either have to right-click and select “more options” or select the file and press shift-F10 to bring up the “old” context menu.

I had to right-click on the taskbar and go into settings, then toggle the slider on “Chat” to hide that icon. Why can I not hide it through the right-click menu, like pinned application icons?

Finally, I installed and enabled rsyncd for my backup system. Modernising, by utilising volume-shallow-copies, and seeing if this can be done with native tools is on my very long to-do list but by the same token this same setup has been working excellently and unchanged for at least 14 years.

KeePassXC settings

In KeePassXC’s settings (Tools -> Settings) I changed the following values. The list below is structured as “Pane (-> Tab) (-> Section) -> Setting” (not all panes have tabs, not all tabs have sections but it should be obvious if you are looking at the dialogue where these are):

  • General
    • Basic Settings
      • Startup
        • Minimise window at application startup (ticked)
      • User Interface
        • Minimise instead of app exit (ticked)
        • Show a system tray icon (ticked)
        • Hide window to system tray when minimised (ticked)
    • Auto-Type
      • Global Auto-Type shortcut (Ctrl-Alt-A)
  • Security
    • Timeouts
      • Lock databases after inactivity of (240 sec)

Visual Studio Code settings

I signed in with my Microsoft account to allow it to sync settings (including extension).

I also tried to make a mental note of some of the keyboard shortcuts, this time around:

  • Ctrl+\ - Split Editor
  • Ctrl+' - built-in terminal
  • Ctrl+, - settings

Microsoft Edge

By default Microsoft Edge syncs a scary amount to Microsoft - I disabled most of it in the settings and told it never to offer to save passwords (which is not in sync, but in passwords).

Installing (Debian) Linux

The Debian installed does not, by default, include non-free (closed source) firmware which mean the laptop’s built in Atheros based wireless card was unavailable. Instead, I followed the Debian project’s recommended approach and downloaded their unofficial non-free including media. Note that from the next release, there will be a new repository section, non-free-firmware, which will be included on official installation media.

To show progress while copying the Debian image to a USB drive, I used pv (Pipe Viewer) to display a progress bar:

# -p = progress bar
# -t = timer (elapsed time)
# -e = eta (time to complete)
# -r = rate of transfer (current)
# -b = total bytes transferred
pv -pterb debian-live-11.5.0-amd64-gnome+nonfree.iso >/dev/sdb

Reading the Debian wiki page on UEFI boot I enjoyed this quote:

By using separate vendor directories like this, UEFI allows for clean interoperability between vendors. If only the firmware developers were competent… :-( Some implementations ignore the boot order altogether, some filter it and will only run things that claim to be “Windows”, etc. See below for tips on how to work around some of the known bugs in broken UEFI implementations.

I did a minimal Debian install, deselecting all additional software options (“Desktop environment” and “standard system utilities”), using this partition scheme (deleting the space-holding partition I previously created to stop Windows clobbering it), which is basically my standard way of installing any Linux system. Where applicable I have given (partition name/filesystem label - sizes - non-default mount options) in braces after what they are. With the exception of SWAP, LVM and crypt all filesystems are ext4. LVM volume group is set to the hostname as it ensure no clash if the disk is attached to another Linux system, e.g. for data recovery, in the future.:

  • boot (boot - 1G - nodev,nosuid,noexec)
  • Physical volume for encryption (Debian)
    • Physical volume for LVM
      • Volume group (defiant)
        • root (root - 30G)
        • swap (32G)
        • var (var - 30G - nodev,nosuid,usrquota,grpquota)
        • opt (opt - 10G - 0% reserved - nodev,usrquota,grpquota)
        • tmp (tmp - 10G - nodev,nosuid,usrquota,grpquota)
        • home (home - 50G - 0% reserved - nodev,nosuid,usrquota,grpquota)
        • srv (srv - 500G - 0% reserved - nodev,nosuid,noexec,usrquota,grpquota)

The LVM volumes were all mounted in their corresponding locations. I made srv particularly huge as I intend to copy my mirrors there for offline use. Filesystem options were chosen with the purpose of the filesystems in mind. For example, many tools write to /var as their running user, so usrquota may be helpful to manage space usage by misbehaving daemons. Despite it being good practice, I have not mounted /tmp, /var or /home noexec:

  • Having /tmp noexec as can cause some problems with some packages and/or software installers that presume they can extract to /tmp then run another binary from there.
  • noexec cannot be used with /var because dpkg (Debain’s package manager) runs scripts from /var/lib/dpkg during package installation.
  • At which point having /home noexec becomes no more than an inconvenience because any user can copy, or setup, their executables in /tmp or /var/tmp. If no generally user-accessible areas were noexec, this configuration would improve the security posture of the system by not allowing users to download or compiler and run their own random scripts/applications but as soon as one becomes exec they might as well all be.

Historically, I mounted /boot read-only and manually remounted read-write for kernel and initial ramdisk updates (to protect against anything tampering with those parts of the OS without my knowledge). With the advent of EFI, a separate /boot/efi also exists - I might (I have not decided) mount /boot and /boot/efi read-only once installed for the same reason. In theory a separate /boot is no longer required (as grub and it’s configuration end up in /boot/efi) however by default Debian installs a stub Grub configuration in /boot/efi/EFI/debian and expects the main grub configuration, kernels and initial ramdisks to be in /boot which precludes it existing within an encrypted container (need to load the initial ramdisk to get the tools to mount the encrypted volume). It has been reported that it is possible to move the files to the EFI partition however this is non-standard and contrary to what the Debian system expects.

Traditionally, I put /usr on a separate partition but with the unification of /(s)bin and /usr/(s)bin it is no longer possible to have a separate /usr filesystems - which I find a shame as most software goes in /usr so it has a different growth (and therefore space-management) profile to / however that said, it is nice not to have to think about what is in /(s)bin and what in /usr/(s)bin any more. The nature of /bin (just the essential files to find and mount the other filesystems, including /usr) has been replaced by the initial ramdisk in modern Linux. I did have to make / much larger than I usually do, as I usually have a very small / and larger /usr - on my first install attempt, with a 10G / the installer ran out of disk space.

I did find that the installer refused to use any of the Debian mirrors it listed. wget from the command line was able to access them over http but not https due to certificate not being recognised, which I presume is the problem with the install media. I proceeded with just using the packages from the install disk for now.

Post install configuration

After install, launching Windows from Grub up stalled with “BitLocker needs your recovery key to unlock your drive because Secure Boot policy has unexpectedly changed”. After providing the key Windows was happy to boot from Grub, but trying to launch it directly from the EFI boot list then requests the key. It appears Windows will only allow the last used boot route and all others will require re-validating the BitLocker key.

To do the initial setup, I needed to establish a network connection to get configurations and install packages. This can be done using ifupdown following the directions on the Debian wiki. This is a temporary setup until I install NetworkManager.

First, I located the wireless interface’s name:

iw dev

Then created a configuration file for it in /etc/network/interfaces.d (I named it after the interface, for ease of identification):

allow-hotplug wlp2s0
iface wlp2s0 inet dhcp
        wpa-ssid ESSID
        wpa-psk PASSWORD

Finally, it can be brought up with:

ifup wlp2s0

Bootstrapping requires installing both git (to fetch the configuration) and virtualenv (to install Ansible). The default minimal package selection has no python installed, so I decided to install virtualenv which will pull it in. Once the wifi was up, I just installed them with apt-get:

apt-get install git virtualenv python-apt3

Then as my normal user I created a virtual env, fetched my Ansible configuration files and installed Ansible (the bootstrap playbook is described in a post I didn’t write until December):

virtualenv ~/venvs/ansible
git clone ~/Projects/ansible-home
# Python requirements
~/venvs/ansible/bin/pip install -r ~/Projects/ansible-home/requirements.txt
# Ansible requirements
~/venvs/ansible/bin/ansible-galaxy install -r ~/Projects/ansible-home/requirements.yaml
# Bootstrap for Ansible (installs sudo, for example) - requires
# root password to become with 'su'
~/venvs/ansible/bin/ansible-playbook -i ~/Projects/ansible-home/inventory.yaml -c local -l $( hostname -s ) -K ~/Projects/ansible-home/bootstrap.yaml
# Get the (new) sudo group into the current session's group list by
# switching to it and then switching back to my current primary group
exec sg sudo newgrp `id -gn`
# Pre-authenticate a sudo session, so no password needed for a 5 minutes
sudo -l # Will prompt for password
# Apply the configurations
~/venvs/ansible/bin/ansible-playbook -i ~/Projects/ansible-home/inventory.yaml -c local -l $( hostname -s ) ~/Projects/ansible-home/site.yaml

(The recipe for adding the new group without starting a new session is from StackExchange.)

Hardware-specific settings

Making the console screen readable

My laptop’s 4k (native resolution 3840x2160) 13” screen is too small to read with the default Grub and console settings. My first thought was to set it to an alternative resolution but Grub’s videoinfo command (which requires secure boot to be turned off, or you get the error message Prevented by secure boot policy) only returned this list, of which only the native resolution is at the screen’s native 16:9 aspect ration (the others are all 4:3):

  • 3840x2160x32
  • 640x480x32
  • 800x600x32
  • 1024x768x32
  • 1280x1024x32
  • 1600x1200x32
  • 1920x1440x32

So, instead of trying to change the mode to a lower resolution in an appropriate aspect ratio, I looked at using a larger font instead. First thing I did was to install a the OpenType version of the Terminus font and console setup package:

apt-get install fonts-terminus console-setup-linux

(As an aside, I would have preferred to use Deja Vu Mono Sans font, however I could only get the TTF version and was struggling to generate a usable PSF format font for the terminal from it - with some perseverance and changing size settings I probably would have succeeded and may revisit this in the future.)

In /etc/default/console-setup I modified these 3 lines to use the Terminus font (I used Uni3 as Uni1 was displaying weirdly, in bold, making it hard to read). 16x32 is the largest font size available (according to one source, 32px is the upper limit of what the Linux console supports):


Once set, these can be applied by running setupcon and incorporated into the initramfs for early boot with update-initramfs -u.

I also got stung by Debian bug #847257 causing console-setup systemd unit git to fail sometimes, where the console-setup systemd unit fails with an error about a missing temporary file in /tmp (it’s a race-condition where systemd-tmpfiles-setup removes the temporary file that the other unit has just created and is working with). To work-around, I created both /etc/systemd/system/console-setup.service.d/override.conf and /etc/systemd/system/keyboard-setup.service.d/override.conf (the latter recommended in the bug report) with this content to not start it until systemd-tmpfiles-setup has finished removing old files from /tmp:


Finally, to get a decent sized font for Grub, I used grub-mkfont to generate a PF2 format font from the OpenType fond installed earlier:

grub-mkfont -s 30 -o /boot/grub/fonts/font-terminus-30pt.pf2 /usr/share/fonts/truetype/terminus/TerminusTTF-4.46.0.ttf

and told Grub to use it by adding this line to /etc/default/grub:


before finally rebuilding the Grub configuration with:


According to Ninja Units the corresponding pt size for 32px is 24pt, however when I experimented 30pt (40px according to Ninja Units) was a much closer match size-wise to the console do I went with that.

Backlight control

The backlight can be set via /sys/class/backlight/intel_backlight/brightness (is was set to 7500 when I first checked it). The maximum possible value (which turns out to be 7500 is in /sys/class/backlight/intel_backlight/max_brightness.

I also installed xbacklight to support easy integration with keymapping from the brightness keys to delta setting the brightness:

    -- Brightness 
    awful.key({                   }, "XF86MonBrightnessDown", function ()
              awful.spawn("xbacklight -dec 3") end),
    awful.key({                   }, "XF86MonBrightnessUp", function ()
              awful.spawn("xbacklight -inc 3") end),


One website says the laptop has a pixel density of 331.27 however Arch Linux’s wiki page on the topic says DPI may be different in the horizontal and vertical planes, so best to let Xorg calculated it from the screen’s dimentions. I did this by creating /etc/X11/xorg.conf.d/monitor.conf (the actual physical dimensions were taken from Dell’s technical specifications for the laptop) with the content:

Section "Device"
  Identifier "Internal GPU"
  Driver "intel"
  BusID "PCI:0:2:0"

Section "Monitor"
  Identifier "Internal panel"
  # Lie - this is ~22% larger than the actual screen size of
  # 293.76x165.24 (rounded to 294x165 when used directly here)
  # to get the content at a size I like (worked out my much
  # exprimentation).
  DisplaySize 358 202 

Section "Screen"
  Identifier "Screen0"
  Device "Internal GPU"
  Monitor "Internal panel"

I also added some configuration for the touchpad to /etc/X11/xorg.conf.d/touchpad.conf to enable detection and behaviour of 1-/2- and 3- finger tapping (left, right and middle click in that order):

Section "InputClass"
	Identifier "Touchpad"
	Driver "libinput"
	MatchIsTouchpad "on"
	Option "Tapping" "on"
	Option "TappingButtonMap" "lrm" # 1/2/3 finger tap - left/right/middle buttons

X Font library

I did a lot of trial and improvement to get the DPI/size that gave me a compromise between readability (size of content) and content quantity (maximising usage of the real-estate) on screen. Once the screen was at the optimum resolution, I set the font (Xft) DPI to match. I make it sound like I did the screen size then the font DPI - in practice I did a lot of back-and-forth between setting these two items to identify my sweet-spot.

The DPI I set in /etc/X11/Xresources/xft-dpi, along with some settings to optimise font rendering for the laptop’s screen:

Xft.dpi: 272
Xft.antialias: true
Xft.rgba: rgb
Xft.hinting: true
Xft.hingstyle: hintslight
Xft.lcdfilter: lcddefault

I installed AwesomeWM as my desktop environment and lightdm as my display manager.

Application specific High-DPI (HiDPI) settings

This page on the Arch Linux Wiki was very helpful for this.

NextCloud was the only client that did not “just work”. It was fixed by setting the envrionment variable QT_AUTO_SCREEN_SCALE_FACTOR to 1 as descibed in the bug report

To set it on startup, I edited ~/.config/autostart/com.nextcloud.desktopclient.nextcloud.desktop’s Exec line:

Exec=/usr/bin/env QT_AUTO_SCREEN_SCALE_FACTOR=1 /usr/bin/nextcloud --background

For ease of use, I installed NetworkManager and its OpenVPN plugin.

Although it might be nice to do something a bit more “native” in Awesome, to get things usable very quickly I installed networkmanager-dmenu by downloading and copying the script to ~/.local/bin. I also installed its dependencies, on Debian Bullseye: dmenu, libnm-dev, python3-gi and gir1.2-nm-1.0`.

To configure it, I created ~/.config/networkmanager-dmenu/config.ini:

dmenu_command = dmenu -i -l 25 -nb #909090 -nf #303030
# # Note that dmenu_command can contain arguments as well like:
# # `dmenu_command = rofi -dmenu -i -theme nmdm`
# # `dmenu_command = rofi -dmenu -width 30 -i`
# # `dmenu_command = dmenu -i -l 25 -b -nb #909090 -nf #303030`
# rofi_highlight = <True or False> # (Default: False) use rofi highlighting instead of '=='
# compact = <True or False> # (Default: False). Remove extra spacing from display
# pinentry = <Pinentry command>  # (Default: None) e.g. `pinentry-gtk`
# wifi_chars = <string of 4 unicode characters representing 1-4 bars strength>
wifi_chars = ▂▄▆█
# list_saved = <True or False> # (Default: False) list saved connections

# # Uses the -password flag for Rofi, -x for bemenu. For dmenu, sets -nb and
# # -nf to the same color or uses -P if the dmenu password patch is applied
# #
# obscure = True
# obscure_color = #222222

# terminal = <name of terminal program>
# gui_if_available = <True or False> (Default: True)

# rescan_delay = <seconds>  # (seconds to wait after a wifi rescan before redisplaying the results)

and I bound it to the Windows + Shift + n keyboard shortcut in Awesome:

    awful.key({ modkey, "Shift"  }, "n", function () awful.spawn.with_shell("$HOME/.local/bin/networkmanager_dmenu") end,
              {description = "open network manager dmenu", group = "launcher"}),


In the interest of speed, I simply used bluetoothctl to connect my bluetooth devices but I made a note to look into Awesome integration, perhaps with awesome-bluetooth from asdil12.


Although I did not initially set it up, I was going to look at using firewalld for firewalling on my laptop - mainly because of its integration with NetworkManager such as assigning the interface to zones based on network SSID.

Prior to writing this post, I was content with no firewall for the time being because I had not conciously installed any listening daemons (not even sshd) on the laptop however when I checked there was an alarming number of (in a couple of cases unexpected, to me at least) applications listening on UDP. I origianlly tried to use the traditional netstat command but apprently netstat has been superceeded by ss.

$ ss -tunlp
Netid           State             Recv-Q            Send-Q                                            Local Address:Port                        Peer Address:Port           Process                                             
udp             UNCONN            0                 0                                                            *               users:(("chromium",pid=48434,fd=137))              
udp             UNCONN            0                 0                                                                *               users:(("avahi-daemon",pid=726,fd=12))             
udp             UNCONN            0                 0                                                               *               users:(("avahi-daemon",pid=726,fd=14))             
udp             UNCONN            0                 0                                                          [::]:5353                                [::]:*               users:(("avahi-daemon",pid=726,fd=13))             
udp             UNCONN            0                 0                                                          [::]:48646                               [::]:*               users:(("avahi-daemon",pid=726,fd=15))             
udp             UNCONN            0                 0                            [fe80::9fdc:b34c:bfa2:4140]%wlp2s0:546                                 [::]:*               users:(("NetworkManager",pid=735,fd=26))

In particular, I was not expecting chromium or NetworkManager to have listening ports. Avahi I just needed to disable:

systemctl stop avahi-daemon
systemctl disable avahi-daemon

Some digging on Google revealed that to stop it listending for multicase DNS (mDNS), which like Avahi(which also does mDNS) a zero-configuration service, Disabling it requires turning off the inbuilt “media router” which can only be done via a policy. To set it, on Debian, I created the file /etc/chromium/policies/managed/multicase-dns-disable.json with the content:

  "EnableMediaRouter": false

After doing this, the only thing that was left listening is NetworkManager on the IPv6 DHCP client port (UDP 546):

$ sudo ss -tunlp
Netid            State             Recv-Q            Send-Q                                            Local Address:Port                       Peer Address:Port           Process                                             
udp              UNCONN            0                 0                            [fe80::9fdc:b34c:bfa2:4140]%wlp2s0:546                                [::]:*               users:(("NetworkManager",pid=735,fd=26))

Unfortunately it does not appear to be possible to tell NetworkManager to operate in SLAAC mode only and ddisable IPv6. Although I found one reference to a setting to disable it the setting value suggested does not exist in the documentation. This means it is not possible to stop NetworkManager listening on the DHCPv6 client port.

I installed firewalld and was a little dismayed to see that it permitted dhcpv6-client and ssh by default on a “public” zoned interface:

$ firewall-cmd --list-all
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: wlp2s0
  services: dhcpv6-client ssh
  forward: no
  masquerade: no
  rich rules:

To fully deny access from public networks, I changed that:

$ firewall-cmd --zone=public --remove-service ssh 
$ firewall-cmd --permanent --zone=public --remove-service ssh 
$ firewall-cmd --zone=public --remove-service dhcpv6-client
$ firewall-cmd --permanent --zone=public --remove-service dhcpv6-client

This removed those from the zone:

$ firewall-cmd --list-all
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: wlp2s0
  forward: no
  masquerade: no
  rich rules: