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:
parent
91455fac39
commit
1524ca66d5
6 changed files with 291 additions and 7 deletions
|
|
@ -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?
|
||||
|
||||
|
|
|
|||
98
05-troubleshooting/fantastical-mcp-permission-denied.md
Normal file
98
05-troubleshooting/fantastical-mcp-permission-denied.md
Normal 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
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
60
05-troubleshooting/python-smtplib-missing-rfc-headers.md
Normal file
60
05-troubleshooting/python-smtplib-missing-rfc-headers.md
Normal 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`.
|
||||
98
05-troubleshooting/ubuntu-dist-upgrade-repo-quarantine.md
Normal file
98
05-troubleshooting/ubuntu-dist-upgrade-repo-quarantine.md
Normal 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.
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue