Below is a step-by-step hardening checklist that keeps your GPD Micro PC (Ubuntu Desktop 24.04.2 LTS, Ubuntu Pro enabled)
This guide assumes your primary workstation runs MacOS
Run the below from the terminal
curl -LO https://releases.ubuntu.com/24.04/ubuntu-24.04-desktop-amd64.iso
curl -LO https://releases.ubuntu.com/24.04/SHA256SUMS
shasum -a 256 ubuntu-24.04-desktop-amd64.iso
Compare the hash to the publisher’s list. (Skip if you’re in a hurry.)
diskutil list # find your card, e.g. /dev/disk3
diskutil unmountDisk /dev/disk3
sudo dd if=ubuntu-24.04-desktop-amd64.iso of=/dev/rdisk3 bs=4m status=progress
diskutil eject /dev/disk3
- Insert the card in the micro-SD slot (left side).
- Hold Fn + F7 while pressing the power button → pick UEFI: SD/MMC … from the blue list.
- The Linux installer appears (likely rotated 90°—normal on the MicroPC).
- Install to the internal SSD or run live; remove the card when done.
- Portrait screen? xrandr --output eDP-1 --rotate normal (add to ~/.xprofile).
- Wi-Fi firmware (Debian only): sudo apt install firmware-iwlwifi.
- Want persistence on the card? Use Ventoy + persistence plugin or mkusb.
That’s it—flash, Fn + F7, and you’re rolling Linux on your pocket-size powerhouse.
What | How |
---|---|
Set a BIOS/UEFI password | Prevents evil-maid attacks. |
Enable Secure Boot | Works with Ubuntu 24.04 by default. |
Disable external boot devices | Turn off “USB boot” and “PXE” in firmware. |
Update system firmware | sudo fwupdmgr get-updates && sudo fwupdmgr update |
- During the installer pick “Erase disk → Encrypt Ubuntu for security”.
- Use a strong, memorizable passphrase; store it in your password manager.
sudo apt update && sudo apt full-upgrade # bring everything current
sudo pro attach <YOUR-TOKEN>
sudo pro enable livepatch # kernel patching without reboots
sudo pro enable usg # turns on the Ubuntu Security Guide (USG)
sudo apt install usg # installs the CLI if it isn’t pulled in automatically
sudo usg generate-tailoring cis_level1_workstation my_cis.xml
sudo usg audit --tailoring-file my_cis.xml # see what would change
sudo usg fix --tailoring-file my_cis.xml # apply the remediations
USG for 24.04 ships with the official CIS benchmark, so the bulk of permissions, kernel parameters, and file-system flags are adjusted for you. 
# One-liner that captures every package installed today
today="$(date +%Y-%m-%d)"
dpkg -l | awk '$1=="ii"{print $2}' > ~/pkglist-$today.txt
apt-mark showmanual
Below is a traffic-light guide to the packages on your manual list.
🔴 RED is core to booting, logging-in or keeping the disk encrypted—leave it alone. 🟢 Everything in GREEN is non-essential for a “browser + Ledger” box and can be purged if you’re sure you won’t miss the feature. 🟡 YELLOW items are technically optional but keep them if you rely on the function they provide.
Keep / Remove? | Package(s) | Why it’s here | Action |
---|---|---|---|
🔴 MANDATORY | bsdutils dash diffutils findutils grep gzip hostname init login ncurses-* shim-signed grub-* efibootmgr linux-generic-hwe-24.04 ubuntu-minimal ubuntu-standard ubuntu-desktop-minimal ubuntu-advantage-tools cryptsetup lvm2 ubuntu-wallpapers ubuntu-restricted-addons |
Shell, coreutils, bootloader, kernel meta, LUKS, UA-Pro hooks, GNOME meta packages. Removing any of these = unbootable or upgrade-broken system. | KEEP |
🟡 Depends on your workflow | openssh-server – remote shell; net-tools – old ifconfig/netstat; usbguard usg – we deliberately installed these; aide – file-integrity scanner; ubuntu-wallpapers – cosmetic |
If you never SSH in, drop openssh-server. net-tools is superseded by iproute2. aide, usbguard, usg are useful for hardening—keep unless you have an alternative. | Remove only what you truly don’t use |
🟢 Safe to purge | Input-method & locale clutter ibus-table-cangjie-* libchewing3* libpinyin* libopencc* libm17n-0 libmarisa0 libotf1 m17n-db language-pack-en* language-pack-gnome-en* wbritish (the English language packs are optional on an English system—GNOME falls back to built-in C.UTF-8) Bluetooth / file-push extras blueman obexfs |
Chinese/Cangjie, Chewing, Pinyin, OpenCC are totally redundant for an English-only signing station. blueman & obexfs handle Bluetooth file transfers—turned off in your threat model | PURGE if you don’t need Chinese input or Bluetooth |
sudo apt purge --autoremove \
blueman obexfs \
ibus-table-cangjie-big ibus-table-cangjie3 ibus-table-cangjie5 \
libchewing3 libchewing3-data libpinyin-data libpinyin15 \
libopencc-data libopencc1.1 libm17n-0 libmarisa0 libotf1 m17n-db \
wbritish
(Skip any package the system says is “not installed”.)
sudo apt purge --autoremove \
aisleriot cheese evolution gnome-{calculator,calendar,characters,clocks,contacts,disk-utility,logs,maps,photos,sudoku,mahjongg,mines,music,tour,weather} \
libreoffice-* shotwell simple-scan remmina rhythmbox \
thunderbird totem transmission-gtk
These are the apps that ship in a default (not “Full”) Noble install, according to Ubuntu’s manifest and community docs. 
--autoremove
tidies up any orphan libraries those programs dragged in.
sudo apt autoremove --purge # sweep orphaned libs
sudo apt update && sudo apt full-upgrade
Reboot and confirm everything still works: encrypted drive unlocks, Xorg/Wayland starts, browser + Ledger Live operate, and UFW / USBGuard / USG are still live.
That’s it—your GPD Micro PC is now lean with zero language packs you don’t speak, no Bluetooth surface, and only the hardening tools you intentionally installed.
# list snaps, remove anything you don't need
snap list
# typical desktop snaps you can live without:
sudo snap remove snap-store gnome-characters gnome-clocks gnome-calculator
sudo snap remove gnome-logs gnome-system-monitor # if not needed
If you really want to match the “Minimal” ISO you can replace the standard meta-package:
# 1. Prevent accidental "Suggested" packages
echo 'APT::Install-Recommends "0";' | sudo tee -a /etc/apt/apt.conf.d/99no-recommends
echo 'APT::Install-Suggests "0";' | sudo tee -a /etc/apt/apt.conf.d/99no-recommends
# 2. Make newly-added snaps ask first
sudo snap set system refresh.hold="$(date --date='today + 30 days' +%Y-%m-%d)"
# 1. Prevent accidental "Suggested" packages
echo 'APT::Install-Recommends "0";' | sudo tee -a /etc/apt/apt.conf.d/99no-recommends
echo 'APT::Install-Suggests "0";' | sudo tee -a /etc/apt/apt.conf.d/99no-recommends
# 2. Make newly-added snaps ask first
sudo snap set system refresh.hold="$(date -u -d '30 days' +%Y-%m-%dT%H:%M:%SZ)"
Run sudo snap get system refresh.hold
to check the hold value is set to today.
sudo apt update && sudo apt full-upgrade
sudo apt autoremove --purge
sudo ufw default deny incoming
sudo ufw default allow outgoing
If you're accessing the PC with SSH, make sure to allow SSH
sudo ufw allow ssh
Rate-limit brute-force attempts (5 conn / 30 s):
sudo ufw limit 22/tcp
Optionally, restrict to a single trusted IP or subnet (safer):
# Replace 203.0.113.4 with your workstation’s public IP
sudo ufw allow from 203.0.113.4 to any port 22 proto tcp
or a whole CIDR block
sudo ufw allow from 203.0.113.0/24 to any port 22 proto tcp
sudo ufw enable
Verify the rules
sudo ufw status verbose
You should see something like:
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip
To Action From
-- ------ ----
22/tcp LIMIT IN Anywhere
22/tcp (v6) LIMIT IN Anywhere (v6)
Setting | Recommended value | Command / note |
---|---|---|
Default incoming policy | deny | sudo ufw default deny incoming (already set) |
Default outgoing policy | allow | sudo ufw default allow outgoing (already set) |
SSH rule added | allow 22/tcp (or scoped) |
see above |
Log level | low (helps audit) | sudo ufw logging low |
sudo apt install usbguard
sudo usbguard generate-policy > rules.conf
The generate-policy
command scans every USB device the kernel currently knows about and writes a baseline rule-set: “allow everything that’s plugged in right now; block everything else by default.” Each line it prints is an allow rule like the ones you just viewed—complete with VID:PID, serial, name, hash, parent-hash, port path, interface classes, and connect type.
echo "allow id 2c97:* serial "*" hash "*" name "*" with-interface * with-connect-type *" >> rules.conf
echo "block with-interface *" >> rules.conf
sudo mv rules.conf /etc/usbguard/rules.conf
sudo systemctl enable --now usbguard
Everything else (thumb-drives, malicious HID devices) is blocked unless manually approved.
You can now plugin your ledger and run sudo usbguard watch
to watch it connect in realtime.
# Fetch the signed AppImage
wget -O ~/Downloads/ledger-live.AppImage https://download.live.ledger.com/latest/linux
chmod +x ~/Downloads/ledger-live.AppImage
wget -q -O - https://raw.githubusercontent.com/LedgerHQ/udev-rules/master/add_udev_rules.sh | sudo bash
# Install libfuse2 for ledger
sudo apt update
sudo apt install libfuse2 # 280 KB, no reboot needed
# (Optional) be sure the kernel module is loaded
sudo modprobe fuse # should return nothing
# Unpack the AppImage
./ledger-live.AppImage --appimage-extract # creates ./squashfs-root
# Move it where only root can write
sudo mv squashfs-root /opt/ledger-live
# Fix the helper’s ownership & mode
sudo chown root:root /opt/ledger-live/chrome-sandbox
sudo chmod 4755 /opt/ledger-live/chrome-sandbox # set-uid root
# Launch Ledger Live
/opt/ledger-live/ledger-live-desktop
Because the files now live on a real ext4 partition (not a FUSE nosuid mount) the set-uid bit is honored and the error disappears. This keeps Ledger Live fully sandboxed.
Use Firefox Snap (already confined with AppArmor) or run any browser with Firejail for an extra seccomp + namespace cage:
sudo apt install firejail firejail-profiles
firejail --private --dns=9.9.9.9 --seccomp firefox
gsettings set org.gnome.desktop.media-handling automount false
gsettings set org.gnome.desktop.media-handling automount-open false
This complements USBGuard in case a device slips through.
Ubuntu already ships many profiles in /etc/apparmor.d/. Ensure they load:
sudo aa-enforce /etc/apparmor.d/*
Add extra hardening to /etc/sysctl.d/99-hardening.conf (example):
kernel.kptr_restrict = 2
kernel.dmesg_restrict = 1
net.ipv4.conf.all.rp_filter = 1
fs.protected_fifos = 2
Apply with sudo sysctl --system.
sudo install -d -m 0755 /etc/apt/keyrings
wget -qO- https://dl.google.com/linux/linux_signing_key.pub |
sudo gpg --dearmor -o /etc/apt/keyrings/google-linux-signing-keyring.gpg
# create repo defination
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/google-linux-signing-keyring.gpg] http://dl.google.com/linux/chrome/deb/ stable main" | sudo tee /etc/apt/sources.list.d/google-chrome.list
# update and install
sudo apt update
sudo apt install google-chrome-stable
sudo apt update
# build & HID libraries
sudo apt install -y python3-pip python3-venv pipx \
libusb-1.0-0-dev libhidapi-libusb0 libhidapi-dev
pipx ensurepath # adds ~/.local/bin to $PATH on next login
curl -sSL https://raw.githubusercontent.com/LedgerHQ/udev-rules/master/add_udev_rules.sh | sudo bash
sudo udevadm control --reload