Add 3 articles; update nav and index (2026-04-29)

New articles:
- Python smtplib: Missing Date/Message-ID Headers Break Mail Clients
- Fantastical MCP: Permission Denied (macOS Quarantine)
- Ubuntu dist-upgrade Repo Quarantine

Updated: troubleshooting index, SUMMARY.md nav, WOL article edits.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marcus Summers 2026-04-29 22:45:17 -04:00
parent 91455fac39
commit 1524ca66d5
6 changed files with 291 additions and 7 deletions

View file

@ -1,14 +1,21 @@
---
title: Wake-on-LAN via Router SSH
description: Send WOL magic packets through an Asus router over SSH
tags: [networking, wol, asus, ssh]
tags:
- networking
- wol
- asus
- ssh
created: 2026-04-19
updated: 2026-04-27T00:53
---
# Wake-on-LAN via Router SSH
Most Asus routers running AsusWRT (or Merlin) include `ether-wake` in their BusyBox environment. Combined with SSH access, this lets you wake machines remotely from anywhere — even over a VPN like Tailscale — without needing a dedicated WOL tool on the LAN.
> **Status: Deployed 2026-04-27.** `wake-majormac` script live on MajorMac and MajorRig. Router SSH confirmed working on RT-AX82U (AsusWRT 388, `ether-wake` at `/usr/sbin/ether-wake`). Credentials stored in Ansible vault as `router_username` / `router_password`.
## Prerequisites
- **SSH enabled on the router** — Administration → System → Enable SSH → LAN only
@ -25,9 +32,11 @@ Asus routers use a non-standard SSH port by default. Check your router's SSH set
Host router
HostName 192.168.50.1
Port 1025
User <your-username>
User majorlinux
```
> RT-AX82U confirmed on port 1025. `sshpass` required for non-interactive use — install via `brew install sshpass` on Mac.
## Sending a WOL Packet
```bash
@ -43,7 +52,7 @@ sshpass -p "$ROUTER_PASS" ssh router 'ether-wake -i br0 AA:BB:CC:DD:EE:FF'
## Scripting with Ansible Vault
If your router password is stored in an Ansible vault, you can pull it at runtime:
Router credentials are stored in the Ansible vault as `router_username` and `router_password`. The `wake-majormac` script at `~/.local/bin/wake-majormac` (deployed on MajorMac and MajorRig) handles this automatically:
```bash
#!/usr/bin/env bash
@ -51,11 +60,21 @@ set -euo pipefail
VAULT_FILE="$HOME/MajorAnsible/group_vars/all/vault.yml"
VAULT_PASS_FILE="$HOME/.ansible/vault_pass"
MAC="9c:76:0e:3f:10:58" # MajorMac (Mac Studio) en0
password=$(ansible-vault view "$VAULT_FILE" --vault-password-file "$VAULT_PASS_FILE" 2>/dev/null \
| grep '^router_password:' | sed 's/^router_password: *"\{0,1\}\([^"]*\)"\{0,1\}/\1/')
sshpass -p "$password" ssh router "ether-wake -i br0 AA:BB:CC:DD:EE:FF"
echo "Sending WOL magic packet to MajorMac ($MAC)..."
sshpass -p "$password" ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 \
-p 1025 majorlinux@192.168.50.1 "ether-wake -i br0 $MAC"
echo "Done. MajorMac should wake within ~30 seconds."
```
To wake MajorMac from any machine on the fleet:
```bash
wake-majormac
```
## Troubleshooting
@ -64,8 +83,10 @@ sshpass -p "$password" ssh router "ether-wake -i br0 AA:BB:CC:DD:EE:FF"
|-------|-----|
| `Connection refused` | SSH not enabled on router, or wrong port |
| `Permission denied` | Wrong username/password |
| Machine doesn't wake | Check WOL is enabled in BIOS; verify MAC address; ensure machine is plugged in (not on battery) |
| `sshpass: command not found` | `brew install sshpass` on Mac; `apt install sshpass` on Debian |
| Machine doesn't wake | Check `womp 1` in `pmset -g`; verify MAC address; machine must be plugged in |
| `ether-wake: not found` | Router firmware may not include it — check with `which ether-wake` |
| SSH times out | Router SSH is LAN-only; must be on LAN or send via a LAN-connected host |
## Why Not Use a Dedicated WOL Tool?

View file

@ -0,0 +1,98 @@
---
title: "Fantastical MCP Server: Permission Denied on Launch (macOS Quarantine)"
domain: troubleshooting
category: productivity
tags: [fantastical, mcp, claude, macos, gatekeeper, quarantine, cowork]
status: published
created: 2026-04-26
updated: 2026-04-26
---
# Fantastical MCP Server: Permission Denied on Launch (macOS Quarantine)
Fantastical's MCP server fails to connect in Claude/Cowork with a `Server disconnected` error and no dialog or prompt to explain why. The binary is installed but macOS Gatekeeper silently blocks it from executing.
## The Short Answer
```bash
xattr -d com.apple.quarantine "/Users/majorlinux/Library/Application Support/Claude/Claude Extensions/ant.dir.gh.flexibits.fantastical-mcp/server/FantasticalMCP.app"
```
Fully quit and reopen Cowork. Fantastical MCP reconnects cleanly.
If the quarantine attribute isn't present, also try setting the executable bit:
```bash
chmod +x "/Users/majorlinux/Library/Application Support/Claude/Claude Extensions/ant.dir.gh.flexibits.fantastical-mcp/server/FantasticalMCP.app/Contents/MacOS/FantasticalMCP"
```
## Why This Happens
macOS automatically tags downloaded files with a `com.apple.quarantine` extended attribute. When you launch an app yourself, macOS shows a Gatekeeper dialog — click Open, and the quarantine flag is cleared. But the FantasticalMCP binary is never launched by the user directly; Claude/Cowork spawns it as a subprocess. There's no dialog, and Gatekeeper just returns `Permission denied`. Claude sees the process die immediately and logs `Server disconnected`.
This recurs after any Fantastical update that replaces the MCP binary — the new binary comes in quarantined again.
## Diagnosis
The log tells the whole story. Check:
```bash
tail -n 50 ~/Library/Logs/Claude/mcp-server-Fantastical.log
```
If you see this sequence, it's the quarantine issue:
```
Server started and connected successfully
Failed to spawn process: Permission denied
Server transport closed
Server disconnected.
```
## Full Fix
**Step 1 — Remove the quarantine flag:**
```bash
xattr -d com.apple.quarantine \
"/Users/majorlinux/Library/Application Support/Claude/Claude Extensions/ant.dir.gh.flexibits.fantastical-mcp/server/FantasticalMCP.app"
```
**Step 2 — Verify the attribute is gone:**
```bash
xattr "/Users/majorlinux/Library/Application Support/Claude/Claude Extensions/ant.dir.gh.flexibits.fantastical-mcp/server/FantasticalMCP.app"
```
Should return empty or only non-quarantine attributes. If `com.apple.quarantine` is still listed, re-run step 1.
**Step 3 — Fully quit and reopen Cowork:**
Cmd+Q (not just close the window). Closing the window leaves the MCP host process running — it won't retry the failed server until the app fully relaunches.
**Step 4 — Verify connection:**
Check the log again:
```bash
tail -n 10 ~/Library/Logs/Claude/mcp-server-Fantastical.log
```
You should see `Server started and connected successfully` with no `Permission denied` line following it.
## After a Fantastical Update
If this breaks again after Fantastical auto-updates, re-run the `xattr -d` command from Step 1. The update replaces the binary and macOS re-quarantines the new one.
## MCP Log Locations
| Log | Path |
|-----|------|
| Fantastical MCP | `~/Library/Logs/Claude/mcp-server-Fantastical.log` |
| All MCP servers | `~/Library/Logs/Claude/mcp*.log` |
| Combined MCP log | `~/Library/Logs/Claude/mcp.log` |
## Related
- [[Fantastical Google Sync Error Flood — Phantom Calendars Fixed via syncselect]]
- MCP debugging docs: https://modelcontextprotocol.io/docs/tools/debugging

View file

@ -1,6 +1,6 @@
---
created: 2026-03-15T06:37
updated: 2026-04-25T12:57
updated: 2026-04-29T22:40
---
# 🔧 General Troubleshooting
@ -39,7 +39,12 @@ Practical fixes for common Linux, networking, and application problems.
## 💾 Storage
- [mdadm RAID Recovery After USB Hub Disconnect](storage/mdadm-usb-hub-disconnect-recovery.md)
## 📧 Email & SMTP
- [Python smtplib: Missing Date/Message-ID Headers Break Mail Clients](python-smtplib-missing-rfc-headers.md)
## 📝 Application Specific
- [Fantastical MCP: Permission Denied (macOS Quarantine)](fantastical-mcp-permission-denied.md)
- [Fantastical Google Sync: Phantom Calendars via syncselect](fantastical-google-phantom-calendar-syncselect.md)
- [Obsidian Vault Recovery — Loading Cache Hang](obsidian-cache-hang-recovery.md)
- [Gemini CLI Manual Update](gemini-cli-manual-update.md)

