Closing the Lid on My Linux Workstation

May 7, 2026

Long-running coding agents and laptops do not get along. The agent wants the machine awake for hours. The laptop wants to sleep the moment you stop touching it. Every time I queue up something serious and walk off, I come back to a paused session and nothing done.

I've been doing the laptop-open-on-the-counter thing for a while. It works until it doesn't. Eventually you want to close the lid, or move rooms, or get on a train. The fix isn't a smarter sleep policy. The fix is to stop using the laptop as the workhorse.

So now I don't. The laptop is a thin client. The actual work happens on a small Linux box at home that wakes up when I want it and shuts down when I don't. It feels exactly like working locally, except I can close my laptop whenever.

Why this works for me

About 90% of my day runs in tmux, Claude Code, nvim, and docker. All terminal. Mosh into a tmux session on the workstation feels identical to running it locally, because there was nothing graphical to lose in the first place. If your day is Figma, Photoshop, or anything that needs a real display, this pattern doesn't help you. Stop reading. For everyone else, three non-obvious tricks make it work.

The shape

Three machines, one role each.

  • Mac (anywhere) is the thin client. Disposable. Could be a different Mac tomorrow.
  • Pi (always on, home LAN) is the gateway. I already had a Raspberry Pi running at home for unrelated reasons, but any always-on Linux box on the LAN works. A NAS, a router with shell access, an old laptop in a drawer. Its only job in this story is to broadcast a wake packet on the local network.
  • PN53 (the workhorse) sleeps until called. ASUS PN53 mini PC in my case, but the pattern is distro and hardware agnostic.
Mac (anywhere)
    │
    ├──── Tailscale ────► Pi (always on, home LAN)
    │                       │
    │                       └── magic packet (LAN broadcast) ──► PN53 NIC
    │                                                              │
    │                                                              ▼
    │                                                          [boot + LUKS auto-unlock]
    │                                                              │
    └──── Tailscale ──── mosh ────────────────────────────► PN53 (tmux session)

Three tricks hold this together, each fixing something that doesn't work out of the box.

Trick 1: the Pi is a Wake-on-LAN accomplice

Magic packets are layer 2 broadcasts. Tailscale is layer 3. The two don't mix. The Mac can reach the PN53's hostname over the tailnet just fine, but it can't send a magic packet that the PN53's NIC will hear, because that packet has to be broadcast on the local subnet.

So the Pi does it. The Mac SSHes into the Pi over Tailscale, and asks the Pi to broadcast the wake packet from inside the LAN.

fish
ssh $BOX_GATEWAY_USER@$BOX_GATEWAY_HOST "wakeonlan $BOX_MAC"

That's it. One line inside the fish function on the Mac. The Pi just needs wakeonlan installed and the PN53's wired MAC address.

Two prereqs that catch people: the PN53 needs ErP disabled and Power-On-By-PCI-E enabled in BIOS so the NIC stays powered in S5, and the cable needs to be wired Ethernet. WiFi WoL is technically possible but unreliable on Linux, so I don't bother.

Trick 2: bind LUKS to the TPM so the disk unlocks itself

Default Omarchy installs ship with a LUKS-encrypted root, which is great until you wake the machine remotely and it sits at a passphrase prompt forever. Nobody is at the keyboard.

The fix is to bind LUKS to the TPM2 with clevis. The disk auto-unlocks as long as the boot path matches the measurements that were enrolled. If the firmware changes, or someone yanks the drive into another box, the auto-unlock stops working and you fall back to the passphrase.

bash
sudo clevis luks bind -d /dev/nvme0n1p2 tpm2 '{}'

You also have to add clevis before encrypt in the mkinitcpio HOOKS line and rebuild the initramfs. Full walkthrough is in the repo at the bottom of the post.

The honest tradeoff, because I would want to know it before doing this: anyone with physical access to the box can boot it. Your user login is still required after boot, but this is no longer a stolen-laptop-grade defense. Fine for a home server tucked behind a desk. Not fine for a portable.

Trick 3: mosh plus tmux, one session per client

Mosh survives sleep, network changes, and IP roaming. Tmux holds the session on the server side. The combination is what actually delivers the "close the lid" part of the title. Close the laptop, walk to a coffee shop, open the laptop, mosh reconnects to the same tmux session that's been running the whole time. Claude Code is still chugging, the test suite is still running, docker hasn't noticed.

The per-client trick is small but worth calling out. Each device gets its own tmux session named after the client's hostname, so my laptop and my iPad don't fight over the same session.

fish
set -l session (scutil --get LocalHostName | string replace -ra '[^a-zA-Z0-9-]' '-')
mosh $BOX_USER@$BOX_HOST -- tmux new-session -A -s $session

new-session -A -s attaches if a session with that name already exists, creates it otherwise. Idempotent. One command, every time.

Daily flow

fish
box          # idempotent: wakes if off, attaches mosh+tmux either way
# work in nvim, docker, claude code, etc.
# Ctrl-b d  to detach (keep tasks running, machine stays on)
bye          # inside the session: full shutdown

box in the morning. bye at the end of the day. Or box down from the Mac when I'm not in a session. That's the whole interaction.

Full setup

The BIOS steps, the systemd unit that persists ethtool wol g, the sudoers rule for passwordless poweroff, the full clevis dance, and the complete fish functions all live in my dotfiles repo:

github.com/samialdury/dotfiles/blob/main/docs/REMOTE_WORKSTATION.md

The pattern works on any Linux distro and any small wired-Ethernet box. PN53 and Omarchy are just what I happen to run.