Chromebook on Linux: Search + top row as media keys, without losing F1–F12

On a Chromebook running Linux, the top row sends plain F1–F10, so brightness and volume keys do nothing. Most remap scripts flip it: media keys by default and Search for F-keys. That makes Ctrl+Alt+F1 a four-key chord. Doing it the other way keeps both: F-keys by default, and Search acts like Fn.

Install keyd (it's packaged in Debian 13) and write /etc/keyd/default.conf:

# internal keyboard only (vendor:product of "AT Translated Set 2 keyboard")
[ids]
0001:0001

# Search is leftmeta; holding it turns the top row into Chromebook actions
[meta]
f1 = back
f2 = forward
f3 = refresh
f4 = f11
f6 = brightnessdown
f7 = brightnessup
f8 = mute
f9 = volumedown
f10 = volumeup

Then restart it:

sudo systemctl restart keyd
sudo journalctl -u keyd -b -o cat | grep match

You should see a DEVICE: match 0001:0001:... line. One gotcha: the Debian package starts keyd as soon as it's installed, before your config exists. The running daemon then logs ignoring for every device until you restart it. Debian also ships the binary as keyd.rvaiya, so keyd check isn't on your PATH.

Restricting [ids] to the built-in keyboard leaves an external USB keyboard's real F-keys alone. You can find your IDs with grep -A4 'AT Translated' /proc/bus/input/devices. The mapping above matches the classic Chromebook top row (back, forward, refresh, fullscreen, overview, brightness −/+, mute, volume −/+).

Old Bay Trail laptop dies with the lid closed and leaves no logs? Cap the C-states

My Linux Chromebook (Celeron N2840, Bay Trail) kept "turning itself off" while the lid was closed. When I opened it the screen was black, and only a long press on Power brought it back. I never shut it down, and the lid action was set to "display off" rather than suspend.

The journal confirmed every boot ended without any shutdown sequence:

journalctl --list-boots
sudo journalctl -b -1 -n 30 --no-pager

The previous boots just stopped. earlyoom logs a line every minute, and that line simply ended. Nothing else was there either: no oops, nothing in /var/lib/systemd/pstore/, memory and temperatures fine. A hard freeze that writes nothing, and only while the machine is idle, is the classic Bay Trail deep C-state bug (kernel bugzilla 109051). It still shows up on a 6.12 kernel.

The usual workaround is to keep the CPU out of the deep idle states:

sudo sed -i 's/^GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"$/GRUB_CMDLINE_LINUX_DEFAULT="quiet splash intel_idle.max_cstate=1"/' /etc/default/grub
sudo update-grub

After a reboot, check it took:

cat /sys/module/intel_idle/parameters/max_cstate      # 1
cat /sys/devices/system/cpu/cpu0/cpuidle/state*/name  # only POLL and C1

The cost is a little more idle power, which hardly matters on AC. If it still freezes, add processor.max_cstate=1 as well.

While I was in there, I found that MX Linux had installed broadcom-sta-dkms, and its wl module was loading on a machine with Intel wifi. That tainted the kernel and printed a WARNING at boot. If lspci shows no Broadcom card, blacklist it:

printf 'blacklist wl\n' | sudo tee /etc/modprobe.d/no-wl.conf
sudo update-initramfs -u

DNS works but the travel router has no Internet? Check the uplink firewall zone

A travel router can hand out addresses and resolve DNS while every web connection times out. On LEDE/OpenWrt, a common cause is putting the Wi-Fi client (STA) interface in the LAN zone instead of the masquerading WAN zone: DNS terminates on the router, but forwarded packets never get NATed upstream.

The Wi-Fi uplink must be a DHCP client (often called wwan), a member of the WAN firewall zone, and covered by WAN masquerading. The access-point interface and its clients stay in the LAN zone.

Inspect the zones first; anonymous section indexes vary:

uci show firewall | grep -E "zone\[[0-9]+\].(name|network)"

In the layout used by this LEDE router, the fix was:

uci del_list firewall.@zone[0].network='wwan'
uci add_list firewall.@zone[1].network='wwan'
uci commit firewall
/etc/init.d/firewall reload

The final check should show wwan in the WAN zone and a MASQUERADE rule for the active uplink:

uci show firewall | grep -E "zone\[[01]\].(name|network)"
iptables -t nat -L POSTROUTING -v -n

For a hotel Ethernet socket, connect the cable to the router's WAN device (eth0 here), disable the STA uplink, and leave the AP enabled. For hotel Wi-Fi, enable STA/wwan and leave the same AP enabled for your devices. Use one upstream at a time; two active uplinks make routing and DHCP failures much harder to diagnose. Complete any hotel captive-portal login before testing the downstream clients.

DNS success alone proves only that the router answered DNS. Verify an actual TCP request after fixing the firewall path; only then can a transparent proxy or split-routing policy reliably handle the traffic.

One endpoint for all your AI subscriptions

If you run coding agents all day, you know the moment: Claude Code stops mid-task because the five-hour window ran out, and you go hunting for the other account. agent-quota-gateway is the small Go binary I wrote so that moment never comes.

What it does. Your agents talk to one local endpoint instead of to Anthropic or OpenAI directly. They don't carry a real token; they send a pool name. The gateway holds the credentials, keeps each agent on one account, and when that account returns a real 429 it fails over to the next one, in the order you declared. When the whole pool is dry it answers 503 with a Retry-After, so Claude Code waits for the reset and resumes instead of ending the turn. The first time it worked, I wrote: the subscription burned out and Claude Code moved to the next one without noticing.

What makes it different.

  • Credentials stay at the gateway and are never logged, so you can hand a pool name to any worker, on any machine.
  • Pool several people's subscriptions and use them off-peak: for a small team, everyone's five-hour quota gets several times larger.
  • Per-worker affinity: each agent sticks to one account, so prompt caches survive; a pool's concurrency decides how many accounts serve at once.
  • Anthropic Messages and OpenAI-compatible Responses pass through untouched, so new endpoints and beta headers work on day one.
  • One stdlib-only binary, a systemd deploy script, a quota dashboard, and shared mode over Tailscale or OpenVPN.

What it is not. It doesn't translate between providers, meter usage, or bill anyone. It is a thin proxy on purpose.

Price. Free. The source is on GitHub under BSL 1.1: read it, change it, run it at home or at work, in production. The only thing you may not do is sell it as a hosted service. Each release becomes Apache 2.0 four years on.

I built it for my own agent fleet (it runs my-ai-team), and I have installed it for a company that wanted to pool its Claude seats. Try it: https://github.com/SHUKE-LABS/agent-quota-gateway

A self-hosted runner can be offline while its server is online

A machine can have normal network connectivity while GitHub Actions sees its self-hosted runner as offline. The runner listener is a separate process, and GitHub may delete a registration after it has been disconnected for a long time. The local .runner file and systemd unit can still remain, which makes the failure easy to miss.

Make ordinary crashes and reboots self-healing with a systemd drop-in:

[Unit]
StartLimitIntervalSec=0

[Service]
Restart=always
RestartSec=10s

Then reload systemd and restart the runner. Check both systemctl is-active locally and the runner status in GitHub. If the log says the registration was deleted or asks you to re-configure, a restart is not enough: obtain a fresh registration token and run config.sh --replace before enabling the service again.