diff --git a/02-selfhosting/dns-networking/wake-on-lan-router-ssh.md b/02-selfhosting/dns-networking/wake-on-lan-router-ssh.md index 03afa3b..2ffad9f 100644 --- a/02-selfhosting/dns-networking/wake-on-lan-router-ssh.md +++ b/02-selfhosting/dns-networking/wake-on-lan-router-ssh.md @@ -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 + 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? diff --git a/05-troubleshooting/fantastical-mcp-permission-denied.md b/05-troubleshooting/fantastical-mcp-permission-denied.md new file mode 100644 index 0000000..626212b --- /dev/null +++ b/05-troubleshooting/fantastical-mcp-permission-denied.md @@ -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 diff --git a/05-troubleshooting/index.md b/05-troubleshooting/index.md index 15fb483..2e87781 100644 --- a/05-troubleshooting/index.md +++ b/05-troubleshooting/index.md @@ -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) diff --git a/05-troubleshooting/python-smtplib-missing-rfc-headers.md b/05-troubleshooting/python-smtplib-missing-rfc-headers.md new file mode 100644 index 0000000..cc8b8e4 --- /dev/null +++ b/05-troubleshooting/python-smtplib-missing-rfc-headers.md @@ -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/ +``` + +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`. diff --git a/05-troubleshooting/ubuntu-dist-upgrade-repo-quarantine.md b/05-troubleshooting/ubuntu-dist-upgrade-repo-quarantine.md new file mode 100644 index 0000000..c3d9f9f --- /dev/null +++ b/05-troubleshooting/ubuntu-dist-upgrade-repo-quarantine.md @@ -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. diff --git a/SUMMARY.md b/SUMMARY.md index 17c8f71..057ed62 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -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)