View file

@ -0,0 +1,60 @@
---
title: "Python smtplib: Missing Date/Message-ID Headers Break Mail Clients"
domain: troubleshooting
category: general
tags: [email, python, smtplib, spam, rfc, spark]
status: published
created: 2026-04-29
updated: 2026-04-29
---
# Python smtplib: Missing Date/Message-ID Headers Break Mail Clients
## Problem
Emails sent via Python's `smtplib` and `EmailMessage` appear on some mail clients but not others. The emails are delivered to the server and visible in Maildir, but specific clients silently suppress them.
## Root Cause
Python's `EmailMessage` does **not** automatically add `Date:` or `Message-ID:` headers. These are required by RFC 5322. Without them:
- **SpamAssassin** flags `MISSING_DATE` and `MISSING_MID`, and may set `X-Spam-Flag: YES` even if the overall score is below the spam threshold
- **Mail clients** (e.g., Spark) may filter on the spam flag header and silently hide the message — no Junk folder, just invisible
- **Other clients** (e.g., iPhone Mail, some Spark builds) may be more lenient and display the message anyway
This creates a confusing situation where the same email appears on one device but not another, despite both using the same IMAP account.
## Fix
Always include `Date` and `Message-ID` headers when constructing emails with `EmailMessage`:
```python
import smtplib
from email.message import EmailMessage
from email.utils import formatdate, make_msgid
msg = EmailMessage()
msg['Subject'] = 'Your subject here'
msg['From'] = 'sender@example.com'
msg['To'] = 'recipient@example.com'
msg['Date'] = formatdate(localtime=True)
msg['Message-ID'] = make_msgid(domain='example.com')
msg.set_content('Email body here')
with smtplib.SMTP('mail.example.com', 25) as s:
s.send_message(msg)
```
## Verification
After applying the fix, check that SpamAssassin no longer flags the headers:
```bash
# Check email headers on the mail server
grep -E 'MISSING_DATE|MISSING_MID|X-Spam' /var/vmail/domain/user/cur/<message-file>
```
A clean message should show `X-Spam-Status: No` with no `MISSING_DATE` or `MISSING_MID` in the test list.
## Key Takeaway
Python's `EmailMessage` is a low-level builder — it trusts you to set all required headers. Unlike higher-level mail libraries or webmail interfaces, it will happily send a message with no date or message ID. Always add both explicitly in any script that sends email via `smtplib`.

View file

