Arch Guides Banner

Arch Guides Wiki

The Official Documentation & Security Suite Wiki

Developed by tilas01

⚙️ 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 lsblk to 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.

Read this first. Full-disk encryption plus Secure Boot with your own keys already defeats the realistic threat for most people: a stolen laptop. The measures below address a targeted attacker with repeated physical access. They cost money, void warranties, and can permanently brick hardware. Work out which threat you actually have before spending anything.

Step 0: lock down the firmware you already have

Free, reversible, and by far the best value. Do this regardless of anything else:

  1. Set a firmware supervisor/admin password. Without one, an attacker just changes the boot order and boots their own media.
  2. Set a separate power-on password if your firmware supports it.
  3. Disable booting from USB, network (PXE) and optical media.
  4. Enable Secure Boot and enrol your own keys — the generator's UKI + Custom Keys option does the key side. See Arch Wiki: Secure Boot.
  5. Clear the factory Microsoft keys once your own are enrolled and you have confirmed the machine boots. Keep recovery media to hand.
  6. 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.

The freedom/security trade-off, stated plainly. If your goal is tamper-evident boot, you want coreboot + Heads + a TPM + a USB token. If your goal is zero proprietary code, you want libreboot — and you will likely be on hardware old enough that measured boot is not available. You usually cannot maximise both at once. Decide which you actually care about.

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 /etc made 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.json and 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

  1. Imports the tilas01.asc signing key and prints its fingerprint for you to check.
  2. Downloads each selected binary plus its .sha512 and .sig.
  3. Verifies the hash, then the GPG signature, for every tool.
  4. Only once all of them pass does it install anything.
  5. Writes a hardened systemd unit per daemon and leaves it stopped.
  6. 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, MemoryDenyWriteExecute and a narrow ReadWritePaths. 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
Uninstall does not touch PAM or your config. If you enabled OTP at login, remove the 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.

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 nouveau instead, 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.

⚠️ A signature only proves the key holder signed it. It says nothing about whether the key is still trustworthy. If the project's signing key is ever exposed, treat every signature made with it as meaningless until a new key is published and the old one revoked.

🧠 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.

🔒 Reproducible & GPG Signed

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.

⚠️ There is no PAM module

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 nossh -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:

PINWhat entering it does
--set-duress-pinErases the LUKS header, then behaves exactly like a wrong password.
--set-decoy-pinOpens a believable working session in a decoy home. Erases nothing.
--set-duress-decoy-pinErases 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...

View All Releases on 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.

🟢 “I want a sensible, safe desktop.”

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.

🟠 “My laptop might be tampered with while I'm away.”

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.

🔴 “I need the machine to be unrecoverable under coercion.”

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 erase and 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.
⚠️ Before you enable an erasing action
  • 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-pin and its siblings configure.
⚠️ This page previously said scarecrow's PINs were entered "at the LUKS prompt". They are not.

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:

PasswordExitWhat happens
Your normal one1default=ignore — falls through to pam_unix, works exactly as before
Duress PIN1LUKS header erased first, then an ordinary-looking failure
Decoy PIN0success=done — auth granted, session starts in the decoy home
Combined PIN0Header 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.

⚠️ Edit PAM with a root shell already open.

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-time raises 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-sessions and 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.
⚠️ Practical warnings
  • 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/passwd and /etc/sudoers for 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

ProviderAddressesWho they are
Quad99.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.
Mullvad194.242.2.2 Run by the VPN provider, usable without a subscription. States no logging.
Cloudflare1.1.1.1, 1.0.0.1 Fast and very widely used. No-logging policy with published third-party audits.
dns0.eu193.110.81.0, 185.253.5.0 European non-profit, GDPR-scoped, states no logging.
AdGuard94.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.

⚠️ Warning

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.

⚠️ WARNING: Firmware Lockout Risk
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 -x at 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 sudo functioning 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 (like sudo -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.timer which 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 -p prompts 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 only will ask for an OTP immediately after LUKS decryption but before giving you a shell. Selecting All will prompt for OTP on every sudo, 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:

Concernx86_64aarch64
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.
⚠️ EEPROM firmware is a real attack surface on ARM. On a Raspberry Pi the bootloader lives in an EEPROM on the board, and it runs before anything you control. Update it deliberately (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

🔴 Do this before you install, not after. Every layer below — LUKS, Secure Boot, boot-integrity hashing — assumes the firmware underneath it is doing what you told it to. Firmware you never configured is firmware configured by whoever had the machine last.

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.

"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.

SettingSet it toWhy
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.
⚠️ Write the supervisor password down and store it away from the machine. On many laptops a forgotten supervisor password is not recoverable by you — it means a service centre visit or a new mainboard. That is the same property that makes it worth setting.

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.

Every command below that names a disk will destroy what is on it. /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

🟢 Encrypted (recommended for anything portable)

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.

🟠 Unencrypted

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

Btrfsext4
SnapshotsYes — a bad update is one rollback awayNo
ComplexitySubvolumes to lay out, and its own toolingFormat and forget
RecoveryExcellent when you have snapshotsVery mature, very well understood
Pick it ifYou want to undo a bad upgradeYou 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 keyssystemd-bootGRUB
Secure BootYes, with keys you controlNeeds shim or your own keysYes, usually via shim
ComplexityHighestLowMedium
Legacy BIOSNoNoYes — the only option
Pick it ifYou want the strongest boot chainYou want simple and UEFI-onlyYou 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 serverWayland or Xorg. Wayland isolates clients from each other, which matters if you care about one window being unable to read another's keystrokes.
  • DesktopDusky, Hyprland, DWM, or none. Dusky is the only preconfigured rice this project installs automatically.
  • FirewallUFW 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 suitethe 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.

A 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

PatternWhy 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
It will never tell you a package is safe. It reports what it found and what it could not analyse. A clean report means "no known-bad pattern matched", which is a much weaker statement than "this is fine", and a determined author can write something it does not match. It is a reading aid, not a verdict.

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.