Writing

· 5 min read

Two laptops that watch each other: my no-bag remote workflow

My MacBook stays home and my Ubuntu laptop stays at the office. Each can drive the other over Tailscale, and each keeps an eye on the Mac: a launchd watchdog on the Mac and a cron check on the Ubuntu side both push alerts to my phone through ntfy.

I don't carry a laptop between home and work. My MacBook Air stays at home, closed on its charger. My Ubuntu laptop stays at the office. From either one I can drive the other, and if the Mac gets into trouble, I hear about it on my phone before it costs me a working session.

This is how the pieces fit together.

The stack#

Job Tool
Private network between all my devices Tailscale
Mac → Ubuntu, terminal SSH (keys only)
Mac → Ubuntu, desktop GNOME Remote Desktop (RDP)
Ubuntu → Mac, desktop RustDesk, with macOS Screen Sharing (VNC, via Remmina) as a fallback
Keep the Mac awake with the lid closed Amphetamine
Keep the Ubuntu laptop running with the lid closed an Ubuntu lid-close power setting
Read Apple Silicon temperatures macmon, plus a tiny Swift helper for macOS thermal pressure
Scheduling launchd on the Mac, cron on Ubuntu
Alerts to my iPhone ntfy

Both directions, over one private network#

Every machine joins the same Tailscale network, so each has a stable private address wherever it is, and nothing is exposed to the public internet.

From home to the office (Mac → Ubuntu). Day-to-day work is plain SSH with key-based login and a short host alias in ~/.ssh/config. When I need the actual screen, GNOME's built-in remote desktop shares the real desktop session over RDP.

From the office to home (Ubuntu → Mac). RustDesk gives me the full Mac desktop. Two settings matter:

  • connect by Tailscale IP, not RustDesk ID, so the session never touches a public relay I don't need;
  • an IP whitelist of 100.64.0.0/10, the range Tailscale assigns, so only my own devices can connect at all.

If RustDesk's service ever stops (it can after a macOS update), macOS Screen Sharing over VNC, opened from Remmina on the Ubuntu laptop, is the backup way in.

One practical note: office internet is slow to anything abroad, so Tailscale often relays traffic instead of connecting directly (150–550 ms). Switching RustDesk to the VP8 codec fixed the freezing, and a phone hotspot roughly halves the latency when I need it snappier.

Keeping the Mac available, but not forever#

Amphetamine keeps the Mac awake with the lid closed on a schedule: working days, from just before I leave home until evening. Outside those hours it sleeps normally. On the Ubuntu laptop, a lid-close power setting stops it suspending when the lid is shut.

Two watchers, one phone#

A machine you can't see needs someone else to look at it. Mine has two.

1. The Mac watches itself (launchd, every 2 minutes)#

A Bash watchdog checks:

  • Heat with macmon: 85 °C twice in a row is "hot", 95 °C (or macOS reporting serious thermal pressure) is urgent, and below 75 °C it's over;
  • Power: the charger dropping out (in Dhaka, usually load shedding), plus an urgent alert under 25% battery;
  • Disk: under 10 GB free (recovered above 15 GB);
  • Remote access: RustDesk and Tailscale still running, because if either dies the Mac is effectively gone;
  • Runaway apps: one process above 80% CPU for 10 minutes.

It writes one log line per run, so I can look back at what the Mac was doing when something went wrong.

2. The Ubuntu laptop watches the Mac (cron, every 3 minutes)#

The Mac can't tell me it's offline, so the office laptop does. During the Mac's awake hours it pings the Mac over Tailscale and alerts after about nine minutes of silence ("Mac unreachable"), then again when it's back.

The important detail: it checks its own internet first. If the office connection is down, it counts nothing, so an office outage never looks like my Mac dying.

Both watchers post to the same private ntfy channel, and I'm subscribed on my iPhone:

curl -H "Title: [MacBook Air] Mac unreachable" -H "Priority: high" \
     -d "No reply for 9 minutes" "https://ntfy.sh/$TOPIC"

Designing alerts I won't learn to ignore#

  1. Confirm before alerting. A problem has to hold across repeated checks.
  2. Alert once when it starts, once when it ends, and nothing in between.
  3. Never lose an alert. If a push fails (Wi-Fi is often down during the same power cut that triggered it), it's retried on the next run.
  4. Hysteresis. Separate thresholds for "problem" and "recovered", so a value sitting on the line doesn't flap.
  5. Know when silence is expected. The offline check only runs during the Mac's awake hours; a Mac that is allowed to sleep isn't an incident.
  6. Keep the channel private. The ntfy topic lives in a local config file on each machine, never in a script, because anyone who knows it can read the alerts.

What it has caught so far#

  • A background app cooking the Mac. A download manager had started itself at login and was quietly seeding with hundreds of connections at 30–55% CPU. The heat and runaway-app alerts pointed straight at it.
  • An update that locked me out. Automatic macOS updates restarted the Mac, and it sat at the login screen, unreachable, until someone at home typed the password. Automatic updates are now off; I update when I'm at the desk.

What it taught me#

Remote access is the easy half. The hard half is knowing the machine is healthy when you can't see it, and that's a monitoring question: what counts as unhealthy, how you confirm it, who watches the watcher, and how you avoid alerts you'll eventually mute. Those are the same questions I ask of the systems I build at work, just at the scale of two laptops and a phone.


Deliberately general where it should be: no hostnames, addresses, usernames, topic names, or configuration that would help anyone else reach these machines.

macosubuntutailscalemonitoringntfy