🔖 Index
Every section of this wiki, in order. Each generator control right-clicks to its entry here, and each entry is a link you can share.
- CPU Architecture: x86_64 or aarch64
- Firmware Setup and Lockdown
- Manual Install — Choose Your Own Path
- Choose Your Own Path
- Dual Booting with Windows
- Generator Configuration Steps
- Generator Option Reference
- Advanced App Configuration
- 100% Libre Software Policy
- Security Suite & Live Releases
- Installing the Suite in One Step
- Security Audit of These Tools
- Memory Integrity Checker
- Build Integrity & Reproducible Builds
- Verifying Downloads (GPG & Hashes)
- LUKS Duress Passphrase
- LUKS Auto-Lock
- USB Kill Switch
- Encrypted DNS
- UFW Firewall Profiles
- Fail2ban Intrusion Prevention
- Endlessh (SSH Tarpit)
- Hardware Security Key (FIDO2 / U2F)
- Third-Party Security Tools
- Hardware & Firmware Security
- The AUR, and How Not to Get Owned by It
- Cheatsheets
⚙️ Generator Configuration Steps
This section explains every dropdown option available in the Arch Guides Dynamic generator.
1. Firmware & Disk
- Firmware Type: Select UEFI (modern) or Legacy BIOS (old). UEFI is strongly recommended.
- File System: Btrfs supports snapshots and subvolumes. Ext4 is the traditional reliable choice.
- Target Disk: The physical drive to format (e.g., /dev/sda, /dev/nvme0n1). Warning: Use
lsblkto verify the correct drive, as it will be entirely wiped.
2. Encryption & Boot
- Partitioning & LUKS: LUKS2 provides modern encryption defaults (Argon2id). Choose LUKS1 only if constrained by legacy BIOS/GRUB limitations. Unencrypted is not recommended for portable devices.
- Init System: systemd is standard. OpenRC/dinit are for advanced users preferring alternative service managers.
- Bootloader: UKI (Unified Kernel Image) bundles the kernel, initramfs, and cmdline into one Secure Boot-compatible EFI file. GRUB is standard. systemd-boot is a lightweight alternative.
- Linux Kernels: linux-hardened includes grsecurity-like patches for exploit mitigation. linux-zen provides desktop performance.
3. Desktop & Software
- Software Type: 'Strictly Libre' installs only open-source software without proprietary blobs. Proprietary enables microcode and closed-source drivers (e.g., Nvidia).
- Desktop Environment: Choose Dusky (custom Hyprland/Wayland), standard Hyprland, DWM, or no GUI.
- Display Server: Wayland is modern and secure; Xorg is legacy but sometimes required for older apps/hardware.
🔗 Generator Option Reference
Right-clicking any dropdown in the generator jumps straight to its entry here. Each one explains what the setting does and how to configure it by hand if you would rather not use the generator at all.
Firmware Interface
UEFI is required for Unified Kernel Images, systemd-boot and Secure Boot. Legacy BIOS can only use GRUB and cannot read a LUKS2 header at boot, so the generator restricts you to GRUB and LUKS1 when you pick it. Check which you booted with: ls /sys/firmware/efi — if that directory exists, you are on UEFI.
File System
BTRFS gives subvolumes, transparent zstd compression and instant snapshots (paired with Snapper). Ext4 is the conservative, universally understood choice. XFS performs well on large sequential workloads but cannot shrink.
Target Disk
The whole device is wiped. Run lsblk -f and match on size and existing partitions before committing. In VMs this is usually /dev/sda; on NVMe hardware, /dev/nvme0n1.
Layer 1: Base Disk Encryption
LUKS2 with Argon2id is the recommended default — memory-hard key derivation makes brute force expensive. LUKS1 exists only for legacy GRUB compatibility. LVM on LUKS2 puts a volume group inside the encrypted container, so you can add or resize logical volumes later. Unencrypted offers no protection for a device that leaves your house.
Layer 2: Cipher Algorithm
AES-256-XTS is the standard and is hardware-accelerated on essentially all modern CPUs (check grep aes /proc/cpuinfo). AES-256-GCM adds authentication but is unusual for full-disk use. Serpent-256-XTS has a larger security margin but no hardware acceleration, so expect a noticeable throughput cost.
Layer 3: Post-Quantum Overlay
Kyber-1024 is a NIST-selected post-quantum KEM. Support in the Linux boot path is experimental and may prevent the system booting at all. Leave this at None unless you are specifically researching it, and test in a VM.
Layer 4: LUKS Duress Password
See the full LUKS duress passphrase section. The erasing options are irreversible.
Duress Decoy Environment
Which environment a duress boot presents. Requires a pre-created partition labelled decoy — see setting up the decoy volume.
Init System
systemd is the Arch default and enables the sd-encrypt initramfs hook plus UKI support. busybox/udev is the traditional path using the encrypt hook. Note that choosing busybox while also selecting a systemd timer for auto-updates is contradictory; the generator warns about that.
Bootloader & Secure Boot
UKI + Custom Keys enrols your own Secure Boot keys and signs a single EFI binary containing kernel, initramfs and cmdline — nothing unsigned on the ESP to tamper with. UKI + Shim uses the Microsoft-trusted shim so you need not enrol keys in firmware. systemd-boot is a minimal UEFI manager. GRUB is the only option under BIOS and leaves /boot unencrypted.
Main Kernel
linux-hardened applies exploit-mitigation patches and stricter defaults at some performance cost. linux is the balanced mainline. linux-zen favours desktop latency. linux-lts favours stability.
Backup Kernel
A second kernel to boot when an update leaves the main one unbootable. Strongly recommended — linux-lts is the usual choice. Selecting None means a bad kernel update leaves you reaching for install media.
CPU Brand
Selects the microcode package: amd-ucode or intel-ucode. Microcode carries silicon errata and speculative-execution mitigations, so it matters for security, not just stability.
GPU Brand
AMD and Intel use the libre Mesa stack. For NVIDIA the generator installs the proprietary driver unless your software type is Libre or Open Source, in which case it falls back to nouveau — which is considerably slower on recent cards.
VM Guest Setup
Installs and enables the matching guest agent for clipboard sharing, display resizing and clean shutdown: VirtualBox, VMware (open-vm-tools), or QEMU/KVM (qemu-guest-agent).
Software Type
Fully Libre avoids proprietary blobs entirely and prefers opendoas and pfetch. Open Source + Firmware permits redistributable firmware. Open Source + Proprietary allows closed drivers such as NVIDIA. Picking Fully Libre while also selecting proprietary applications is flagged as a conflict — see the libre policy.
Swap Size
Created as a swapfile (a BTRFS-native one on BTRFS). Hibernation needs swap at least the size of RAM; the generator blocks hibernation with no swap. 0GB is viable on a machine with plenty of RAM that never hibernates.
USB Kill Switch
See the full USB kill switch section. Start with "lock session"; the shutdown actions fire without confirmation.
USB Kill Trigger
Whether to react to an unlisted device being connected, an allowlisted device being removed, or both.
Automatic System Updates
Pacman hook runs on transactions; systemd timer runs on a schedule and requires systemd as your init. Unattended updates on a rolling release can pull in a broken package, so keep your backup kernel and snapshots.
Root SSH Access
Leave at No. Root login over SSH is the single most attacked door on an internet-facing host; use a normal user with doas/sudo. If enabled, the generator sets PermitRootLogin prohibit-password so only keys work.
Post-Install Cleanup
Clears the pacman cache and build dependencies after installation. Saves a few gigabytes; the trade-off is that downgrading a package later means re-downloading it.
Dusky Automated Setup
A single yes/no. Yes installs Dusky by dusklinux: a pre-configured Hyprland desktop with dotfiles, theming and a full Wayland environment. Because Dusky brings its own answers, choosing Yes also sets your desktop and forces Wayland — Hyprland cannot run on Xorg — and hides those two questions rather than letting you contradict it.
Watch the setup video and keep the keybind cheatsheet to hand: a tiling WM is unusable until you know the shortcuts. Answer No if you want GNOME, KDE, DWM or a plain TTY, and pick those yourself.
Desktop Environment
None leaves a pure TTY. GNOME and KDE are full environments. DWM is a minimal tiling WM requiring Xorg. Dusky is a pre-configured Hyprland setup by dusklinux and forces Wayland.
Display Server
Wayland isolates clients from each other, so one window cannot read another's input or framebuffer — a real security improvement over Xorg, where any client can. Xorg is still needed for DWM and some older applications. Auto follows your desktop choice. See the full comparison.
Firewall Setup
UFW + Gufw is the recommended default-deny setup with a GUI. Firewalld suits zone-based configurations. iptables is for people who want to write their own rules. None leaves every listening service exposed on untrusted networks.
DNS Caching
systemd-resolved is built in and supports DNS-over-TLS. unbound is a full validating recursive resolver. dnscrypt-proxy encrypts queries to upstream resolvers. dnsmasq and BIND suit local network serving.
OTP Enforcement Mode
Where Libre OTP is enforced: at login only, in the initramfs before the kernel hands over, or both.
OTP Hash Algorithm
SHA-1 is what essentially every authenticator app supports and is not a weakness in the HMAC construction TOTP uses. SHA-256 and SHA-512 are stronger but far fewer apps implement them.
Double OTP Verification
Requires two consecutive valid codes, meaning a 30-second wait between them, or codes from two separate authenticators. Meaningful extra assurance, meaningful extra friction.
OTP Bypass Uses
A limited-use password that skips OTP entirely. Every use is one fewer factor, so None is recommended.
OTP Recovery Codes
Single-use codes for when you lose your authenticator. Print them and store them offline — not on the machine they unlock. Choosing None means a lost authenticator locks you out permanently.
🔧 Hardware & Firmware Security
Everything the generator does happens after your firmware has already run. If someone can modify the firmware, none of it helps — they can capture your passphrase before the kernel exists. This section is about closing that gap, and none of it is something an install script can do for you.
Step 0: lock down the firmware you already have
Free, reversible, and by far the best value. Do this regardless of anything else:
- Set a firmware supervisor/admin password. Without one, an attacker just changes the boot order and boots their own media.
- Set a separate power-on password if your firmware supports it.
- Disable booting from USB, network (PXE) and optical media.
- Enable Secure Boot and enrol your own keys — the generator's UKI + Custom Keys option does the key side. See Arch Wiki: Secure Boot.
- Clear the factory Microsoft keys once your own are enrolled and you have confirmed the machine boots. Keep recovery media to hand.
- Disable Intel ME / AMD PSP features where your firmware exposes the option.
Caveat worth knowing: a supervisor password is stored in firmware, and on many consumer boards it can be cleared by shorting a jumper or pulling the CMOS battery. It stops casual tampering, not a determined attacker with a screwdriver — which is the gap the rest of this section addresses.
🔌 BusKill — recommended, cheap, reversible
BusKill is a magnetically-separating USB cable: one end plugs into your laptop, the other clips to your belt. Walk away — or have the laptop taken — and the magnet releases, which registers as a USB removal event and triggers your chosen response.
Why it is the first thing to buy: it is inexpensive, needs no firmware modification, works on any machine, and is completely reversible. It also addresses the scenario that FDE genuinely does not: the machine being seized while it is already unlocked, when your keys are sitting in RAM.
Pair it with the generator's USB kill switch set to removal as the trigger. Start with lock-session and only move to shutdown once you trust the setup:
# BusKill registers as an ordinary USB device, so the same allowlist applies.
lsusb # identify the cable
echo '1d6b:0104' >> /etc/arch-security/usb-allowlist # example ID — use yours
udevadm control --reload-rules
⚠️ Test it thoroughly with the lock action before arming a shutdown. A cable that disconnects when you shift in your chair will lose your work.
Measured boot: what coreboot, Heads and NitroPad actually do
Secure Boot checks a signature — it asks whether the thing about to run was signed by a trusted key. Measured boot instead hashes each stage into a TPM before running it. The TPM will then only release a stored secret if those hashes match what it saw last time. The practical difference: measured boot can prove to you that the firmware has not changed since you last looked, which is precisely the evil-maid problem.
How a NitroPad demonstrates it
A NitroPad is a ThinkPad shipped with coreboot and Heads, paired with a Nitrokey. At every boot, Heads measures the firmware and boot files into the TPM, and asks the TPM to sign a challenge. The Nitrokey blinks green if the measurements are unchanged and red if anything was modified. You are told about tampering before you type your passphrase — the one thing a purely software defence cannot do.
It is also the honest shortcut: you get measured boot without flashing anything yourself, and without the risk of bricking a machine.
coreboot vs libreboot — DIY route
Both replace your proprietary firmware. They are not the same thing:
- coreboot is the upstream project. It is a firmware framework that still permits proprietary blobs where hardware requires them — notably Intel FSP for memory initialisation, and the ME (which can be neutered but not fully removed on most Intel platforms). Broad hardware support, and the base that Heads builds on.
- libreboot is a coreboot distribution with a stricter policy: no proprietary blobs at all. That means it only supports hardware that can boot without them, which in practice is older machines — the very ones that lack a usable TPM. So libreboot gives you maximum software freedom, but generally not measured boot.
Heads is the payload that adds the security story on top of coreboot: it measures the boot chain into the TPM and can attest to a USB security key.
ThinkPad notes
ThinkPads are the usual choice here: well-documented, repairable, and the best supported family in both projects. The X230, T430, T440p, X220 and W541 come up most often. Support varies by exact model and even by board revision, so check the hardware list for your machine before buying anything.
This guide does not cover the flashing procedure, deliberately. Reflashing firmware on a ThinkPad usually means disassembling the machine and attaching a clip to the SPI chip. Getting it wrong leaves you with a laptop that will not power on, and recovery needs an external programmer. It is also model-specific in ways a general guide cannot safely cover. Follow the upstream documentation for your exact board — the coreboot docs, libreboot install guide and Heads documentation — or buy a machine with it pre-installed.
Where this leaves the generator
The generator's Anti-Evil Maid is the software approximation: it hashes your boot files and tells you if they changed. That is genuinely useful and costs nothing — but it runs after the firmware, so it cannot detect firmware-level tampering. Measured boot is what closes that gap, and it needs hardware. Both are worth having; neither replaces the other.
🔎 Security Audit of These Tools
A tool you are asked to trust with your boot chain should be able to show its own review. docs/security-audit.md is a source-level audit of all five crates, including what it found wrong.
What it found, and fixed
- The integrity check failed open. Deleting one file in
/etcmade the evil-maid check report success and boot normally — easier for an attacker than modifying/boot, so the defence could be removed by a weaker attacker than it was built to stop. It now fails closed. - The baseline hash was not deterministic. Directory traversal order is not stable, and only file contents were hashed — not paths. That produced false "do not enter your passphrase" alerts, which is worse than none, because an alarm that cries wolf gets ignored when it is finally right.
- The master-password hash was world-readable. Written with the default umask, so any local user could copy it and brute-force offline — defeating the point of a memory-hard KDF. Now
0600, set at creation. - Lockout destroyed your OTP secret. It overwrote
secret.jsonand your recovery codes with a sentinel string, so a lockout — possibly from a false positive — permanently cost you your OTP enrolment. It now writes a separate flag and leaves your secret alone. - Argon2 was at the bare minimum. Raised to m=64 MiB, t=3, p=4; old hashes still verify.
What it has not covered
Stated plainly, because a partial audit presented as complete is its own
problem: nothing was run, only read. anti-ducky's
keystroke-timing thresholds have not been measured on real hardware, so its
false-positive rate is unknown — worth knowing before you let it guard the
keyboard you log in with. Scarecrow's kernel module and the eBPF paths in
Kernel Watcher have not been reviewed. Dependency scanning has now been added
to CI.
Found something? Say so
An audit is a snapshot by one set of eyes, and the paragraph above is explicit about what it did not cover. If you find a real vulnerability, a command that does not do what the guide claims, a package that no longer exists, or documentation describing behaviour the code does not have, please open an issue. Disagreements count too — where this project and the Arch Wiki conflict, the Arch Wiki is right and this is a bug.
For anything you believe is exploitable, say so in the title so it is triaged first. There is no separate private channel: this is a personal project, not an organisation with a security team, and implying otherwise would be worse than saying so plainly.
Build and verify them yourself: building from source includes the exact commands to reproduce a published hash bit-for-bit.
📦 Installing the Suite in One Step
scripts/install-security-suite.sh installs all five tools at once,
so you do not have to fetch and verify each one by hand.
curl -fsSLO https://raw.githubusercontent.com/tilas01/arch-guides-dynamic/main/scripts/install-security-suite.sh
# Read it first. It runs as root; you should know what it does.
less install-security-suite.sh
sudo bash install-security-suite.sh --all
What it does, in order
- Imports the
tilas01.ascsigning key and prints its fingerprint for you to check. - Downloads each selected binary plus its
.sha512and.sig. - Verifies the hash, then the GPG signature, for every tool.
- Only once all of them pass does it install anything.
- Writes a hardened systemd unit per daemon and leaves it stopped.
- Prints the specific setup command each tool still needs.
Why it behaves the way it does
- Fails closed. A bad hash or signature aborts the run. There is no
--skip-verify, deliberately — a security tool you cannot verify is worse than none, because it gives false confidence. - Verifies everything before installing anything. Binaries are staged in a temporary directory first, so a failure on the fifth tool cannot leave you with four installed and one missing.
- Enables nothing by default. Several of these can lock you out — Input Guard can reject a keyboard, OTP can block login. They install stopped, and you enable them once you have read the configuration.
- Units are hardened. Each service gets
NoNewPrivileges,ProtectSystem=strict,ProtectHome=read-only,MemoryDenyWriteExecuteand a narrowReadWritePaths. A security daemon should not be the weakest thing on the box. - Idempotent. Re-running upgrades in place.
Options
--all # every tool
--only libre-otp,scarecrow # just these
--enable # also start the daemons now (understand them first)
--from-source # build with cargo from this repo instead of downloading
--dry-run # print what would happen, change nothing
--uninstall # remove binaries and units
pam_exec lines from /etc/pam.d/system-auth
before rebooting, or you can be locked out. Config in
/etc/arch-security is left alone too, because it may hold OTP secrets
and duress hashes you did not mean to destroy.
Prefer to do it by hand?
Nothing here is magic. The generator's markdown output contains the equivalent per-tool commands, and Verifying Downloads covers doing the hash and signature checks yourself.
📋 Cheatsheets
Quick references, kept outside the generator so you can reach them any time.
- Arch command cheatsheet — pacman, systemd, LUKS, BTRFS/Snapper, networking, journald and recovery.
- Dusky / Hyprland keybinds — every shortcut in the Dusky desktop, if you chose it in the generator.
- Xorg vs Wayland — what actually differs, and which you need.
- Maintenance — keeping an Arch install healthy over time.
- Architecture — how the generator and the tools fit together.
The generator also drops the cheatsheets into ~/cheatsheets on the
installed system, so they are available offline on the machine itself.
🐧 100% Libre Software Policy
Enabling Enforce 100% Libre Software Policy in the generator highlights any proprietary selection in red and writes an explicit conflict notice into the generated guide, so you cannot end up believing a system is fully libre when it is not.
What counts as non-libre here
- Firefox — ships proprietary firmware blobs and closed telemetry components. LibreWolf is the hardened libre fork.
- Chromium — bundles proprietary codecs and Google service integrations.
- Signal Desktop — an Electron build distributing pre-compiled binaries you cannot reproduce.
- Flatpak — libre itself, but the default Flathub remote distributes proprietary applications.
- NVIDIA drivers — heavily proprietary. With Libre selected the generator uses
nouveauinstead, which is markedly slower. - Discord, Steam, Spotify — closed-source clients, some with DRM.
A practical note: microcode updates (amd-ucode, intel-ucode)
are non-libre binary blobs, and they also carry the CPU vulnerability mitigations.
Excluding them on freedom grounds means accepting known silicon-level
vulnerabilities. That is a real trade-off, and worth making deliberately.
🔒 Verifying Downloads (GPG & Hashes)
Every binary published by this project is SHA-512 hashed and, when the CI signing key is configured, GPG-signed. Verify before running anything.
# 1. Import the public key (committed at the repository root)
gpg --import tilas01.asc
# 2. Verify the detached signature against the binary
gpg --verify libre-otp.sig libre-otp
# 3. Verify the hash
sha512sum -c libre-otp.sha512
A Good signature from "tilas01" plus an OK from
sha512sum means the binary is what was built. If either check
fails, do not run the file.
🧠 Memory Integrity Checker
If an advanced rootkit attempts to patch syscall tables in live memory or hide malicious kernel modules (Direct Kernel Object Manipulation - DKOM), the integrity checker will detect the checksum mismatch and alert you instantly.
🔍 Build Integrity & Reproducible Builds
In the Generator, every post-install application displays an information tooltip with its Build Integrity. Here is what those terms mean for your security and trust model:
- 100% Reproducible: The application's build process is perfectly deterministic. If you compile the exact same source code twice, the resulting binary will be bit-for-bit identical. This guarantees that the binary distributed in the repositories has not been tampered with or injected with a backdoor by the build server. You do not have to blindly trust the distributor; you can mathematically verify it.
- Mostly Reproducible: (Still highly verified and safe) The application is fully open-source and the code can be audited. However, the build system (like GitHub Actions or the Arch Build System) embeds non-deterministic metadata during compilation, such as build timestamps, randomized IDs, or system file paths. This means the binaries won't be perfectly identical bit-for-bit, but the actual compiled code logic is safe and verifiable.
- Non-Reproducible: (Requires trusting the developer) The application uses an opaque build process or relies on precompiled blobs (common with Electron apps like Signal). This does not mean the software is malicious, but it means you cannot independently verify that the resulting binary exactly matches the public source code. You must trust that the developer, or the automated CI system they authorized, compiled it safely without modifications.
- Proprietary / Closed Source: The application contains closed-source, proprietary code (such as Firefox's DRM firmware blobs, Chromium's Google integration, or Flatpak's proprietary catalog). The source code is not fully available for public auditing, so you are entirely reliant on trusting the corporate entity or developer behind it.
🦀 Security Suite & Live Releases
All tools can be compiled directly via Cargo or downloaded from the GitHub releases page below. Ensure you have the Rust toolchain installed (pacman -S rust) for compiling.
Release binaries are built deterministically in GitHub Actions and published with SHA-256/512 hashes. When the CI signing key is configured they are also GPG-signed; if it is not, hashes are published alone rather than placeholder signature files. See Verifying Downloads.
🔐 Libre OTP
Native Rust TOTP/HOTP two-factor authentication. Configurable SHA-1/256/512 (SHA-256 is the default and the strongest option here), single-use recovery codes, a limited-use bypass password, and an escalating lockout after repeated failures. Configure it via the OTP options in the generator.
The prompt shows a code and asks for one. That is deliberate:
an ordinary 2FA prompt only proves you hold the secret, so a convincing
fake prompt harvests a code and you learn nothing. If the number on screen does
not match your authenticator, the thing asking does not hold the secret and you
should not answer it. --double-check additionally requires two
consecutive codes, so a single shoulder-surfed code is not enough.
This page previously said Libre OTP was “enforced through PAM”.
It is not, and never was — the crate builds a binary, not a
cdylib, so pam_libre_otp.so does not exist. Adding
a line for it to /etc/pam.d/sshd references a missing module
and can lock you out of the machine. If you did that on this page's advice,
remove that line.
SSH is covered instead by libre-otp --gate as sshd's
ForceCommand: the key authenticates first, then the code gates
the session. It applies to the session, not to authentication, so
pair it with AllowTcpForwarding no and
PermitTunnel no — ssh -N runs no command and would
otherwise slip past it.
Use an offline, open-source authenticator — Aegis or FreeOTP. One that syncs seeds to a vendor's cloud makes the second factor only as strong as that vendor, which is most of what it exists to remove.
🦆 Input Guard (Anti-Ducky)
Monitors USB HID keystroke timing and sandboxes unknown input devices, blocking Rubber Ducky / BadUSB style automated keystroke injection before the payload can type. Requires approval from already-trusted inputs before a new keyboard is authorised. For a policy-based alternative at the kernel level, see USBGuard; for a hard power-off response, see the USB kill switch.
🕵️ Anti-Evil Maid
Verifies boot integrity by hashing the boot chain and comparing it across boots, and generates generic decoy kernel entries so the real encrypted target is not obvious. It also provides LUKS auto-lock: --lock-now suspends the volume and flushes the master key from RAM, which a screen lock does not do. Note that with GRUB plus LUKS, /boot is unencrypted on disk — a UKI bootloader removes that attack surface entirely.
Decoy and duress passwords are Scarecrow's job, not this tool's.
This page previously credited them here, and the generator emitted
anti-evil-maid --decoy-password and --duress-password to match.
Neither flag has ever existed in the crate, so every one of those lines failed — and they
put the password on the command line, where ps shows it to every user on the
machine. See LUKS duress passphrase for the three PINs that are
real, each prompted for interactively and never passed as an argument.
👁️ Kernel Watcher
Asynchronous filesystem monitor that flags infostealers touching browser profiles, SSH keys and wallet files, and detects userland rootkit behaviour. A lightweight semi-EDR that runs as a systemd daemon and logs to /var/log.
🦅 Scarecrow
Plants canary tokens and spoofs sandbox/VM artefacts, so malware that checks whether it is being analysed decides to stay dormant. Includes an optional kernel module. Detection-oriented rather than preventive: it tells you something looked, and discourages execution.
It also provides three optional duress PINs for the login prompt — see LUKS duress passphrase. Each is set separately and none announces itself:
| PIN | What entering it does |
|---|---|
--set-duress-pin | Erases the LUKS header, then behaves exactly like a wrong password. |
--set-decoy-pin | Opens a believable working session in a decoy home. Erases nothing. |
--set-duress-decoy-pin | Erases the header and opens the decoy, so neither is visible. |
Nothing is erased until --set-duress-device names a device, and
you should take a luksHeaderBackup first and keep it somewhere the
person you are hiding from cannot reach. All three are verified on every
attempt with no early exit, so the time taken does not reveal which one
matched.
📦 Live GitHub Statistics & Releases
Fetching live releases from GitHub...
Where the suite keeps its state
Every tool stores its configuration and baselines under
/etc/arch-security/<tool>/ — kernel-watcher/tamper.hash,
kernel-watcher/evil_maid.hash, anti-evil-maid/boot.hash,
anti-ducky/approved_devices.json and so on. That is the directory
the installer creates and the only path inside /etc the daemons are
allowed to write to, because their systemd units run with
ProtectSystem=strict. The one exception is
Libre OTP, which still keeps its secret in
/etc/libre-otp/.
Versions before 1.0 used /etc/arch-rusty-security-suite/ (and
/etc/anti-ducky/). Those are still read if the new file is
absent, so an existing install keeps verifying after an upgrade instead of
reporting a missing baseline as tampering. Nothing writes there any more:
re-run the tool's --setup and it will tell you which stale copy to
delete. Note that anti-ducky device approvals made before 1.0 were
being lost on restart — its registry was outside the writable path.
Compile via Cargo
Full Security Suite
cargo install arch-rusty-security-suite --git https://github.com/tilas01/arch-guides-dynamic
Installs the unified binary containing all modules.
Standalone Modules / Static Explanation
- Libre OTP: Double OTP verifier. Requires two consecutive valid OTP codes (must wait 30s) or two different authenticators for extreme security.
- Anti-Ducky: USB Blocker. Dynamically detects and blocks rogue HID devices (Rubber Duckies) attempting payload injection.
- Anti-Evil-Maid: Boot Verification. Checks TPM and Secure Boot state against known-good hashes to prevent offline tampering.
- Kernel Watcher: Ring 0 Monitor. Scans for illicit kernel module loads and unexpected sysfs parameter modifications.
- Scarecrow: Canary Tokens. Generates and monitors fake credentials/files to detect network breaches immediately.
🧭 Choose Your Own Path
The options below are not a checklist to complete — several of them can destroy your data or lock you out permanently. Start from the threat you actually have and follow that branch only.
LUKS2 + Argon2id · UKI with your own Secure Boot keys · BTRFS with Snapper · UFW default-deny · AppArmor · Libre OTP at login. Nothing here can erase your disk. This is the recommended baseline.
Add Anti-Evil Maid for boot-integrity hashing, Input Guard against keystroke-injection USB devices, and the USB kill switch set to “lock session”. Still non-destructive — you can always get back in.
LUKS duress passphrase with keyslot erasure, and/or USB kill switch set to shutdown. These permanently destroy data with no confirmation prompt. Do not enable either until you have verified offline backups and have rehearsed the whole flow in a virtual machine. Understand the legal position in your jurisdiction before relying on them.
🔴 LUKS Duress Passphrase
A duress passphrase is a second valid passphrase on the LUKS header. Your real passphrase keeps working unchanged; entering the duress one at the boot prompt runs a chosen response instead of unlocking normally.
How the generator implements it
- The passphrase is registered as an additional LUKS keyslot with
cryptsetup luksAddKey. - Only a salted SHA-512 hash is written to
/etc/arch-security/duress.conf(mode 600). The passphrase itself is never stored. - A small initramfs hook runs before
encrypt/sd-encrypt, compares what you typed against the hash, and either hands the passphrase on to the normal unlock path or triggers the response.
The four actions
- Shut down immediately — powers off, changes nothing on disk. The safe choice, and the one to start with. An observer sees a machine that turned itself off.
- Erase LUKS keyslots, then shut down — runs
cryptsetup eraseand overwrites the header region. Every byte becomes permanently unrecoverable. - Erase keyslots, then boot a decoy — as above, then unlocks a separate decoy volume so the machine appears to boot normally.
- Boot a decoy only — unlocks the decoy and leaves the real volume completely intact. Non-destructive, and reversible.
- There is no undo, no confirmation prompt at boot, and no forensic recovery.
- Typing the duress passphrase by mistake destroys the installation.
- Choose a duress passphrase you could not confuse with the real one.
- Keep verified offline backups and rehearse the entire flow in a VM.
- Destroying evidence may itself be an offence where you live. That is your call to make, not this tool's.
Two different mechanisms — know which one you have
There are two duress implementations in this project, at two different layers, and they are not interchangeable:
- The initramfs hook described above fires at the boot passphrase prompt, before the root filesystem is mounted. It is what the four actions above configure.
- Scarecrow's three PINs fire at the login prompt,
after the disk is already unlocked. They are what
scarecrow --set-duress-pinand its siblings configure.
Believing that is not a harmless mix-up: you would type your duress PIN at the boot passphrase prompt, where it is simply a wrong passphrase, nothing would happen, and you would find that out at the one moment it needed to work. If you want a response before the disk is unlocked, use the initramfs hook.
Wiring scarecrow's PINs into the login
Setting a PIN is not enough on its own — something has to check it. Scarecrow is
reached through stock pam_exec, so there is no custom PAM module involved
and it works anywhere PAM does: login, greetd, sddm, gdm, su.
# /etc/pam.d/system-auth — ABOVE the pam_unix line
auth [success=done default=ignore] pam_exec.so expose_authtok quiet /usr/bin/scarecrow --pam-gate
expose_authtok hands the entered password to scarecrow on stdin, so it is
typed exactly once and nothing on screen differs from an ordinary login. The exit code
does the rest:
| Password | Exit | What happens |
|---|---|---|
| Your normal one | 1 | default=ignore — falls through to pam_unix, works exactly as before |
| Duress PIN | 1 | LUKS header erased first, then an ordinary-looking failure |
| Decoy PIN | 0 | success=done — auth granted, session starts in the decoy home |
| Combined PIN | 0 | Header erased and the decoy session opens |
The gate fails closed. A read error, a missing hash file or an unparseable hash all exit non-zero, because a gate that returned success on a missing file would be an authentication bypass an attacker triggers by deleting something.
A mistake in /etc/pam.d/system-auth can lock every account out of the
machine, including root. Keep a second TTY logged in as root while you edit, and
test authentication from a third before closing either. Recovery otherwise
means booting the install medium.
Setting up the decoy volume
The decoy options expect a partition labelled decoy, LUKS-formatted
with the duress passphrase and containing a minimal install. Until that exists the
hook falls back to powering off rather than revealing anything. Create it with:
cryptsetup luksFormat --type luks2 /dev/disk/by-partlabel/decoy
cryptsetup open /dev/disk/by-partlabel/decoy decoyroot
mkfs.ext4 /dev/mapper/decoyroot
# then pacstrap a minimal system into it, with no reference to the real volume
Verify your real passphrase still works, and that the duress passphrase behaves as expected, in a virtual machine before trusting this on hardware.
🔒 LUKS Auto-Lock
Locking your screen does not lock your disk. The LUKS master key stays resident in kernel memory for as long as the volume is open, so a DMA-capable port, a cold-boot attack on the DIMMs, or a kernel bug recovers it while the lock screen is still up. The lock screen is a UI, not a cryptographic boundary.
cryptsetup luksSuspend is the primitive that actually locks the disk: it
wipes the master key from kernel memory and freezes I/O on the device until
luksResume supplies the passphrase again. After a suspend, the data is as
protected as it is when the machine is powered off.
Configuring it
sudo anti-evil-maid --configure-autolock --idle 15m
sudo systemctl enable --now anti-evil-maid-autolock.timer
# Lock on demand at any time
sudo anti-evil-maid --lock-now
--idle accepts the presets the walkthrough offers and a free-form value:
15m, 1h, 2d3h, 3d4h15m, or
never. A bare number is read as minutes. An interval of zero is rejected
rather than guessed at — it reads as "off" to a person and as "lock immediately, over
and over" to a timer, and the difference matters too much to assume.
--configure-autolock also writes
/usr/local/bin/anti-evil-maid-on-lock. Point your screen locker at it and
the key stops being resident the moment you lock the session.
Why suspending the root volume is delicate
Once a device is suspended, every read and write to it blocks forever. If the volume
backs /, that includes the cryptsetup binary you are about to
run and every shared library it links. A naive implementation suspends the root device
and then deadlocks trying to load the code that would resume it — the machine is not
locked, it is bricked until a hard power cycle.
So before suspending anything, the tool stages cryptsetup and its
libraries into /run (tmpfs, so RAM), verifies that directory really is
memory-backed rather than assuming it, and locks its own pages with
mlockall so the kernel cannot page the resume helper out to a swap device
that is also about to freeze. Every check that can fail happens
before the suspend, because after it a missing binary is not an error path,
it is data loss.
Test it once while you can still reach the machine physically.
The unlock delay is not phone-grade brute-force protection
The resume prompt allows four attempts — a deliberate concession to typos, because a passphrase long enough to resist offline attack is long enough to fat-finger — then imposes a delay that doubles from 30 seconds to a ceiling of one hour. The ceiling is there because an uncapped doubling locks the machine's owner out for days over someone else's failed guesses, and the owner is the person most likely to be typing.
That raises the cost of someone typing at your running machine's prompt. It is worth having and it is all it is. It is not equivalent to the brute-force resistance of a phone. GrapheneOS's escalating delays are enforced by a secure element (Titan M / Weaver) that rate-limits key derivation in hardware; bypassing the OS does not bypass the delay, because the OS is not the thing enforcing it.
A generic PC has no such component. An attacker who images the disk attacks the LUKS header offline, at whatever rate their hardware allows, and this delay is simply absent from that attack. What actually defends an imaged header is:
- The KDF cost. LUKS2 with Argon2id and a high memory cost is what
makes each offline guess expensive. Raising
--iter-timeraises the floor further. - A TPM-sealed keyslot with a TPM-enforced lockout
(
systemd-cryptenroll --tpm2-pin=yes) — the nearest thing to a secure element on a PC, because the counter lives where the OS cannot reach it. - A passphrase strong enough to survive offline Argon2id attack, which is worth more than either of the above.
Related: LUKS duress passphrase.
🔌 USB Kill Switch
Modelled on the usbkill
project. At install time the generator snapshots the USB devices currently
attached into an allowlist at /etc/arch-security/usb-allowlist, and
installs a udev rule that reacts when something outside that list appears.
Actions
- Lock the session — calls
loginctl lock-sessionsand sends a desktop notification. Non-destructive. Start here. - Shut down — forces an immediate power-off. Unsaved work is lost.
- Shut down and wipe RAM/swap — syncs, disables swap, drops caches, then powers off. Intended to frustrate cold-boot key recovery, at the cost of a less clean shutdown.
Triggers
- Connected — an unlisted device is plugged in. Guards against a malicious device being attached.
- Removed — an allowlisted device is unplugged. Guards against your machine being taken, if you keep a USB token attached.
- Either — both of the above.
- The shutdown actions fire with no confirmation. A keyboard, mouse, dock, phone or webcam that is not on the allowlist is enough.
- The allowlist reflects only what was attached during installation, so review it before arming a shutdown action.
- Test in a VM first, and prefer “lock session” until you trust your allowlist.
Managing it afterwards
# See the current allowlist (vendor:product IDs)
cat /etc/arch-security/usb-allowlist
# Find the ID of a device you want to trust, then add it
lsusb
echo '1234:5678' >> /etc/arch-security/usb-allowlist
udevadm control --reload-rules
# Review what has triggered
cat /var/log/usb-kill.log
# Disable the kill switch entirely
rm /etc/udev/rules.d/99-usb-kill.rules
udevadm control --reload-rules
For a less drastic alternative, USBGuard enforces the same allowlist idea by refusing to authorise the device, without touching power state.
🛡️ Third-Party Security Tools
Well-established tools from the Arch repositories, selectable in the generator and on the Security Tools page.
- AppArmor — path-based mandatory access control. Confines a program to a declared set of files and capabilities, so a compromised process cannot roam. The generator adds the required kernel parameters to your bootloader or UKI cmdline automatically.
- fail2ban — bans source addresses after repeated authentication failures. Ships with a default SSH jail: 3 attempts, 1 hour ban.
- UFW — readable front end to netfilter, configured default-deny inbound.
- Lynis — audits the installed system against hardening benchmarks and writes a report to
/var/log/lynis-initial.log. - USBGuard — device authorisation policy for USB. Generates an allowlist from what is attached and rejects anything else at the kernel level. A gentler alternative to the USB kill switch.
- auditd — kernel audit daemon, preconfigured to watch
/etc/passwdand/etc/sudoersfor changes.
🌐 Encrypted DNS
Every site you visit starts with a DNS lookup, and by default that lookup goes to whoever DHCP nominated — usually your ISP — as plaintext on port 53. HTTPS does not help: the encryption starts after the name has already been resolved. So your ISP, and anyone able to watch the connection, sees a list of every domain you ask for.
Both generators let you pick an upstream and turn on DNS-over-TLS, which fixes the plaintext part and moves the trust to a provider you chose.
The providers offered
| Provider | Addresses | Who they are |
|---|---|---|
| Quad9 | 9.9.9.9, 149.112.112.112 |
Swiss non-profit foundation. Blocks known-malicious domains at the resolver. States it does not log the querying IP. |
| Mullvad | 194.242.2.2 |
Run by the VPN provider, usable without a subscription. States no logging. |
| Cloudflare | 1.1.1.1, 1.0.0.1 |
Fast and very widely used. No-logging policy with published third-party audits. |
| dns0.eu | 193.110.81.0, 185.253.5.0 |
European non-profit, GDPR-scoped, states no logging. |
| AdGuard | 94.140.14.14, 94.140.15.15 |
Blocks advertising and tracking domains for every application on the machine, not just a browser. |
Why the #hostname matters
The generated config writes each address as 9.9.9.9#dns.quad9.net,
not just 9.9.9.9. That suffix is the name the TLS certificate must
match.
DNSOverTLS=yes on its own encrypts the query but does
not authenticate the server. Anyone who can answer on
port 853 — on a hostile network, that is the network — is then trusted.
Encryption without authentication is not much of a defence, and this is a
common way DoT setups are quietly useless.
FallbackDNS= is deliberately set empty. systemd's compiled-in
fallbacks are other providers, so leaving it at the default silently
leaks a share of your queries to whoever those happen to be — which defeats
the point of choosing one.
What this does not do
It moves who sees your lookups from your ISP to the provider you picked. Their no-logging policy is a published claim you cannot verify from your machine.
It also does nothing about the IP address you then connect to, or about SNI where Encrypted Client Hello is not in use — so an observer can often still infer which site you reached, just not from the DNS query. This is a real improvement over plaintext DNS. It is not anonymity, and anything claiming otherwise is selling something.
Arch Wiki: systemd-resolved · Domain name resolution · this project defers to both.
🛡️ UFW Firewall Profiles
The Generator allows you to configure Uncomplicated Firewall (UFW) dynamically during installation.
- Strict: Drops all inbound connections. Highly recommended for laptops and portable devices.
- Web Server: Allows only HTTP (80) and HTTPS (443). Drops all other inbound traffic.
- SSH Only: Allows port 22 (and 2222 if Endlessh is used). Recommended for remote headless servers.
You can always modify these rules post-installation using sudo ufw allow/deny <port>.
🚫 Fail2ban Intrusion Prevention
Fail2ban monitors system logs (like /var/log/auth.log) for automated attacks and brute-force attempts. When it detects multiple failed login attempts from a single IP address, it automatically updates firewall rules to ban that IP.
Our default configuration protects the SSH daemon by banning an IP for 1 hour after 3 failed password attempts.
⏳ Endlessh (SSH Tarpit)
Endlessh is an SSH tarpit that slowly sends an endless, random SSH banner to clients. This ties up automated SSH scanners and botnets for hours or even weeks, preventing them from attacking other hosts.
When you enable Endlessh in the generator, it binds to the default SSH port (22), and automatically moves your real SSH daemon to port 2222. You must connect to your server using ssh -p 2222 user@host.
🔑 Hardware Security Key (FIDO2 / U2F)
The generator supports integrating physical FIDO2/U2F tokens (like YubiKeys) directly into the Linux PAM stack via pam_u2f.
If you enroll a YubiKey, it will be required to log in, use SSH, or execute Sudo/Doas commands. If you lose your YubiKey and do not have a fallback, you will be permanently locked out of your system. It is highly recommended to enroll backup keys post-installation.
⚙️ Advanced App Configuration
This section details the advanced configuration choices available in the generator when selecting Ask all questions in generator now toggle enabled or when prompted via the interactive bash script.
100% Libre Software Policy
If you toggle the Enforce 100% Libre Software Policy in the generator, the interface will dynamically audit your selections. Any software containing proprietary or closed-source components (like Discord, Signal, or Flatpak) will be aggressively highlighted with a solid red border. This ensures you do not accidentally violate your own open-source purity requirements.
Script Verbosity Level
- Quiet: The script suppresses standard output (stdout) to keep your terminal clean, only printing critical errors.
- Normal: (Default) Standard terminal output for pacman, pacstrap, and configuration logs.
- Debug / Verbose: Injects
set -xat the top of the generated bash script. This forces bash to print every single command it executes, along with its arguments, directly to the terminal before running it. Crucial for troubleshooting installation failures.
System Usernames
Instead of relying on the script to prompt you interactively during the build, you can pre-define the exact system usernames you want created directly in the generator. This ensures the output markdown guide accurately reflects your target state.
JetBrains Terminal Themes
If you selected to install JetBrains Mono and Terminal Themes, you can pick your preferred color palette (TokyoNight, Catppuccin, Rosé Pine, Dracula) here. The script will automatically deploy the corresponding dotfiles for your shell.
AEM Decoy Count
Anti-Evil Maid secures your /boot partition by verifying decoy kernels. You can select how many decoy images to generate. 1 Decoy is standard. 3 Decoys is for maximum paranoia but increases boot time slightly as more checksums must be validated.
Doas Integration Mode
- Keep Sudo intact alongside Doas: Installs Doas but leaves
sudofunctioning normally. Applications that hardcode sudo will continue to use the standard sudo binary. - Fully replace Sudo with Doas wrapper: Removes sudo entirely and installs a custom Bash wrapper script at
/usr/local/bin/sudo. This wrapper intercepts standard sudo calls (likesudo -e,sudo -i) and translates them into Doas commands, ensuring seamless compatibility with legacy apps while maintaining the security benefits of Doas. - Remove Sudo entirely (no wrapper): The strictest option. Completely purges sudo from the system. Note that many standard Linux scripts or AUR helpers (like paru/yay) expect sudo by default and may need manual configuration to use doas.
Snapper Timeline Mode
- Pre/Post Transaction Snapshots Only: (Default) Only takes snapshots when using pacman to install/update/remove packages. Uses minimal disk space while keeping your system safe from bad updates.
- Enable Hourly/Daily Timeline: Enables
snapper-timeline.timerwhich takes automated hourly snapshots and cleans them up based on standard retention policies. Useful if you frequently modify system files manually and want point-in-time recovery outside of pacman transactions.
Secure Password Handling
Passwords are never stored in the UI or inside the generated scripts for your security. Regardless of the Configuration Mode you choose:
- Markdown Guide: Passwords are ALWAYS censored (displayed as
*******) because Markdown files are static text and should never contain plaintext secrets. - Bash Script: The script will always use secure, echoing-disabled
read -s -pprompts to ask you for passwords live in the terminal during execution.
Libre-OTP Configuration
Libre-OTP allows you to integrate Time-Based One-Time Passwords (TOTP) natively into Linux Pluggable Authentication Modules (PAM). This enforces 2FA physically onto your machine.
- OTP Mode: Choose which operations require an OTP code. Selecting
Boot/Login onlywill ask for an OTP immediately after LUKS decryption but before giving you a shell. SelectingAllwill prompt for OTP on everysudo,su, SSH login, and boot sequence. - Bypass Uses: Set the number of times you can bypass the OTP prompt using a fallback recovery code (e.g., in case you lose your phone). Maximum allowed is 10.
🧩 CPU Architecture: x86_64 or aarch64
This is the first question the generator asks, because almost everything downstream depends on it. Getting it wrong does not produce a subtly worse install — it produces a script that cannot work at all.
Which one am I on?
uname -m
# x86_64 → Intel or AMD desktop/laptop. Use plain Arch Linux.
# aarch64 → 64-bit ARM. Raspberry Pi 3/4/5, Pinebook, Apple Silicon under a VM,
# most ARM servers. Use Arch Linux ARM, which is a different project.
# armv7l → 32-bit ARM. Arch Linux ARM still supports some boards; the
# generator does not target it.
x86_64
The path everything else in this wiki assumes. UEFI firmware, an EFI
system partition, a bootloader you choose
(UKI, systemd-boot or GRUB), CPU microcode
from intel-ucode or amd-ucode, and Secure
Boot available if you
enrol your own keys.
aarch64 — what actually changes
ARM is not "x86 with a different compiler flag". These differences are structural, and the generated guide branches on all of them:
| Concern | x86_64 | aarch64 |
|---|---|---|
| Installation media | One ISO you write to a stick and boot | A per-board rootfs tarball you extract onto a prepared card or disk. There is no single Arch ARM ISO, so the ISO verifier's hashing step applies to the tarball instead. |
| Firmware | UEFI, and a firmware setup screen you can lock down | Board-specific: U-Boot, EDK2, Raspberry Pi's
start.elf in an EEPROM, or a vendor blob. Some
boards do expose UEFI; most do not. |
| Boot configuration | EFI executables and a bootloader menu | A device tree (.dtb) plus extlinux.conf
or boot.scr. The kernel needs a device tree
describing hardware the firmware does not enumerate. |
| Microcode | intel-ucode / amd-ucode |
Not applicable. There is no equivalent early-load microcode package; firmware updates come from the board vendor. |
| Secure Boot | Available; enrol your own keys | Only where the board's firmware implements UEFI Secure Boot. On a Raspberry Pi it does not exist — the equivalent is signed-boot in the EEPROM, and it is not the same guarantee. |
| Package repository | archlinux.org mirrors |
archlinuxarm.org mirrors. Different project, different maintainers, different signing keys, and it lags upstream Arch. |
rpi-eeprom-update -a), then check what you are running
with vcgencmd bootloader_version. On boards with a
write-protect jumper or an EEPROM lock bit, set it once you are
happy — an attacker with brief physical access can reflash an
unprotected EEPROM, and none of the disk encryption below survives
that.
The security tools build for aarch64. The parts that touch
evdev and udev use the same kernel
interfaces on ARM, so
Input Guard and the rest work — but
Anti-Evil Maid's
/boot hashing covers whatever your board actually boots
from, which on ARM is often a FAT partition holding firmware blobs
rather than a kernel and initramfs.
🔐 Firmware Setup and Lockdown
Getting into firmware setup
Tap the key during POST, before any operating system loads. Common ones: Del and F2 on desktops, F1/F2/F10/F12/Esc on laptops. If the machine boots too fast to catch it, from an existing Linux install:
systemctl reboot --firmware-setup
From Windows: Settings → System → Recovery → Advanced startup → Restart now → Troubleshoot → Advanced options → UEFI Firmware Settings.
The recommended settings, and why each one
"Highest security" is the right default here. Each of these closes a path an attacker with physical access would otherwise take, and the cost to you is a few seconds at boot.
| Setting | Set it to | Why |
|---|---|---|
| Supervisor / Administrator password | Set one. Long, and not the same as your disk passphrase. | Without it, anyone who opens the lid can re-enable USB booting, disable Secure Boot, and boot their own media — every other setting on this page is one unlocked menu away from being undone. This is the single most important item here. |
| Secure Boot | Enabled, in Setup Mode long enough to enrol your own keys, then back to User Mode. | Stops the firmware from executing an unsigned bootloader. Using your keys rather than Microsoft's means you, not a third-party CA, decide what may boot. Turning Secure Boot off because it is inconvenient also lowers the bar for any other OS on the machine. |
| Boot order & external boot | Internal disk first. Disable USB, network (PXE) and optical boot. | An attacker who can boot their own USB has read access to
every unencrypted byte and write access to /boot.
This is the actual mechanism behind most "evil maid"
scenarios. |
| Boot menu (F12) override | Require the supervisor password. | Otherwise the boot-order setting above is decorative — the one-time boot menu walks straight past it. |
| TPM / fTPM / PTT | Enabled. | Needed for measured boot and for sealing a LUKS key to the machine's state. Also what makes a firmware change detectable rather than merely possible. |
| Intel ME / AMD PSP | Disable or restrict if the option exists. Most consumer firmware will not let you. | A processor with its own network stack running below your OS. If you genuinely need this gone, that is a coreboot conversation, not a BIOS-menu one. |
| Thunderbolt / DMA security | "User authorisation" or higher. Never "no security". | Thunderbolt and ExpressCard grant direct memory access. A malicious device can read RAM — including your disk encryption key — without the OS being involved. Kernel DMA protection helps only if the firmware turns it on. |
| Wake-on-LAN, wake-on-USB | Disable unless you use them. | Fewer ways to bring a locked machine out of a state where its keys are not in memory. |
| Firmware version | Update it, from the vendor, before locking anything. | Firmware updates fix privilege-escalation bugs that sit below every mitigation your OS can apply. Do this first, because some updates reset the settings above. |
Check it actually took effect
# Are we on UEFI at all?
ls /sys/firmware/efi && echo "UEFI" || echo "Legacy BIOS"
# Is Secure Boot enforcing?
bootctl status | grep -i "secure boot"
# Is the TPM present and which version?
ls /sys/class/tpm/
cat /sys/class/tpm/tpm0/tpm_version_major
# What did the firmware measure into the TPM?
sudo tpm2_pcrread sha256
What firmware lockdown does not do
It raises the cost of an attack that needs the machine in front of it. It does not stop an attacker who can desolder the SPI flash chip and reprogram it, and on most consumer hardware a supervisor password is enforced by firmware that the same attacker can replace. If your threat model includes someone willing to open the case, the honest answer is measured boot with coreboot and Heads, not a BIOS menu. Everything above is still worth doing — it is just worth knowing where the line is.
🪟 Dual Booting with Windows
Dual booting is the single most common way people lose data during an Arch install, and almost always for one of five reasons. Each has a preventable cause.
Install Windows first
If you are starting from empty disks, install Windows before Arch. The Windows installer overwrites the EFI boot order and will happily remove a Linux entry it does not recognise. Arch does not do this to Windows.
Do not reformat the EFI system partition
Windows and Arch share one ESP quite happily. What breaks it is
formatting the existing one — that deletes
\EFI\Microsoft\Boot\bootmgfw.efi and with it the Windows
boot entry.
# Identify it: type EF00, usually 100-300 MiB, already FAT32
lsblk -f
sudo fdisk -l /dev/nvme0n1
# MOUNT it, do not mkfs it
mount /dev/nvme0n1p1 /mnt/boot # correct
# mkfs.fat -F32 /dev/nvme0n1p1 # this deletes Windows' bootloader
If the existing ESP is only 100 MiB you may not fit a second kernel and initramfs alongside Windows. Either use systemd-boot with a small kernel set, or create a second ESP — the firmware can hold entries for both.
Turn off Fast Startup before you touch NTFS
Windows Fast Startup is hibernation wearing a shutdown costume. The NTFS filesystem is left in a dirty, in-use state. Resizing it or mounting it read-write from Linux in that state corrupts it.
In Windows, as administrator:
powercfg /h off
Then Control Panel → Power Options → Choose what the power buttons do → Change settings that are currently unavailable → untick Turn on fast startup. Do a full Restart, not a Shut down, before booting the installer.
Suspend BitLocker first
BitLocker seals its key to TPM measurements that include the boot configuration. Installing a second bootloader changes those measurements, and the next Windows boot demands a 48-digit recovery key. Before installing:
manage-bde -protectors -disable C: -RebootCount 2
Print or write down your BitLocker recovery key first, and keep it somewhere that is not the machine you are about to repartition. Get this wrong and the Windows data is gone — that is BitLocker working correctly.
The clock will disagree
Linux keeps the hardware clock in UTC; Windows expects local time. Fix it on the Linux side, which is the standards-compliant one:
timedatectl set-local-rtc 0
sudo hwclock --systohc --utc
Or, if you would rather change Windows, set
HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation\RealTimeIsUniversal
to DWORD 1.
Encryption alongside Windows
LUKS on the Arch partitions and BitLocker
on the Windows one coexist without trouble — they are separate
partitions with separate keys. What does not work is expecting
either one to protect the other: an attacker who boots Windows is not
slowed down by your LUKS volume, and a compromised Windows install on
the same machine can read your ESP, which is where your kernel lives.
If you are dual booting, treat /boot as shared ground
and make boot-integrity checking part
of the setup rather than an optional extra.
Longer walkthrough with partition tables: docs/06-dual-boot. Arch Wiki: Dual boot with Windows.
⌨️ Manual Install — Choose Your Own Path
The generator writes this script for you. This section is the same install done by hand, with the decision points called out, so you can do it without the generator or check what the generator produced. The Arch Wiki installation guide is the authority; where this page and the Arch Wiki disagree, the Arch Wiki is right.
/dev/sdX and /dev/nvme0n1 are
placeholders. Run lsblk, identify your disk by size and
model, and substitute deliberately.
0 · Before you boot the installer
- Verify the ISO — hash it, and take the checksum from a host other than the one that served the image.
- Configure the firmware — update it, set a supervisor password, disable external boot.
- If dual booting: Fast Startup off, BitLocker suspended, recovery key written down.
- Back up anything you cannot lose. Not to the disk you are about to partition.
1 · Boot and get a network
loadkeys uk # your layout; skip for US
ls /sys/firmware/efi && echo UEFI # confirms firmware mode
# Wired: usually already up. Wireless:
iwctl
[iwd]# device list
[iwd]# station wlan0 scan
[iwd]# station wlan0 get-networks
[iwd]# station wlan0 connect YOUR_SSID
[iwd]# exit
ping -c3 archlinux.org
timedatectl set-ntp true
2 · Decide: encrypted or not
LUKS2 with Argon2id. Costs you a passphrase at every boot. Means a stolen laptop is a stolen laptop, not a data breach. Continue at step 3 and take the encrypted branch.
Reasonable only for a desktop that never leaves a room you
control, or a machine holding nothing. Anyone who picks the
machine up reads everything. Skip the cryptsetup
lines below.
3 · Partition
UEFI + GPT, which is what you want unless the firmware gives you no choice:
lsblk # identify the disk. Twice.
gdisk /dev/nvme0n1
# n → partition 1 → +1G → type ef00 (EFI system partition)
# n → partition 2 → rest → type 8300 (Linux filesystem)
# w → write
# 1 GiB for the ESP, not the 512 MiB you will see elsewhere: a UKI bundles
# kernel and initramfs into one EFI file, and two of those plus a fallback
# does not fit in 512 MiB.
Encrypted branch
cryptsetup luksFormat --type luks2 --pbkdf argon2id /dev/nvme0n1p2
cryptsetup open /dev/nvme0n1p2 cryptroot
mkfs.fat -F32 /dev/nvme0n1p1
mkfs.btrfs /dev/mapper/cryptroot # or mkfs.ext4
Unencrypted branch
mkfs.fat -F32 /dev/nvme0n1p1
mkfs.btrfs /dev/nvme0n1p2
4 · Decide: Btrfs or ext4
| Btrfs | ext4 | |
|---|---|---|
| Snapshots | Yes — a bad update is one rollback away | No |
| Complexity | Subvolumes to lay out, and its own tooling | Format and forget |
| Recovery | Excellent when you have snapshots | Very mature, very well understood |
| Pick it if | You want to undo a bad upgrade | You want the fewest moving parts |
Btrfs subvolume layout that makes snapshots useful:
mount /dev/mapper/cryptroot /mnt
btrfs subvolume create /mnt/@
btrfs subvolume create /mnt/@home
btrfs subvolume create /mnt/@log
btrfs subvolume create /mnt/@pkg
btrfs subvolume create /mnt/@snapshots
umount /mnt
O="noatime,compress=zstd:3,ssd,space_cache=v2"
mount -o $O,subvol=@ /dev/mapper/cryptroot /mnt
mkdir -p /mnt/{home,var/log,var/cache/pacman/pkg,.snapshots,boot}
mount -o $O,subvol=@home /dev/mapper/cryptroot /mnt/home
mount -o $O,subvol=@log /dev/mapper/cryptroot /mnt/var/log
mount -o $O,subvol=@pkg /dev/mapper/cryptroot /mnt/var/cache/pacman/pkg
mount -o $O,subvol=@snapshots /dev/mapper/cryptroot /mnt/.snapshots
mount /dev/nvme0n1p1 /mnt/boot
# @log and @pkg are separate so a snapshot rollback does not also roll back
# your logs (you want those to explain what went wrong) or re-download the
# entire package cache.
5 · Install the base system
# Choose your kernel here. linux-hardened trades some performance and some
# out-of-tree driver compatibility for exploit mitigations; linux-lts trades
# newest hardware support for stability. Installing two is cheap insurance.
pacstrap -K /mnt base linux linux-firmware linux-lts \
btrfs-progs cryptsetup \
networkmanager sudo vim man-db man-pages texinfo
# x86_64 only — microcode. Skip entirely on aarch64.
pacstrap /mnt intel-ucode # Intel
pacstrap /mnt amd-ucode # AMD
genfstab -U /mnt >> /mnt/etc/fstab
cat /mnt/etc/fstab # read it before you trust it
arch-chroot /mnt
6 · Configure inside the chroot
ln -sf /usr/share/zoneinfo/Region/City /etc/localtime
hwclock --systohc
sed -i 's/^#en_GB.UTF-8/en_GB.UTF-8/' /etc/locale.gen
locale-gen
echo "LANG=en_GB.UTF-8" > /etc/locale.conf
echo "KEYMAP=uk" > /etc/vconsole.conf
echo "myhost" > /etc/hostname
cat >> /etc/hosts <<'EOF'
127.0.0.1 localhost
::1 localhost
127.0.1.1 myhost.localdomain myhost
EOF
passwd # root
useradd -m -G wheel -s /bin/bash you
passwd you
EDITOR=vim visudo # uncomment %wheel ALL=(ALL:ALL) ALL
systemctl enable NetworkManager
Encrypted: the initramfs needs to know
vim /etc/mkinitcpio.conf
# HOOKS=(base systemd autodetect microcode modconf kms keyboard sd-vconsole \
# block sd-encrypt filesystems fsck)
#
# sd-encrypt must come BEFORE filesystems, and keyboard before sd-encrypt —
# otherwise you get a passphrase prompt you cannot type into.
mkinitcpio -P
7 · Decide: which bootloader
| UKI + your own Secure Boot keys | systemd-boot | GRUB | |
|---|---|---|---|
| Secure Boot | Yes, with keys you control | Needs shim or your own keys | Yes, usually via shim |
| Complexity | Highest | Low | Medium |
| Legacy BIOS | No | No | Yes — the only option |
| Pick it if | You want the strongest boot chain | You want simple and UEFI-only | You need BIOS, or many OSes |
Full walkthroughs: docs/04-bootloaders and Secure Boot with your own keys. Minimal systemd-boot:
bootctl install
cat > /boot/loader/loader.conf <<'EOF'
default arch.conf
timeout 3
console-mode max
editor no
EOF
# editor no matters: without it anyone at the boot menu can append
# init=/bin/bash and walk straight past your login.
UUID=$(blkid -s UUID -o value /dev/nvme0n1p2)
cat > /boot/loader/entries/arch.conf <<EOF
title Arch Linux
linux /vmlinuz-linux
initrd /intel-ucode.img
initrd /initramfs-linux.img
options rd.luks.name=$UUID=cryptroot root=/dev/mapper/cryptroot rootflags=subvol=@ rw
EOF
8 · Decide: what goes on top
Nothing here is required to have a working system. Reboot first, confirm you can log in, then add things — debugging a desktop environment is much easier from a system you know boots.
- Display server — Wayland or Xorg. Wayland isolates clients from each other, which matters if you care about one window being unable to read another's keystrokes.
- Desktop — Dusky, Hyprland, DWM, or none. Dusky is the only preconfigured rice this project installs automatically.
- Firewall — UFW default-deny. Two commands, and it closes everything you did not deliberately open.
- Snapshots — Snapper or btrfs-assistant, if you chose Btrfs. A snapshot you never configured is not a backup.
- Security suite — the Rust tools. Read what each one does first; several can lock you out.
- Backups — the thing everyone skips. Snapshots live on the disk that fails.
9 · Reboot
exit
umount -R /mnt
reboot
If it does not come up: boot the installer again,
cryptsetup open, remount, arch-chroot, and
you are back where you were. Almost nothing at this stage is
unrecoverable. The recovery cheatsheet has
the sequence.
📦 The AUR, and How Not to Get Owned by It
The Arch User Repository is not a package repository. It is a
collection of build scripts that strangers wrote and that
makepkg executes on your machine, as you, with your
permissions. Nothing reviews them. That is not a criticism of the
AUR — it is stated plainly in
its own documentation
— but it is regularly forgotten.
PKGBUILD is a shell script. Anything
you can type in a terminal, it can do: read your SSH keys, add a
systemd unit, install a cron job, send a file somewhere. It does not
need to wait for you to install the package — prepare()
and build() run first.
Read it. Every time, including updates.
git clone https://aur.archlinux.org/some-package.git
cd some-package
less PKGBUILD
less *.install # these run as root, at install time
makepkg -si # only after you have read both
Updates matter as much as the first install. A package that was clean for two years can have a malicious commit pushed to it today, and your AUR helper will pull it without comment. Use one that shows you the diff:
paru --review # or: yay --answerdiff All
What to look for
| Pattern | Why it is worth stopping for |
|---|---|
curl … | sh, wget … | bash |
Executes whatever the server returns, at that moment. The code you reviewed is not the code that runs. |
sha256sums=('SKIP') |
Downloads are not verified at all. Combined with an
unpinned git+https:// source, you get whatever
the branch happens to point at. |
sudo or su inside a build function |
Build functions have no business escalating. Legitimate
root work happens in the .install file, which
you can read. |
Writes outside $srcdir / $pkgdir |
A package that touches $HOME or /etc
during build() is escaping the packaging model. |
base64 -d, eval, \x escapes |
Obfuscation. Build scripts have no legitimate need to hide what they contain. |
Network access in package() |
package() should only move already-built files
into place. |
| Source URL unrelated to the package name | A -bin package pulling a binary from a
personal file host is exactly how this goes wrong. |
| Recently orphaned, or a brand-new maintainer | Ownership transfer is a normal part of a supply-chain attack. Check the AUR page's comments and history. |
The auditor in this project
aur-guard statically checks a PKGBUILD and
its .install files for the patterns above, before
makepkg gets to run any of it.
aur-guard ./PKGBUILD
aur-guard --dir ~/.cache/paru/clone/some-package
aur-guard --json ./PKGBUILD | jq # for hooking into a helper
Or need less of it
The most effective AUR hardening is wanting fewer AUR packages.
Check the official repositories and Flatpak first; prefer
source-built AUR packages over -bin ones, since you can
at least read what is being compiled; and for anything you only need
occasionally, a container or a VM costs less than trusting a build
script.