Documentation of personal system configuration patterns and conventions.
• SOCKS5 and Service Tunnels — stunnel + s5pin.py for reaching remote services as local ports
• VNC Setup — x11vnc (display :0 mirror) and tightvncserver (display :1 virtual Xfce desktop) on pomelo
• VNC Alt+Tab / Window Switching — RealVNC SendSpecialKeys fix; ≤/≥ as XFCE window switcher (in progress)
• Infrastructure TLS / Stunnel PKI — mutual-TLS PKI for pomelo↔server tunnels; cert locations and renewal
Uses a modular approach to shell configuration rather than one monolithic .bashrc file.
The main ~/.bashrc loops over files in ~/.bashrcs/ and sources them IF they are executable.
Benefits:• Easy enable/disable: chmod +x to enable, chmod -x to disable• Modular: separate concerns into different files• No commenting out blocks of code• Clean organization
• No colour in the shell prompt or output — plain text only.• PS1 set via ~/.bashrcs/ps1 (executable). Current format: \D{%a %d %b} \u@\h:\w\$ — gives e.g. Fri 06 Mar john@pomelo:~/py/envoy$
File: ~/.bashrcs/api_keys
Contains: OpenAI API key exports
Permissions: 700 (read/write/execute for owner only)
Usage in cron: . /home/john/.bashrcs/api_keys — sources the file to load environment variables
Similar pattern exists at system level:
• Directory: /etc/rcs/• Run from: rc.local• Purpose: System-level initialization scripts• Same executable flag pattern for enable/disable
Cron runs with minimal environment — doesn't source ~/.bashrc or ~/.profile
Solution: Explicitly source needed config files in crontab:
. /home/john/.bashrcs/api_keys; python3 orchestrator.py
IMAP: Dovecot on cv.critchley.biz (port 993 IMAPS). Accessed locally via stunnel on port 143 (/etc/stunnel/stunnel.conf). Envoy connects to hostname imap (resolves to localhost).WebDAV maildir: webdav.critchley.biz/mail/john — used by both Envoy and popit3. Credentials in ~/.netrc.Local SMTP: Postfix on localhost. Some providers (Hotmail, Swisscom/bluewin) block SMTP from this host.
• envoy@critchley.biz — Envoy's IMAP account• jsr_critchley@hotmail.com — Outlook/Hotmail (SMTP: smtp-mail.outlook.com:587, OAuth2)• john.critchley@bluewin.ch — Swisscom/bluewin account• john@critchley.biz — Primary address
• Keep sensitive files (like api_keys) with 600 or 700 permissions• Use absolute paths in cron jobs• Source only what's needed for each cron job• Document which scripts depend on which config files
• Do NOT use the /z mount for this. /z is a davfs2 mount of the same share and is convenient for interactive poking about, but davfs2 caches writes locally and uploads them asynchronously. A write through /z can return success — and read back correctly through the same mount, because it is served from cache — while nothing has reached the server. Data lands on flush/unmount, not on close, and is lost if the mount or host goes first. Never use /z in a script, or anywhere the write is expected to have arrived. Use the client methods above against the URL, and verify with a HEAD afterwards.
• Files on gravlax that live under the WebDAV share (https://webdav.critchley.biz/, maps to /var/www/webdav/) should be read via a WebDAV client (python webdav4 or requests with ~/.netrc), not SSH — simpler and avoids SSH permission issues. Credentials live in ~/.netrc (machine webdav.critchley.biz, login john). Never hard-code the password.
• WB (writeback) is https://webdav.critchley.biz/writeback/ — where John posts command output/screenshots for Claude. To "check WB", list the share, take the newest file by modified time, and read it:
import netrc, io
from webdav4.client import Client
user, _, pw = netrc.netrc().authenticators("webdav.critchley.biz")
c = Client("https://webdav.critchley.biz/writeback/", auth=(user, pw))
files = [e for e in c.ls("/", detail=True) if e.get("type") == "file"]
latest = max(files, key=lambda e: e.get("modified") or "")
buf = io.BytesIO(); c.download_fileobj(latest["name"], buf)
print(latest["name"], buf.getvalue().decode())
• To save a remote file (e.g. from a DAP host) into a note, POST to the notes API from gravlax rather than reading it locally first; wrap the content with python json.dumps for correct escaping.
• xearth is built and installed at ~/bin/xearth (from ~/src/xearth-1.1/) but is NOT wired to the root window — xfdesktop still runs as XFCE session Client4. An attempt to replace xfdesktop with xearth failed (xearth ran but did not paint the root window; likely a compositing / xfwm4 issue). To re-attempt: set /sessions/Failsafe/Client4_Command via xfconf-query to xearth and killall xfdesktop; revert by setting it back to xfdesktop.
A WD My Cloud NAS is connected directly to pomelo's spare ethernet port (enp1s0, no switch) — no DHCP server on that link by default, so it never got an IPv4 address on its own. Fixed with: pomelo statically configured as 192.168.99.1/24 on enp1s0 (nmcli connection modify netplan-enp1s0 ipv4.method manual ipv4.addresses 192.168.99.1/24), plus a dedicated dnsmasq instance bound only to enp1s0 (--port=0 to disable its DNS side) handing out 192.168.99.50-150 with a fixed reservation for the NAS's MAC → 192.168.99.2 (--dhcp-host=<mac>,192.168.99.2). The NAS picks this up within seconds of the dnsmasq instance starting.
For the Mac (on the normal 192.168.0.x WiFi LAN) to reach the NAS on 192.168.99.x through pomelo: IP forwarding on pomelo (usually already on, from Docker), a static route on the Mac (route -n add 192.168.99.0/24 192.168.0.2), and — the part that actually blocked it — an explicit iptables -I DOCKER-USER -i wlo1 -o enp1s0 -j ACCEPT (and the reverse direction). Docker sets the FORWARD chain's default policy to DROP and routes everything through DOCKER-USER first; that chain is the right place for custom rules since Docker won't flush it on restart. Without this rule, ping/SMB to the NAS from the Mac just times out with no obvious cause.
Before connecting this NAS to any other network: disable its own DHCP server first (via its web admin UI). If it's running one and gets plugged into a LAN that already has a DHCP server (e.g. the home router), it becomes a rogue DHCP server and can hand out bad leases to other devices on that network.
Throughput: transfers to the NAS from the Mac ran at ~10-13MB/s, not gigabit — confirmed the bottleneck is the Mac's WiFi (802.11n, 270Mbps PHY), not pomelo's routing (enp1s0 to the NAS negotiates a full 1000Mb/s) or software forwarding overhead (negligible at these speeds). Plan: get a switch, wire pomelo + NAS + the Mac (via its unused en0 port) all into it — removes the WiFi hop entirely for that traffic. Not yet done (2026-09-15).