@ -0,0 +1,98 @@
---
title: "Ubuntu dist-upgrade Quarantines Third-Party Repos"
domain: troubleshooting
category: ubuntu
tags: [ubuntu, apt, dist-upgrade, repositories, tailscale, digitalocean]
status: published
created: 2026-04-28
updated: 2026-04-28
---
# Ubuntu dist-upgrade Quarantines Third-Party Repos
## Problem
When running `do-release-upgrade` (e.g., Jammy 22.04 to Noble 24.04), Ubuntu renames all third-party `.list` files in `/etc/apt/sources.list.d/` to `.list.distUpgrade`. This silently disables every third-party repo — packages from those repos stop receiving updates with no warning.
The upgrade process does this intentionally because it can't guarantee third-party repos will have packages for the new release. Some repos get re-added as `.sources` files during the upgrade, but many don't.
## Symptoms
- `apt list --upgradable` shows nothing for packages you know have updates (e.g., Tailscale stuck on an old version)
- `apt list --installed` shows packages as `[installed,local]` instead of `[installed]` — the "local" tag means apt has no repo to check for updates
- `.distUpgrade` files accumulate in `/etc/apt/sources.list.d/` indefinitely
## Diagnosis
Check for quarantined repos:
```bash
ls /etc/apt/sources.list.d/*.distUpgrade
```
For each file, check whether a replacement `.list` or `.sources` file already exists:
```bash
ls /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources
```
## Fix
### Distro-agnostic repos (e.g., DigitalOcean agents)
If the repo URL doesn't reference a distro codename (jammy/noble), just rename:
```bash
mv /etc/apt/sources.list.d/digitalocean-agent.list.distUpgrade \
/etc/apt/sources.list.d/digitalocean-agent.list
```
### Distro-specific repos (e.g., Tailscale, ondrej-php)
The quarantined file references the old distro (jammy). Re-run the upstream install script to get a correct entry for the new release:
```bash
# Tailscale
curl -fsSL https://tailscale.com/install.sh | sh
# Or manually: update the codename
sed 's/jammy/noble/' /etc/apt/sources.list.d/tailscale.list.distUpgrade \
> /etc/apt/sources.list.d/tailscale.list
apt update && apt upgrade tailscale
```
### Already replaced by .sources
If the upgrade process already created a `.sources` replacement (common for ubuntu-esm-apps, ondrej-php), the `.distUpgrade` file is just clutter — delete it:
```bash
rm /etc/apt/sources.list.d/ondrej-ubuntu-php-jammy.list.distUpgrade
```
### After all fixes
```bash
apt update
apt list --upgradable # should now show pending updates
apt upgrade
```
## Real-World Example: MajorsHouse Fleet (2026-04-28)
Five Ubuntu 24.04 servers were dist-upgraded from Jammy in October 2024. The `.distUpgrade` quarantine was discovered 6 months later when Tailscale's website wouldn't load (Pi-hole was blocking subdomains, but the investigation revealed teelia was stuck on Tailscale 1.76.0 — 20 versions behind — because the repo was disabled).
| Host | Quarantined files | Impact |
|------|------------------|--------|
| dcaprod | 8 | Tailscale, DO agents, MySQL, ondrej-php, ESM, vector |
| teelia | 4 | Tailscale (stuck on 1.76.0), DO agents, certbot bionic PPA |
| majorlinux | 8 | Tailscale, DO agents, MySQL, ondrej-php, ESM, apt-fast |
| majortoot | 11 | Tailscale, DO agents, nodesource, PostgreSQL, vector, zabbix, ESM |
| tttpod | 0 | Clean — was likely rebuilt rather than upgraded |
All files were audited, stale ones deleted, distro-agnostic repos renamed, and distro-specific repos re-added via upstream install scripts. DO agents upgraded from 3.16.11 to 3.18.12, teelia's Tailscale jumped from 1.76.0 to 1.96.4.
## Prevention
- **Post-upgrade audit:** After any `do-release-upgrade`, immediately run `ls /etc/apt/sources.list.d/*.distUpgrade` and resolve each file.
- **Prefer `.sources` format:** When adding new third-party repos, use the DEB822 `.sources` format — it's what Ubuntu itself uses on Noble and is handled more gracefully during upgrades.
- **Ansible playbook:** Consider a post-upgrade play that checks for `.distUpgrade` files and alerts or auto-fixes distro-agnostic repos.

View file

@ -1,6 +1,6 @@
---
created: 2026-04-02T16:03
updated: 2026-04-25T12:57
updated: 2026-04-26T10:11
---
* [Home](index.md)
* [Linux & Sysadmin](01-linux/index.md)
@ -90,6 +90,8 @@ updated: 2026-04-25T12:57
* [Ollama Drops Off Tailscale When Mac Sleeps](05-troubleshooting/ollama-macos-sleep-tailscale-disconnect.md)
* [Ollama: `ollama run` with Piped Stdin Bypasses Chat Template + SYSTEM Prompt](05-troubleshooting/ollama-chat-template-pipe-stdin-bypass.md)
* [rsync over Tailscale: Hung in TCP Teardown After Transfer Completes](05-troubleshooting/networking/rsync-tailscale-teardown-stall.md)
* [Fantastical MCP: Permission Denied (macOS Quarantine)](05-troubleshooting/fantastical-mcp-permission-denied.md)
* [Fantastical Google Sync: Phantom Calendars Fixed via syncselect](05-troubleshooting/fantastical-google-phantom-calendar-syncselect.md)
* [macOS: Repeating Alert Tone from Mirrored iPhone Notification](05-troubleshooting/macos-mirrored-notification-alert-loop.md)
* [ClamAV CPU Spike: Safe Scheduling with nice/ionice](05-troubleshooting/security/clamscan-cpu-spike-nice-ionice.md)
* [Ansible: Vault Password File Not Found](05-troubleshooting/ansible-vault-password-file-missing.md)