WRKLOG-2026-06-22
Summary
Monday. Fill summary at end of day.
URGENT - All Domains
Carryover Backlog (CRITICAL)
| Task | Details | Origin | Days | Status |
|---|---|---|---|---|
MSCHAPv2 Migration Report |
Report due. 6-sheet Standard Report (exec summary, trend, waves, device detail, stale, policy match). Sheet 6 added 05-14: policy match by protocol for removal planning + anonymous identity validation. Migration window 2026-05-04 to 2026-05-30. ~6,227 devices, 5 waves. |
2026-04-17 |
83 |
P0 - DUE — run report this week |
Abnormal Security — ✅ COMPLETE |
CR-2026-05-07-abnormal-read-write. CAB approved 2026-05-12. Implemented successfully 2026-05-13. Read/write enabled for pilot group. Post-deployment validation pending. |
2026-05-07 |
63 |
✅ IMPLEMENTED — post-validation pending |
SIEM QRadar → Sentinel Migration |
Lead role. Monad console error RESOLVED 2026-05-12 — secrets configured in CHLA production tenant. ISE secure syslog integration in progress — cert imported, remote logging target configured, streaming errors under investigation. Blocking: DCR not created (Rule ID + Stream Name). Azure private network policy unresolved. Victor + Mauricio action. |
2026-04-10 |
90 |
P0 - ACTIVE — ISE syslog + DCR blocking |
Monad Pipeline Evaluation |
Sentinel output connector. Console error resolved. 3 of 6 values configured. Remaining: Endpoint URL (have it), Rule ID + Stream Name (need DCR). ISE Remote Logging Target configured 2026-05-18 — TLS cert imported, secure syslog target created. Streaming errors in Monad console under investigation. |
2026-03-11 |
120 |
P0 - ACTIVE — ISE integration in progress |
Guest Redirect ACL |
Guest redirect ACL work needed. Related to Mandiant remediation findings. |
2026-05-12 |
58 |
P0 - TODO |
ISE Patch 10 (CVE-2026-20147 CVSS 9.9) |
ISE 3.2 Patch 10. Supersedes Patch 9. 61 days on a CVSS 9.9 — schedule maintenance window. Write CR if needed. |
2026-03-12 |
119 |
P0 - OVERDUE — schedule immediately |
k3s NAT verification |
NAT rule 170 for 10.42.0.0/16 pod network - test internet connectivity. 64 days — test this week or defer to Q3. |
2026-03-09 |
122 |
P0 - BLOCKING — TRIAGE: schedule or defer |
Wazuh indexer recovery |
Restart pod after NAT confirmed working - SIEM visibility blocked. Blocked by k3s NAT — cannot proceed until above resolved. |
2026-03-09 |
122 |
P0 - Blocked by k3s |
Strongline Gateway VLAN fix |
8 devices in wrong identity group (David Rukiza assigned) |
2026-03-16 |
115 |
P0 - TODO |
TCP Clocks deployment |
ISE identity group validation, query outputs, comms with team. Active d001 data Apr 22-23. |
2026-04-22 |
78 |
P0 - ACTIVE |
IoT Dr. Kim — recurring |
Sleep study devices (Apr 15-16), watches recurrence (Apr 22). 5 incident versions in d001. Validate iPSK enrollment. |
2026-04-15 |
85 |
P0 - RECURRING |
Murus Portae (WAF) — Phase 0 |
FMC cert expired, ACP returns zero rules. d001: zone map, architecture D2, FMC API reference, ops script. |
2026-04-16 |
84 |
P0 - INVESTIGATING |
Vocera EAP-TLS Supplicant Fix |
~10 phones failing 802.1X, missing supplicant config. 61 days — schedule with clinical engineering team. |
2026-03-12 |
119 |
P1 - TODO — schedule |
ISE MnT Messaging Service |
Enable "Use ISE Messaging Service for UDP syslogs delivery". 61 days — low risk, schedule with ISE Patch 10 maintenance window. |
2026-03-12 |
119 |
P2 - BUNDLE with Patch 10 |
| Professional backlog remains critical. Check Days column for priorities. |
BLOCKERS — Fix Immediately
| Task | Details | Origin | Days | Impact |
|---|---|---|---|---|
Z Fold 7 Termux |
gopass and SSH not working |
2026-03-10 |
58 |
BLOCKER — Cannot access passwords on mobile |
gopass v3 organization |
Inconsistent structure, poor key-value usage |
2026-03-20 |
48 |
Inefficient password management, no aggregation |
Git history scrub — sensitive personal terms |
Plaintext references to personal legal matters in committed worklogs (WRKLOG-2026-03-14, WRKLOG-2026-04-18). Forward-fixed but old commits still contain strings. Requires |
2026-04-22 |
15 |
SECURITY — sensitive terms in public git history |
Runbook: Git History Scrub (d000 Personal Terms)
Problem: Two committed worklogs contained plaintext references to personal legal matters. The files have been edited (forward-fix), but git history retains the original text in prior commits.
Affected commits: Any commit touching these files:
# Identify affected commits
git log --oneline -- \
docs/modules/ROOT/pages/2026/03/WRKLOG-2026-03-14.adoc \
docs/modules/ROOT/pages/2026/04/WRKLOG-2026-04-18.adoc
Scrub procedure:
# 1. BEFORE: Full backup of the repo
cp -a ~/atelier/_bibliotheca/domus-captures ~/atelier/_bibliotheca/domus-captures.bak
# 2. Install git-filter-repo (if not present)
# Arch: pacman -S git-filter-repo
# pip: pip install git-filter-repo
# 3. Create expressions file for replacement
cat > /tmp/scrub-expressions.txt << 'EXPR'
regex:(?i)divorce==[REDACTED]
regex:(?i)dissolutio(?!n\.adoc\.age)==[REDACTED-LEGAL]
regex:(?i)iliana==[REDACTED-NAME]
regex:(?i)angulo-arreola==[REDACTED-NAME]
regex:legal-divorce-notes\.age==legal-notes.age
regex:1099-NEC-iliana==1099-NEC
EXPR
# 4. Verify before (dry run — count matches in history)
git log -p --all -S 'divorce' -- '*.adoc' | grep -c 'divorce' || echo "0 matches"
git log -p --all -S 'iliana' -- '*.adoc' | grep -c 'iliana' || echo "0 matches"
# 5. Run filter-repo (DESTRUCTIVE — rewrites all commit hashes)
git filter-repo --replace-text /tmp/scrub-expressions.txt --force
# 6. Verify after
git log -p --all -S 'divorce' -- '*.adoc' | grep -c 'divorce' || echo "0 matches — CLEAN"
git log -p --all -S 'iliana' -- '*.adoc' | grep -c 'iliana' || echo "0 matches — CLEAN"
# 7. Re-add remotes (filter-repo removes them)
git remote add origin git@github.com:<user>/domus-captures.git
# Add any other remotes (Gitea, etc.)
# 8. Force-push to all remotes (DESTRUCTIVE — overwrites remote history)
git remote | xargs -I{} git push {} main --force
# 9. Clean up
rm /tmp/scrub-expressions.txt
rm -rf ~/atelier/_bibliotheca/domus-captures.bak # only after verifying
Post-scrub checklist:
-
Backup created before running
-
git filter-repoinstalled -
Expressions file reviewed — no false positives (e.g., Don Quijote "Angulo el Malo" is in
segunda-parte/texto/texto-011.adoc— the regex targetsangulo-arreolaspecifically to avoid this) -
Dry-run counts match expectations
-
Filter-repo executed
-
Post-scrub verification shows 0 matches
-
Remotes re-added
-
Force-pushed to all remotes
-
Cloudflare Pages rebuild verified
-
Local clones on other machines re-cloned or
git fetch --all && git reset --hard origin/main -
Backup removed
URGENT - Requires Immediate Action
| Item | Details | Deadline | Status | Impact |
|---|---|---|---|---|
Housing Search |
Granada Hills area - apartments/rooms |
TBD |
In Progress |
Quality of life, commute |
2025 Tax — IRS Transcript Review |
MFJ filed 2026-04-22. Pull IRS Return Transcript to verify contents. Consult attorney re: Form 8857 (Innocent Spouse Relief). Details in encrypted case file. |
Before attorney meeting |
In Progress |
Financial — liability exposure. See encrypted D000 case file. |
Rack Relocation |
Physical move of server rack. CR written: CR-2026-04-18 (pending in infra-ops). Borg backup completed. VM XML dumps, switch save, shutdown/startup procedure documented. |
TBD |
Pending |
Infrastructure downtime — all services offline during move |
D000 Legal Planning |
Encrypted D000 case file. Open: |
Before Jan 2029 |
Active — escalating |
Life transition — see case file for details |
Credit Report Review |
Pull reports from all 3 bureaus via annualcreditreport.com. Verify no unknown joint accounts or debts. Credentials in gopass: |
TBD |
In Progress |
Financial discovery — FL-142 preparation |
Gopass Security Audit |
Rotate passwords on shared/known accounts. Add 2FA backup codes to |
TBD |
Pending |
Digital security — pre-filing preparation |
Subscription Audit |
Download 3 months bank/CC statements (Chase, NFCU, USAA). Identify all recurring charges. Cancel unnecessary. Document active subscriptions for FL-150. |
TBD |
Pending |
Financial — expense documentation |
401(k) Enrollment |
Enroll in CHLA 401(k) immediately. Post-separation contributions are 100% separate property. Reduces gross income for support calculations. Max 2026: $23,500/yr. |
In progress (started 5/4) |
In Progress |
Financial — support calculation + retirement |
URGENT — Performance Review Certifications
| Certification | Provider | Deadline | Status | Impact |
|---|---|---|---|---|
CISSP |
ISC² — Certified Information Systems Security Professional |
July 12, 2026 |
ACTIVE — Week 2 of 10 (Project) |
Required for performance review. 10-week accelerated plan. |
RHCSA 9 |
Red Hat Certified System Administrator |
Q3 2026 |
ACTIVE — 21-phase curriculum (Project) |
After CISSP. Required for performance review. |
| CISSP: 41 days remaining (exam July 12). Domain 1 study in progress. Schedule exam today (06-01). |
Early Morning - 5:30am
Regex Training (CRITICAL CARRYOVER)
-
Session 3 - Character classes, word boundaries
-
Practice drills from regex-mastery curriculum
-
Status: 52 days carried over (since 2026-03-16) — CRITICAL
| Regex training continues to slip. This is the foundation for all CLI mastery. |
Daily Notes
CVE-2026-55200 — libssh2 RCE (CVSS 9.2)
CVE-2026-55200 — libssh2 Out-of-Bounds Write (RCE)
Summary
CVE |
CVE-2026-55200 |
CVSS 4.0 |
9.2 CRITICAL (AV:N/AC:L/AT:P/PR:N/UI:N) |
CVSS 3.1 |
8.1 HIGH (AV:N/AC:H/PR:N/UI:N) |
Published |
2026-06-17 |
Affected |
libssh2 through 1.11.1 |
Fix |
Commit |
CWE |
CWE-680 (Integer Overflow to Buffer Overflow) |
Vector |
Remote — crafted SSH packet with oversized |
Mechanism: ssh2_transport_read() in transport.c fails to enforce upper bounds on the packet_length field. A remote attacker sends a crafted SSH packet with an excessively large packet_length, triggering an integer overflow that allocates less memory than expected, causing a heap buffer overflow. No authentication required — the attack occurs during the SSH handshake before authentication completes.
Scope Assessment — Does This Need a Project?
No. This is a triage-and-delegate, not a remediation project.
-
No patch exists yet. The fix commit is not in a tagged release. No distro has shipped an update. There is nothing to deploy.
-
Windows is not affected. Windows does not ship libssh2. Win32-OpenSSH, PuTTY, WinSCP — all use their own SSH implementations.
-
OpenSSH is not affected.
ssh,scp,sftp,sshd— all safe. Different codebase entirely. -
The attack requires outbound client connections to a malicious SSH server via libssh2. This is not remotely exploitable against our SSH listeners.
Your role: Triage analyst. Hand the server team the detection script + one-liner mitigation. They execute. You monitor for the distro patch. When it drops, it’s apt upgrade / dnf update / zypper update — routine patching, not an incident.
What Is and Is NOT Affected
| Tool / Platform | Affected? | Why |
|---|---|---|
Windows (all versions) |
NO |
Does not ship libssh2. Win32-OpenSSH, PuTTY, WinSCP use their own implementations. |
|
NO |
OpenSSH binaries — completely separate codebase from libssh2 |
|
NO |
OpenSSH server — does not use libssh2 |
|
NO |
HTTPS does not invoke libssh2 — only |
|
YES |
curl links libssh2 for SSH-based protocols |
|
NO |
git CLI shells out to OpenSSH binary, not libssh2 |
libgit2-based tools (some GUIs, Rust cargo) |
YES |
libgit2 links libssh2 directly for SSH transport |
|
YES |
nmap links libssh2 for SSH service probes |
Python paramiko |
NO |
paramiko uses libssh (different project) or pure Python — not libssh2 |
Ansible over SSH |
NO |
Uses paramiko or OpenSSH — not libssh2 |
WSL distros |
CHECK |
Each WSL instance has its own Linux packages — check each independently |
Detection Commands — By Distro
Every command below works over SSH. Run from your workstation or distribute the triage script.
# Version check
rpm -qa 'libssh2*'
# Example output: libssh2-1.11.0-2.el9.x86_64
# Reverse dependencies — what packages require libssh2
dnf repoquery --installed --whatrequires libssh2
# .so version string (works even if rpm metadata is stale)
strings /usr/lib64/libssh2.so.1 2>/dev/null | grep -oP 'libssh2_\K[0-9.]+'
# What binaries link it
for bin in /usr/bin/curl /usr/bin/git /usr/bin/nmap /usr/bin/ssh /usr/bin/scp; do
[ -x "$bin" ] && {
printf "%-20s " "$bin"
ldd "$bin" 2>/dev/null | grep -q libssh2 \
&& echo "LINKS libssh2 — AFFECTED" \
|| echo "safe — does NOT link libssh2"
}
done
# Check libcurl.so separately (anything using libcurl inherits)
ldd /usr/lib64/libcurl.so.4 2>/dev/null | grep libssh2
# Patch availability
dnf check-update libssh2
# Apply patch when available
sudo dnf update libssh2
# Version check
dpkg -l 'libssh2*' 2>/dev/null | awk '/^ii/{print $2, $3}'
# Example output: libssh2-1:amd64 1.11.0-4
# Reverse dependencies
apt rdepends --installed libssh2-1 2>/dev/null
# .so version string
strings /usr/lib/x86_64-linux-gnu/libssh2.so.1 2>/dev/null | grep -oP 'libssh2_\K[0-9.]+'
# What binaries link it
for bin in /usr/bin/curl /usr/bin/git /usr/bin/nmap /usr/bin/ssh /usr/bin/scp; do
[ -x "$bin" ] && {
printf "%-20s " "$bin"
ldd "$bin" 2>/dev/null | grep -q libssh2 \
&& echo "LINKS libssh2 — AFFECTED" \
|| echo "safe — does NOT link libssh2"
}
done
# Check libcurl
ldd /usr/lib/x86_64-linux-gnu/libcurl.so.4 2>/dev/null | grep libssh2
# Patch availability
sudo apt update && apt list --upgradable 2>/dev/null | grep libssh2
# Apply patch when available
sudo apt upgrade libssh2-1
# Version check
rpm -qa 'libssh2*'
# Example output: libssh2-1-1.11.0-150000.4.22.1.x86_64
# SUSE uses libssh2-1 as the package name (note the -1 suffix)
zypper info libssh2-1
# Reverse dependencies
zypper search --requires libssh2-1
# .so version string — SUSE puts libs in /usr/lib64
strings /usr/lib64/libssh2.so.1 2>/dev/null | grep -oP 'libssh2_\K[0-9.]+'
# What binaries link it
for bin in /usr/bin/curl /usr/bin/git /usr/bin/nmap /usr/bin/ssh /usr/bin/scp; do
[ -x "$bin" ] && {
printf "%-20s " "$bin"
ldd "$bin" 2>/dev/null | grep -q libssh2 \
&& echo "LINKS libssh2 — AFFECTED" \
|| echo "safe — does NOT link libssh2"
}
done
# Check libcurl
ldd /usr/lib64/libcurl.so.4 2>/dev/null | grep libssh2
# Patch availability
zypper list-updates | grep libssh2
# Apply patch when available
sudo zypper update libssh2-1
# SUSE-specific: check SUSE Security Announcements
# https://www.suse.com/security/cve/CVE-2026-55200.html
WSL Detection
WSL instances run their own Linux userland with their own package managers. Each WSL distro must be checked independently.
# Identify distro
cat /etc/os-release | awk -F= '/^PRETTY_NAME/{print $2}'
# Confirm you're in WSL (not bare metal)
grep -qi microsoft /proc/version && echo "WSL detected" || echo "Bare metal / VM"
# Then run the Debian/Ubuntu or RHEL commands above based on distro
# WSL-specific: check if Windows-side tools are aliased into WSL
which curl && file $(which curl)
# /usr/bin/curl = Linux curl (may link libssh2) — CHECK
# /mnt/c/.../curl.exe = Windows curl (WinHTTP, not libssh2) — SAFE
which ssh && file $(which ssh)
# /usr/bin/ssh = Linux OpenSSH — SAFE (not libssh2)
# /mnt/c/.../ssh.exe = Windows OpenSSH — SAFE (not libssh2)
# List all WSL instances
wsl -l -v
# Run version check across all WSL distros
foreach ($distro in (wsl -l -q | Where-Object { $_.Trim() -ne '' })) {
$d = $distro.Trim()
Write-Host "=== WSL: $d ==="
wsl -d $d -- bash -c '
echo " OS: $(cat /etc/os-release 2>/dev/null | grep PRETTY_NAME | cut -d= -f2 | tr -d \")"
VER=$(find /usr/lib /usr/lib64 /usr/lib/x86_64-linux-gnu -name "libssh2.so*" -type f 2>/dev/null | head -1 | xargs strings 2>/dev/null | grep -oP "libssh2_\K[0-9.]+")
if [ -z "$VER" ]; then
echo " libssh2: NOT INSTALLED — not affected"
elif [ "$(printf "%s\n" "1.11.1" "$VER" | sort -V | tail -1)" = "1.11.1" ]; then
echo " libssh2: $VER — VULNERABLE"
else
echo " libssh2: $VER — PATCHED"
fi
printf " curl: " && (ldd /usr/bin/curl 2>/dev/null | grep -q libssh2 && echo "links libssh2" || echo "clean")
'
Write-Host ""
}
# Windows does NOT ship libssh2 natively
# But some third-party tools may bundle libssh2.dll
Get-ChildItem -Path "C:\Program Files","C:\Program Files (x86)","C:\ProgramData" `
-Filter "libssh2*" -Recurse -ErrorAction SilentlyContinue |
Select-Object FullName, Length, LastWriteTime
# Check Git for Windows (uses its own OpenSSH, but verify)
& "C:\Program Files\Git\usr\bin\ssh.exe" -V 2>&1
# Should show OpenSSH — not libssh2
# Check if any installed app bundles a vulnerable libssh2
# If Get-ChildItem returns nothing — Windows is clean
Universal .so Detection (Any Distro)
When you don’t know the distro or package manager, these work everywhere:
# Find the library on disk — covers all lib paths
find / -name 'libssh2.so*' -type f 2>/dev/null
# Extract version from whatever .so you find
find / -name 'libssh2.so*' -type f 2>/dev/null \
| head -1 | xargs strings 2>/dev/null | grep -oP 'libssh2_\K[0-9.]+'
# Find every binary and library on the system that links it
find /usr/bin /usr/sbin /usr/lib /usr/lib64 /usr/lib/x86_64-linux-gnu \
-type f 2>/dev/null | xargs ldd 2>/dev/null | grep -B1 'libssh2' \
| awk '/^\// {gsub(/:$/,""); print}' | sort -u
# Find running processes with libssh2 loaded (no lsof needed)
grep -rl 'libssh2' /proc/*/maps 2>/dev/null \
| awk -F/ '{print $3}' | while read pid; do
printf " PID %-8s %s\n" "$pid" "$(cat /proc/$pid/comm 2>/dev/null)"
done | sort -u
Mitigation Commands (Pre-Patch, All Distros)
printf '%s\n' '--proto -sftp -scp' >> ~/.curlrc
# Verify
curl sftp://localhost 2>&1 | head -1
# Expected: "Protocol "sftp" not supported or disabled in libcurl"
# Undo after patching
sed -i '/--proto -sftp -scp/d' ~/.curlrc
printf '%s\n' '--proto -sftp -scp' | sudo tee -a /etc/curlrc
# Verify as another user
sudo -u nobody curl sftp://localhost 2>&1 | head -1
HOSTS=(
"server1.example.com"
"server2.example.com"
# Add all Linux hosts here
)
for host in "${HOSTS[@]}"; do
echo "=== $host ==="
ssh "$host" 'grep -q "proto.*-sftp" /etc/curlrc 2>/dev/null \
|| printf "%s\n" "--proto -sftp -scp" | sudo tee -a /etc/curlrc && echo " curlrc applied"' \
2>/dev/null || echo " FAILED"
done
foreach ($distro in (wsl -l -q | Where-Object { $_.Trim() -ne '' })) {
$d = $distro.Trim()
Write-Host "=== WSL: $d ==="
wsl -d $d -- bash -c 'printf "%s\n" "--proto -sftp -scp" >> ~/.curlrc && echo " curlrc applied for $(whoami)"'
}
# Install build deps first
# Debian/Ubuntu: sudo apt install cmake gcc libssl-dev zlib1g-dev
# RHEL/Rocky: sudo dnf install cmake gcc openssl-devel zlib-devel
# openSUSE: sudo zypper install cmake gcc libopenssl-devel zlib-devel
cd /tmp
git clone https://github.com/libssh2/libssh2.git
cd libssh2
git log --oneline -1 97acf3df # Verify fix commit exists
mkdir build && cd build
cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_SHARED_LIBS=ON \
-DCRYPTO_BACKEND=OpenSSL
make -j$(nproc)
# Verify the fix — bounds check should be present in transport.c
grep -A5 'packet_length' ../src/transport.c | head -20
# Back up current library
SOFILE=$(find /usr/lib /usr/lib64 /usr/lib/x86_64-linux-gnu \
-name 'libssh2.so.1.0.*' -type f 2>/dev/null | head -1)
sudo cp "$SOFILE" "${SOFILE}.bak.$(date +%F)"
# Install
sudo make install
sudo ldconfig
# Verify
strings /usr/lib64/libssh2.so.1 2>/dev/null | grep -oP 'libssh2_\K[0-9.]+'
strings /usr/lib/x86_64-linux-gnu/libssh2.so.1 2>/dev/null | grep -oP 'libssh2_\K[0-9.]+'
Infrastructure Triage Script (d001 — Run On Each Host)
This script auto-detects distro, package manager, and library paths. Works on RHEL, Debian, Ubuntu, openSUSE, SLES, and WSL.
#!/usr/bin/env bash
# cve-2026-55200-check.sh — portable triage for any Linux distro + WSL
# Usage: scp cve-2026-55200-check.sh user@host:/tmp/ && ssh user@host 'bash /tmp/cve-2026-55200-check.sh'
set -euo pipefail
HOST=$(hostname -f 2>/dev/null || hostname)
OS=$(cat /etc/os-release 2>/dev/null | awk -F= '/^PRETTY_NAME/{print $2}' | tr -d '"')
echo "=== CVE-2026-55200 triage: $HOST ==="
echo " OS: $OS"
# WSL detection
if grep -qi microsoft /proc/version 2>/dev/null; then
echo " Environment: WSL"
else
echo " Environment: Bare metal / VM"
fi
# 1. Version check — try all package managers
echo -e "\n[1] libssh2 version:"
if command -v dpkg &>/dev/null; then
dpkg -l 'libssh2*' 2>/dev/null | awk '/^ii/{print " "$2, $3}' || echo " Not installed (dpkg)"
elif command -v rpm &>/dev/null; then
rpm -qa 'libssh2*' 2>/dev/null | sed 's/^/ /' | grep . || echo " Not installed (rpm)"
elif command -v pacman &>/dev/null; then
pacman -Qi libssh2 2>/dev/null | awk '/^Version/' | sed 's/^/ /' || echo " Not installed (pacman)"
fi
# .so version string — works regardless of package manager
# Covers: /usr/lib64 (RHEL, SUSE), /usr/lib/x86_64-linux-gnu (Debian/Ubuntu), /usr/lib (Arch)
SOPATH=$(find /usr/lib /usr/lib64 /usr/lib/x86_64-linux-gnu -name 'libssh2.so*' -type f 2>/dev/null | head -1)
if [[ -n "$SOPATH" ]]; then
echo " .so path: $SOPATH"
strings "$SOPATH" 2>/dev/null | grep -oP 'libssh2_\K[0-9.]+' | head -1 | sed 's/^/ .so version: /'
else
echo " libssh2.so not found on disk"
fi
# 2. What links it
echo -e "\n[2] What uses libssh2:"
for bin in /usr/bin/curl /usr/bin/git /usr/bin/nmap /usr/bin/ssh /usr/bin/scp /usr/bin/sftp; do
if [ -x "$bin" ]; then
printf " %-20s " "$(basename "$bin")"
ldd "$bin" 2>/dev/null | grep -q libssh2 \
&& echo "LINKS libssh2 — AFFECTED" \
|| echo "does NOT link libssh2 — safe"
fi
done
# Check shared libraries — cover all distro lib paths
for lib in \
/usr/lib64/libcurl.so.4 \
/usr/lib/x86_64-linux-gnu/libcurl.so.4 \
/usr/lib/libcurl.so.4 \
/usr/lib64/libgit2.so* \
/usr/lib/x86_64-linux-gnu/libgit2.so* \
/usr/lib/libgit2.so*; do
[ -f "$lib" ] 2>/dev/null && {
printf " %-50s " "$lib"
ldd "$lib" 2>/dev/null | grep -q libssh2 \
&& echo "LINKS libssh2" || echo "clean"
}
done 2>/dev/null
# 3. Running processes with libssh2 loaded
echo -e "\n[3] Processes with libssh2 loaded:"
if command -v lsof &>/dev/null; then
PROCS=$(lsof 2>/dev/null | awk '/libssh2\.so/{print " "$1, "(PID "$2")"}' | sort -u)
[[ -n "$PROCS" ]] && echo "$PROCS" || echo " None"
else
# Fallback — works without lsof
FOUND=0
grep -rl 'libssh2' /proc/*/maps 2>/dev/null \
| awk -F/ '{print $3}' | sort -u | while read pid; do
printf " PID %-8s %s\n" "$pid" "$(cat /proc/$pid/comm 2>/dev/null)"
FOUND=1
done
[[ $FOUND -eq 0 ]] && echo " None"
fi
# 4. Active outbound SSH connections
echo -e "\n[4] Outbound SSH connections (port 22):"
CONNS=$(ss -tnp 2>/dev/null | awk '/:22 /{print " "$0}')
[[ -n "$CONNS" ]] && echo "$CONNS" || echo " None"
# 5. curl protocol check
echo -e "\n[5] curl SSH protocol support:"
if command -v curl &>/dev/null; then
PROTOS=$(curl -V 2>/dev/null | grep -oE 'sftp|scp' | tr '\n' ' ')
[[ -n "$PROTOS" ]] && echo " Enabled: $PROTOS" || echo " sftp/scp NOT in curl protocols"
# Check curlrc mitigation
for rc in ~/.curlrc /etc/curlrc; do
[ -f "$rc" ] && grep -q 'proto.*-sftp' "$rc" \
&& echo " $rc mitigation: ACTIVE" \
|| echo " $rc mitigation: NOT SET"
done
else
echo " curl not installed"
fi
# 6. Cron/systemd check — any automated curl sftp/scp usage?
echo -e "\n[6] Automated sftp/scp usage (cron, systemd):"
CRON_HITS=$(grep -rn 'curl.*\(sftp\|scp\)://' /etc/cron* /var/spool/cron 2>/dev/null | head -5)
[[ -n "$CRON_HITS" ]] && echo "$CRON_HITS" | sed 's/^/ /' || echo " None found in cron"
# 7. Patch availability
echo -e "\n[7] Patch availability:"
if command -v apt &>/dev/null; then
apt list --upgradable 2>/dev/null | grep -q libssh2 \
&& echo " PATCH AVAILABLE — run: sudo apt upgrade libssh2-1" \
|| echo " No Debian/Ubuntu patch yet"
elif command -v dnf &>/dev/null; then
dnf check-update libssh2 2>/dev/null | grep -q libssh2 \
&& echo " PATCH AVAILABLE — run: sudo dnf update libssh2" \
|| echo " No RHEL/Rocky patch yet"
elif command -v zypper &>/dev/null; then
zypper list-updates 2>/dev/null | grep -q libssh2 \
&& echo " PATCH AVAILABLE — run: sudo zypper update libssh2-1" \
|| echo " No SUSE patch yet"
elif command -v checkupdates &>/dev/null; then
checkupdates 2>/dev/null | grep -q libssh2 \
&& echo " PATCH AVAILABLE — run: sudo pacman -Syu" \
|| echo " No Arch patch yet"
else
echo " Unknown package manager — check manually"
fi
# 8. Verdict
echo -e "\n[8] VERDICT:"
VER=$(strings "${SOPATH:-/dev/null}" 2>/dev/null | grep -oP 'libssh2_\K[0-9.]+' | head -1)
if [[ -z "$SOPATH" || -z "$VER" ]]; then
echo " NOT AFFECTED — libssh2 not found on $HOST"
elif printf '%s\n' "1.11.2" "$VER" | sort -V | head -1 | grep -q "1.11.2"; then
echo " PATCHED — libssh2 $VER on $HOST (> 1.11.1)"
else
echo " VULNERABLE — libssh2 $VER on $HOST (≤ 1.11.1)"
echo " Action required: apply curlrc mitigation + patch when available"
fi
echo -e "\n=== End triage: $HOST ==="
Deployment Commands
HOSTS=(
# Work servers — add your actual hostnames
"rhel-server-01"
"ubuntu-jump-01"
"suse-db-01"
"debian-build-01"
)
for host in "${HOSTS[@]}"; do
scp /tmp/cve-2026-55200-check.sh "$host":/tmp/ 2>/dev/null \
&& ssh "$host" 'bash /tmp/cve-2026-55200-check.sh' 2>/dev/null \
|| echo "=== FAILED to reach $host ==="
echo
done | tee /tmp/cve-2026-55200-results-$(date +%F).txt
# Review results — just the verdicts
awk '/VERDICT/,/End triage/' /tmp/cve-2026-55200-results-$(date +%F).txt
foreach ($distro in (wsl -l -q | Where-Object { $_.Trim() -ne '' })) {
$d = $distro.Trim()
Write-Host "=== WSL: $d ==="
wsl -d $d -- bash /tmp/cve-2026-55200-check.sh
Write-Host ""
}
HOSTS=(
"rhel-server-01"
"ubuntu-jump-01"
"suse-db-01"
)
for host in "${HOSTS[@]}"; do
echo "=== $host ==="
scp /tmp/cve-2026-55200-check.sh "$host":/tmp/ 2>/dev/null
ssh "$host" '
# Apply mitigation (idempotent)
grep -q "proto.*-sftp" /etc/curlrc 2>/dev/null \
|| printf "%s\n" "--proto -sftp -scp" | sudo tee -a /etc/curlrc
echo " curlrc mitigation applied"
# Run triage
bash /tmp/cve-2026-55200-check.sh
' 2>/dev/null || echo " FAILED to reach $host"
echo
done | tee /tmp/cve-2026-55200-mitigated-$(date +%F).txt
Teams Message
Copy from /tmp/cve-2026-55200-teams-message.txt or from the block below:
CVE-2026-55200 — libssh2 RCE (CVSS 9.2) — Triage Complete
WHAT: Heap buffer overflow in libssh2 ≤ 1.11.1. RCE via crafted SSH packet
during handshake. No auth required.
NOT AFFECTED:
- Windows (all versions) — does not ship libssh2
- OpenSSH (ssh, scp, sftp, sshd) — different codebase
- PuTTY, WinSCP — own SSH implementations
- curl over HTTPS — only sftp:// and scp:// protocols trigger it
- Ansible — uses paramiko/OpenSSH, not libssh2
AFFECTED:
- Linux hosts with libssh2 ≤ 1.11.1 installed (most ship it as curl dep)
- Only when curl sftp:// or curl scp:// is used — not normal curl/ssh
- WSL instances — each has its own packages, check independently
CURRENT STATUS:
- No distro has shipped a patch yet (RHEL, Ubuntu, Debian, SUSE — all pending)
- Fix commit exists upstream but no tagged release
IMMEDIATE MITIGATION (one-liner, all Linux hosts):
echo '--proto -sftp -scp' | sudo tee -a /etc/curlrc
DETECTION:
Triage script attached — run on each host:
scp cve-2026-55200-check.sh user@host:/tmp/
ssh user@host 'bash /tmp/cve-2026-55200-check.sh'
Auto-detects distro (RHEL, Debian, Ubuntu, SUSE, WSL).
Checks version, maps dependencies, gives VULNERABLE/PATCHED/NOT AFFECTED verdict.
RISK CONTEXT:
This is NOT remotely exploitable against SSH servers. The attacker must get
the victim to connect outbound to a malicious SSH server via libssh2
(not OpenSSH). Practical risk is low unless hosts run automated curl sftp://
transfers to external endpoints.
ACTION ITEMS:
1. Server team: run triage script on Linux hosts + WSL instances
2. Server team: apply curlrc mitigation on affected hosts
3. All: monitor distro security trackers for patch
4. Cisco team: check PSIRT for ISE/FMC/ASA advisories
5. No Windows action needed
REFERENCES:
- NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-55200
- Fix commit: https://github.com/libssh2/libssh2/commit/97acf3dfda80c91c3a8c9f2372546301d4a1a7a8
- VulnCheck: https://www.vulncheck.com/advisories/libssh2-out-of-bounds-write-via-unchecked-packet-length-in-transport-c
- Red Hat: https://access.redhat.com/security/cve/CVE-2026-55200
- SUSE: https://www.suse.com/security/cve/CVE-2026-55200.html
- Debian: https://security-tracker.debian.org/tracker/CVE-2026-55200
- Ubuntu: https://ubuntu.com/security/CVE-2026-55200
- Cisco PSIRT: https://sec.cloudapps.cisco.com/security/center/publicationListing.x
Local Workstation Note (modestus-aw — Arch)
Personal workstation runs libssh2 1.11.1-1 — vulnerable but exposure is low. No curl sftp:// usage in history. OpenSSH used for all SSH sessions. Apply ~/.curlrc mitigation and monitor checkupdates for Arch patch.
Status
| Item | Status |
|---|---|
Triage complete |
■■■■■■■■■■ 10/10 |
Blast radius mapped |
■■■■■■■■■■ 10/10 |
Actual exposure assessed — LOW |
■■■■■■■■■■ 10/10 |
Triage script written (all distros) |
■■■■■■■■■■ 10/10 |
Teams message drafted |
■■■■■■■■■■ 10/10 |
Teams message sent |
□□□□□□□□□□ 0/10 — copy from |
curlrc mitigation on personal workstation |
□□□□□□□□□□ 0/10 — |
d001 server team runs triage script |
□□□□□□□□□□ 0/10 — hand off script |
WSL instances checked |
□□□□□□□□□□ 0/10 — hand off PowerShell command |
Cisco PSIRT check (ISE, FMC, ASA) |
□□□□□□□□□□ 0/10 |
Distro patches available |
□□□□□□□□□□ 0/10 — RHEL, Debian, Ubuntu, SUSE all pending |
YubiKey Provisioning — age Key Rotation
No — do not reveal the contents of ~/.age/identities.
Directory names and permissions are fine. This is safe:
ls -la ~/.age
find ~/.age -maxdepth 2 -type f -printf '%M %u %g %p\n'
This is not safe to paste:
cat ~/.age/identities/*
cat ~/.age/recipients/*
Especially never paste anything that starts like:
AGE-SECRET-KEY-...
Age’s model is: encrypt to recipients, decrypt with identities; an identity is the private side. age-plugin-yubikey adds identities stored on YubiKeys, so the YubiKey performs the private-key operation instead of leaving a normal private key as your daily-use decryptor. ([GitHub][1])
Safe plan
0. Freeze first
Before changing anything:
chmod 700 ~/.age ~/.age/identities ~/.age/recipients
find ~/.age -type f -exec chmod 600 {} \;
Do a metadata-only inventory:
find ~/.age -maxdepth 2 -type f -printf '%M %u %g %s %TY-%Tm-%Td %TH:%TM %p\n'
Safe. This shows filenames, sizes, and permissions, not secrets.
1. Check for dangerous raw private keys
This does not print the key. It only prints filenames that contain raw age private keys:
grep -RIl '^AGE-SECRET-KEY-' ~/.age/identities ~/.age/recipients 2>/dev/null
If anything appears under ~/.age/identities, treat it as crown-jewel material.
2. Set up the YubiKey safely first
Your earlier output showed FIDO PIN not set and PIV defaults still active. Fix those before using it for serious secrets.
ykman fido access change-pin
ykman piv access change-pin
ykman piv access change-puk
ykman piv access change-management-key --generate --protect
Yubico documents that YubiKey 5 supports separate applications including FIDO2, OATH, OpenPGP, OTP, PIV, and U2F, and ykman is the CLI for managing them. ([Yubico Documentation][2]) Your PIV warnings matter because the PIV PIN/PUK/management key are the administrative controls for PIV operations. ([Yubico Documentation][3])
3. Create a YubiKey-backed age identity
Install if needed:
sudo pacman -S age age-plugin-yubikey
Create a new identity file:
mkdir -p ~/.age/identities ~/.age/recipients
chmod 700 ~/.age ~/.age/identities ~/.age/recipients
age-plugin-yubikey --generate > ~/.age/identities/yubikey-main.txt
chmod 600 ~/.age/identities/yubikey-main.txt
Then get the recipient line without exposing private key material:
age-plugin-yubikey --list > ~/.age/recipients/yubikey-main.txt
chmod 600 ~/.age/recipients/yubikey-main.txt
gopass’s age backend docs also explicitly note that using age with a YubiKey requires age-plugin-yubikey, and that a decrypt may wait for your YubiKey touch if touch policy is enabled. ([GitHub][4])
4. Test on a fake file
Do not test first on your real password store.
tmp="$(mktemp -d)"
printf 'hello yubikey\n' > "$tmp/plain.txt"
age -R ~/.age/recipients/yubikey-main.txt \
-o "$tmp/plain.txt.age" \
"$tmp/plain.txt"
age -d -i ~/.age/identities/yubikey-main.txt \
"$tmp/plain.txt.age"
Expected output:
hello yubikey
5. Add backup before migration
Do not make one Nano your only decryptor.
Minimum safe recipient set:
daily YubiKey recipient
offline recovery age recipient
existing old age recipient temporarily
Better:
daily YubiKey recipient
backup YubiKey recipient
offline recovery age recipient
Generate an offline recovery identity and do not keep it on the daily machine:
age-keygen -o /tmp/offline-recovery-age-key.txt
grep '^# public key:' /tmp/offline-recovery-age-key.txt
The line after # public key: is safe to use as a recipient. The file itself is private.
6. Re-encrypt gradually
For standalone age files, re-encrypt one test secret first. Pattern:
old_id="$HOME/.age/identities/personal.key"
yk_id="$HOME/.age/identities/yubikey-main.txt"
yk_recips="$HOME/.age/recipients/yubikey-main.txt"
age -d -i "$old_id" secret.txt.age > /tmp/secret.txt
age -R "$yk_recips" \
-r 'age1_YOUR_OFFLINE_RECOVERY_RECIPIENT_HERE' \
-o /tmp/secret.txt.age.new \
/tmp/secret.txt
age -d -i "$yk_id" /tmp/secret.txt.age.new >/dev/null &&
mv /tmp/secret.txt.age.new secret.txt.age
shred -u /tmp/secret.txt 2>/dev/null || rm -f /tmp/secret.txt
Do this slowly. Verify each decrypt before replacing.
7. For gopass
First identify backend and recipients without printing secrets:
gopass config
gopass recipients
gopass mounts
If your gopass store is already using age, follow the gopass age backend flow and add the YubiKey identity/recipient. If it is GPG-backed, do not mix changes casually; migrate one mount/store deliberately.
Safe approach:
1. Export/backup current gopass store.
2. Add YubiKey-backed age recipient.
3. Re-encrypt store.
4. Test show/copy on several entries.
5. Sync.
6. Only then remove old daily-use disk identity.
8. Remove the old disk key from daily use only after testing
Do not delete it immediately.
Move it to an offline encrypted backup:
mkdir -p ~/age-key-retirement
chmod 700 ~/age-key-retirement
mv ~/.age/identities/personal.key ~/age-key-retirement/personal.key.RETIRED
chmod 600 ~/age-key-retirement/personal.key.RETIRED
Then test that your normal workflow still works with only the YubiKey identity.
Final target:
~/.age/identities/yubikey-main.txt okay, daily use
~/.age/recipients/yubikey-main.txt okay
raw AGE-SECRET-KEY file not on daily machine
gopass encrypted store still on disk/Git
YubiKey required to decrypt
The simple rule: recipient files can be public-ish, identity files are private, and anything containing AGE-SECRET-KEY must not be revealed.
[1]: github.com/str4d/age-plugin-yubikey?utm_source=chatgpt.com "str4d/age-plugin-yubikey" [2]: docs.yubico.com/hardware/yubikey/yk-tech-manual/yk5-apps.html?utm_source=chatgpt.com "Protocols and Applications — YubiKey Technical Manual …" [3]: docs.yubico.com/yesdk/users-manual/application-piv/pin-puk-mgmt-key.html?utm_source=chatgpt.com "The PIV PIN, PUK, and management key" [4]: github.com/gopasspw/gopass/blob/master/docs/backends/age.md?utm_source=chatgpt.com "gopass/docs/backends/age.md at master"
Monad Pipeline v3 Deployment
Monad Pipeline v3 Deployment
Kenneth lifted billing 3T/3O limit (2026-06-19). Full v3 graph deployed via PATCH to chlxsyslog-pipeline (2016ea3e). 18 nodes, 8 transforms, 17 edges. ISE 3-tier classification, ASA perimeter routing, per-source enrichment.
Deployment Sequence
dsource d001 lab/network
API="${MONAD_API_URL}/v2/${MONAD_ORG_ID}"
curl -sS -X POST -H "X-API-Key: ${MONAD_API_KEY}" -H "Content-Type: application/json" \
-d @/tmp/chlxsyslog-v3-full.json \
"${MONAD_API_URL}/v2/${MONAD_ORG_ID}/pipelines" | \
tee /tmp/monad-v3-response.json | \
jq '{id, name, nodes: (.nodes | length), edges: (.edges | length)}'
id: 7244d3bb-c785-4318-87a9-8376abb84ad1 name: chlxsyslog-pipeline-v3 nodes: 18, edges: 17
bash data/d001/projects/siem-qradar-to-sentinel/scripts/verify-v3.sh | \
tee /tmp/monad-v3-verify.txt
✓ asa-vpn-to-perimeter → hot-sentinel-perimeter ✓ asa-failover-to-perimeter → hot-sentinel-perimeter ✓ asa-acl-to-perimeter → hot-sentinel-perimeter Nodes: 18 (expected 18) Edges: 17 (expected 17) ✓ ALL CHECKS PASSED
v3 pipeline created but received zero traffic. v2 re-enabled: traffic flows immediately. Monad input components bind per pipeline instance — POST does not inherit TLS connection.
V2_PID="2016ea3e-3571-4e42-aacf-b459effc2a64"
curl -sS -X PATCH -H "X-API-Key: ${MONAD_API_KEY}" -H "Content-Type: application/json" \
-d @/tmp/chlxsyslog-v2-patch.json \
"${MONAD_API_URL}/v2/${MONAD_ORG_ID}/pipelines/${V2_PID}" | \
jq '{id, name, nodes: (.nodes | length), edges: (.edges | length)}'
id: 2016ea3e, nodes: 18, edges: 17 — traffic flowing
V3_PID="7244d3bb-c785-4318-87a9-8376abb84ad1"
jq '.enabled = false' /tmp/monad-v3-response.json > /tmp/monad-v3-disable.json
curl -sS -X PATCH -H "X-API-Key: ${MONAD_API_KEY}" -H "Content-Type: application/json" \
-d @/tmp/monad-v3-disable.json \
"${MONAD_API_URL}/v2/${MONAD_ORG_ID}/pipelines/${V3_PID}" | \
jq '{id, name, enabled}'
Transform Fixes
Three transforms had tag mismatches discovered during audit.
bash data/d001/projects/siem-qradar-to-sentinel/scripts/monad-fix-transforms.sh fix-ise-cold
bash data/d001/projects/siem-qradar-to-sentinel/scripts/monad-fix-transforms.sh fix-win-tier
bash data/d001/projects/siem-qradar-to-sentinel/scripts/monad-fix-transforms.sh fix-asa-acl
bash data/d001/projects/siem-qradar-to-sentinel/scripts/monad-fix-transforms.sh audit-all | tee /tmp/monad-transform-audit.txt
| Transform | Fix | Before → After | Status |
|---|---|---|---|
ISE cold |
environment |
lab → production |
PASS |
Windows |
retention_tier, severity_tier |
hot/high → cold/medium |
PASS |
ASA ACL |
retention_tier, severity_tier |
cold/medium → hot/high |
PASS |
Final Transform Audit
| Transform | source_type | environment | retention | severity |
|---|---|---|---|---|
ise-hot |
ise-auth |
production |
hot |
high |
ise-cold |
ise |
production |
cold |
— |
asa-drop-noise |
— |
— |
— |
— |
asa-vpn-extract |
asa-vpn |
production |
hot |
high |
asa-acl-extract |
asa-acl |
production |
hot |
high |
asa-failover-extract |
asa-failover |
production |
hot |
high |
win-logon-extract |
windows-security |
production |
cold |
medium |
switch-extract |
switch |
production |
cold |
medium |
Traffic Verification (post-PATCH)
bash data/d001/projects/siem-qradar-to-sentinel/scripts/monad-ops.sh traffic v2
input-syslog in=3991 out=4619 ise-hot-extract in=203 out=203 ise-hot-to-sentinel in=203 out=203 ise-cold-extract in=11 out=11 ise-cold-to-datalake in=11 out=11 ise-noise-to-discard in=65 out=65 win-logon-extract in=1924 out=1924 catch-all-to-datalake in=531 out=531 asa-drop-noise in=30 out=30
Live Event Observation
bash data/d001/projects/siem-qradar-to-sentinel/scripts/monad-ops.sh observe v2 input-syslog
bash data/d001/projects/siem-qradar-to-sentinel/scripts/monad-ops.sh observe v2 ise-hot-extract
bash data/d001/projects/siem-qradar-to-sentinel/scripts/monad-ops.sh observe v2 win-logon-extract
Confirmed live: ISE auth (CISE_Authentication_Flow, CISE_RADIUS_Accounting), Windows Security logon events, Monad agent MetricsExtension heartbeats in catch-all.
Pipeline Final State
| Pipeline | Status | Purpose |
|---|---|---|
chlxsyslog-pipeline (2016ea3e) |
Enabled — v3 graph via PATCH |
ISE 3-tier + ASA perimeter + per-source enrichment |
chlxsyslog-pipeline-v3 (7244d3bb) |
Disabled — orphan |
Reference only |
saas-pipeline (7c7a96b0) |
Enabled |
Mind DLP hot/cold |
Lesson Learned
Monad input components bind per pipeline instance. POST creates a new instance with no TLS connection inheritance. To modify a live pipeline: PATCH the existing pipeline, do not POST a replacement.
Scripts Created
| Script | Purpose |
|---|---|
|
Pipeline operations toolkit (pipelines, traffic, edges, outputs, observe, deploy) |
|
3-freeway output conversion (dev-null → azure-monitor when DCR values arrive) |
|
Transform tag fixes + full audit |
|
v3 graph validation (ASA routing, node count, edge count) |
|
DCR values template for Victor + Mauricio |
Next Steps
-
Verify ASA output nodes receiving traffic (Monday — no ASA traffic on Sunday)
-
Investigate catch-all content (531 records — Monad agent metrics, confirm no misrouted logs)
-
Victor + Mauricio: create 3 DCRs (identity, perimeter, datalake) for Sentinel output
-
When DCR values arrive:
bash scripts/monad-output-config.sh check-prereqs→build-payloads→update-outputs -
d001 close before commit
Monad Pipeline — Clean Graph Architecture
Monad Pipeline — Clean Graph Architecture
Restructured chlxsyslog-pipeline (2016ea3e) for clarity. 1 edge per log source from CHLXSYSLOG01. ISE fans out via router to hot/cold/discard. Engineers and management see a clean tree, not spaghetti.
Architecture
CHLXSYSLOG01 (input-syslog)
│
├── [ise-route] → ise-router (pass-through)
│ ├── [ise-hot-route] → ise-hot-extract → hot-sentinel-identity
│ │ 8 categories: Passed_Auth, Failed_Attempts, Auth_Flow,
│ │ RADIUS_Accounting, TACACS_Accounting, Administrator,
│ │ Threat_Centric, Guest
│ │
│ ├── [ise-cold-route] → ise-cold-extract → cold-datalake
│ │ 9 categories: Profiler, Posture, Policy_Diagnostics,
│ │ Identity_Stores, AD_Connector, Administrative,
│ │ PassiveID, ACI, MyDevices
│ │
│ └── [ise-noise-route] → discarded
│ 10 categories: System_Statistics, Internal_Operations,
│ External_MDM, Internal_MDM, Distributed_Management,
│ Licensing, RADIUS_Diagnostics, TACACS_Diagnostics,
│ MDM_Diagnostics, IPSec
│
├── [asa-route] → asa-extract → hot-sentinel-perimeter
│ condition: msg contains "%ASA-" OR hostname starts_with "USC-TEST"
│
├── [win-route] → win-logon-extract → cold-datalake
│ condition: app-name contains "MSWinEventLog"
│
├── [switch-route] → switch-extract → cold-datalake
│ condition: hostname contains "10.193.144.249"
│
└── [all-other-logs] → cold-datalake (catch-all)
condition: NOR (excludes CISE_, MSWinEventLog, %ASA-, USC-TEST, switch IP)
3 Freeways (Output Destinations)
| Freeway | Sources | Component ID | Type |
|---|---|---|---|
hot-sentinel-identity |
ISE auth events (8 categories) |
|
dev-null (→ Sentinel DCR when ready) |
hot-sentinel-perimeter |
ASA VPN/ACL/failover |
|
dev-null (→ Sentinel DCR when ready) |
cold-datalake |
ISE cold + Windows + Switch + catch-all |
|
dev-null (→ Sentinel DCR when ready) |
discarded |
ISE internals (10 categories) |
|
dev-null (permanent) |
Before / After
| Metric | Before | After |
|---|---|---|
Nodes |
18 |
14 |
Edges |
17 |
13 |
Edges from input |
9 (3 duplicate ASA) |
5 (1 per source) |
ISE paths from input |
3 |
1 (via router) |
ASA paths |
3 (same condition) |
1 |
Verification
bash data/d001/projects/siem-qradar-to-sentinel/scripts/monad-ops.sh graph v2
bash data/d001/projects/siem-qradar-to-sentinel/scripts/monad-ops.sh edges v2
bash data/d001/projects/siem-qradar-to-sentinel/scripts/monad-ops.sh traffic v2
bash data/d001/projects/siem-qradar-to-sentinel/scripts/monad-ops.sh outputs v2
bash data/d001/projects/siem-qradar-to-sentinel/scripts/monad-clean-graph-v2.sh verify
Transform Tags (per source)
| Transform | source_type | retention | severity | environment |
|---|---|---|---|---|
ise-hot-extract |
ise-auth |
hot |
high |
production |
ise-cold-extract |
ise |
cold |
— |
production |
ise-router |
— |
— |
— |
— (pass-through, 0 ops) |
asa-extract |
asa-vpn |
hot |
high |
production |
win-logon-extract |
windows-security |
cold |
medium |
production |
switch-extract |
switch |
cold |
medium |
production |
Scripts
| Script | Purpose |
|---|---|
|
Show current node slugs and edge names |
|
Per-node ingress/egress counts |
|
Edge routing table |
|
Output component mapping |
|
Live stream events from a node |
|
Full verification suite |
|
Transform tag audit |
|
3-freeway output type check |
Show Colleagues
Build the PDF from the d001 assembler:
bash data/d001/projects/siem-qradar-to-sentinel/scripts/build-monad-doc.sh
Or export just this daily note:
make pdf FILE=docs/modules/ROOT/pages/2026/06/WRKLOG-2026-06-22.adoc
For a live demo: load creds (ds d001 lab/network), then run monad-ops.sh traffic v2 and monad-clean-graph-v2.sh verify to show the architecture in real time.
Monad KV Lookup + Security Audit
Monad KV Lookup + Security Audit
KV Lookup Store — Created
Reference pipeline for dynamic ISE category→tier routing. Eliminates hardcoded CISE_ edge conditions — new categories added to KV table, not pipeline graph.
bash data/d001/projects/siem-qradar-to-sentinel/scripts/monad-kv-setup.sh create-output
bash data/d001/projects/siem-qradar-to-sentinel/scripts/monad-kv-create-input.sh
bash data/d001/projects/siem-qradar-to-sentinel/scripts/monad-kv-setup.sh create-pipeline
bash data/d001/projects/siem-qradar-to-sentinel/scripts/monad-kv-setup.sh populate
bash data/d001/projects/siem-qradar-to-sentinel/scripts/monad-kv-setup.sh verify
| Component | ID | Type |
|---|---|---|
kv-ise-tiers (output) |
|
kv-lookup |
ise-tier-reference (input) |
|
monad-http |
ise-tier-reference (pipeline) |
|
2 nodes, 1 edge |
hot (8): Passed_Authentications, Failed_Attempts, Authentication_Flow,
RADIUS_Accounting, TACACS_Accounting, Administrator,
Threat_Centric, Guest
cold (9): Profiler, Posture, Policy_Diagnostics, Identity_Stores,
AD_Connector, Administrative, PassiveID, ACI, MyDevices
discard (10): System_Statistics, Internal_Operations, External_MDM,
Internal_MDM, Distributed_Management, Licensing,
RADIUS_Diagnostics, TACACS_Diagnostics, MDM_Diagnostics, IPSec
| What we tried | Error | Correct field |
|---|---|---|
|
|
|
|
|
|
|
|
|
Pending — KV Enrichment Integration
-
Attach KV Lookup enrichment node to
chlxsyslog-pipeline(API or GUI — ask Kenneth) -
Join key:
app-name→categoryfield in KV store -
Test: observe
ise-enrichnode, confirmtierfield appears on events -
Replace 27
CISE_edge conditions with 3tierequals checks -
Set up cron to re-run
monad-kv-setup.sh populatebefore 24h TTL expires
Security Audit — Shell Expansion Vulnerability
Kenneth (Monad) identified potential command injection via unquoted heredocs during screen share. Audit confirmed 4 vulnerable files.
Attack Vector
Unquoted heredocs (<< EOF instead of << 'EOF') allow shell expansion inside the document body. If a variable contains backticks or $() syntax — for example, from an API response stored in a variable — the shell executes it during heredoc expansion.
# DANGEROUS — variable expansion active inside heredoc
api_response=$(curl -s ... | jq -r '.id')
cat > /tmp/payload.json << EOF
{"component_id": "${api_response}"}
EOF
# If api_response contains: test$(rm -rf /)
# The command inside $() EXECUTES
# SAFE — jq -n --arg escapes all values, no shell expansion possible
api_response=$(curl -s ... | jq -r '.id')
jq -n --arg id "$api_response" \
'{component_id: $id}' > /tmp/payload.json
Findings
| File | Vulnerability | Severity | Fix Applied |
|---|---|---|---|
|
3 unquoted heredocs interpolating DCE URLs, Rule IDs, client_secret |
HIGH |
Replaced with |
|
Unquoted heredoc with function parameters ( |
MEDIUM |
Replaced with |
|
Unquoted heredoc with API-sourced component IDs ( |
MEDIUM |
Replaced with |
|
Unquoted |
MEDIUM |
Quoted heredoc + |
Rule
Never interpolate variables inside heredocs for JSON construction. Use jq -n --arg — it escapes values properly and cannot execute shell commands regardless of input content.
cat > /tmp/payload.json << EOF
{"id": "${api_var}", "secret": "${credential}"}
EOF
jq -n --arg id "$api_var" --arg secret "$credential" \
'{id: $id, secret: $secret}' > /tmp/payload.json
Files verified safe (no issues)
21 other scripts in the project use quoted heredocs (<< 'EOF') or jq -n correctly. All curl commands properly quote variable expansions. All jq pipelines handle output safely.
INC — Transform Operations Wiped by Rename
INC — Transform Operations Wiped by Rename
Incident
All 6 pipeline transform operations were wiped to 0 ops by monad-rename-transforms.sh. The script sent a PATCH with {name, description} only — no config field. Monad’s transform PATCH replaces the entire object. Missing config = all operations deleted.
Timeline
| Time | Event |
|---|---|
~12:20 |
|
~12:23 |
Dashboard showed all transforms with 0 operations |
~12:25 |
Root cause identified — PATCH payload missing |
~12:27 |
|
~12:27 |
Verified: ise-enrich=3, ise-hot=2, ise-cold=1, asa-enrich=5, win-enrich=5, switch-enrich=8 |
Root Cause
The rename script built PATCH payloads with only name and description:
# WRONG — missing config, wipes all operations
jq -n --arg n "$new" --arg d "$desc" \
'{name: $n, description: $d}' > /tmp/monad-rename.json
Monad API constraint (documented in session-2026-06-18.adoc, constraint #5): Transform PATCH replaces entire object — always include name and description. This also means always include config.
Fix Applied
The rename script now GETs the existing config before PATCHing:
# SAFE — preserves existing config.operations
curl -sS -m 30 -H "X-API-Key: ${MONAD_API_KEY}" \
"${MONAD_API_URL}/v1/${MONAD_ORG_ID}/transforms/${id}" | \
jq --arg n "$new" --arg d "$desc" \
'{name: $n, description: $d, config: .config}' > /tmp/monad-rename.json
Prevention
Rule: Every Monad transform PATCH must include the full object — name, description, AND config.operations. A PATCH without config is destructive.
# 1. GET current state
curl -sS -H "X-API-Key: ${MONAD_API_KEY}" \
"${MONAD_API_URL}/v1/${MONAD_ORG_ID}/transforms/${ID}" | \
jq '{name, ops: (.config.operations | length)}'
# 2. Build payload preserving config
curl -sS -H "X-API-Key: ${MONAD_API_KEY}" \
"${MONAD_API_URL}/v1/${MONAD_ORG_ID}/transforms/${ID}" | \
jq --arg n "new-name" '{name: $n, description: .description, config: .config}' \
> /tmp/patch.json
# 3. Verify payload has operations
jq '.config.operations | length' /tmp/patch.json
# Must be > 0 for transforms with operations
# 4. Apply
curl -sS -X PATCH ... -d @/tmp/patch.json
# 5. Verify after
curl -sS ... | jq '{name, ops: (.config.operations | length)}'
Severity
P2 — Data loss (recoverable). Operations were known from scripts executed minutes before. No data loss in pipeline traffic — transforms with 0 ops pass data through without enrichment, so events were still routed correctly. Enrichment tags were missing for ~5 minutes.
Related
-
Constraint #5 from
session-2026-06-18.adoc: Transform PATCH replaces entire object -
Kenneth’s security audit (same session): shell expansion in heredocs — same root cause pattern of insufficient verification before destructive API calls
Monad Script Architecture — CLI Toolkit Path
Monad Script Architecture — CLI Toolkit Path
Current State: 14 Shell Scripts
| Script | Purpose |
|---|---|
|
Pipeline operations (graph, traffic, edges, outputs, observe, deploy) |
|
3-freeway output conversion (dev-null → azure-monitor) |
|
Transform tag fixes + full audit |
|
Trim ASA/Windows/Switch to lean pattern |
|
Trim ISE hot/cold to tier-specific only |
|
ISE enrich — facility, environment, drop pri |
|
ISE router shared enrichment setup |
|
Clean transform names (ise-hot, asa-enrich, etc.) |
|
ASA collapse (18→13 nodes) |
|
1 edge per source + ISE router fan-out |
|
Pipeline + output descriptions |
|
KV lookup store + reference pipeline + populate |
|
KV output creation (API field discovery) |
|
HTTP input creation (API field discovery) |
|
v3 graph validation (ASA routing, node count) |
Pattern
Every script follows the same skeleton:
check_creds # dsource d001 lab/network
build_payload # jq -n --arg (injection-safe, never heredoc)
curl_with_timeout # -m 30, save to output/
parse_response # jq, tee to timestamped file
Why This Scales
-
Credential separation —
dsourceloads env vars, scripts consume${VARS}, no hardcoded secrets -
Payload safety —
jq -n --argfor all JSON construction, no shell expansion possible -
Audit trail — every API response saved to
output/with timestamps -
Subcommand dispatch —
casepattern maps directly to Go cobra commands -
Reusable across APIs — swap
MONAD_*forISE_*,FMC_*,CLAROTY_*— same structure
Evolution Path
Bash scripts (now) → prove API patterns, validate payloads, document edge cases → discover errors (output_type vs type, input binding, heredoc injection) Go CLI (when API interface stabilizes) → each cmd_* function → cobra command → each jq -n --arg → Go struct + json.Marshal → each curl → http.Client → same architecture, compiled binary, tab completion, cross-platform
ISE ERS and DataConnect are stable — ready for Go port. Monad KV enrichment is still being discovered — stays in bash until the interface solidifies.
Pending Architecture: 3-Tier Fan-Out for All Sources
ISE has the full hot/cold/discard fan-out via ise-enrich router. ASA, Windows, and Switch currently go to a single output each. The architecture should support 3-tier routing for every source — even if all traffic initially goes one direction. When sub-classification is needed, only edge conditions change, not the graph structure.
Monad Pipeline — Phase 2 Roadmap
Monad Pipeline — Phase 2 Roadmap
Phase 1 complete: clean graph (14 nodes, 13 edges), ISE 3-tier fan-out, lean transforms, KV lookup store, security audit. Phase 2: source-specific enrichment and 3-tier fan-out for all sources.
ASA Observations
hostname: USC-TEST-RAVPN-5515 msg: %ASA-1-717055: The <ID> certificate in the trustpoint <TrustPoint_Test> has expired.
-
Severity encoded in msg:
%ASA-{severity}-{msgid}(1=critical, 6=info) -
Message ID determines event type: 717055=cert expired, 113039=VPN session, 106023=ACL deny
-
ASA logging confirmed:
logging host FW_MGMT 10.248.13.254, trapinformational(0-6) -
Low volume on Sunday — Monday will show VPN/ACL/failover traffic
Parse %ASA-X-NNNNNN from msg: - Extract severity (1-7) → dynamic severity_tier (not hardcoded "high") - Extract message ID → route to hot/cold/discard based on ID ranges - VPN sessions (113xxx, 716xxx) → hot - ACL denies (106xxx) → hot - Connection lifecycle (302xxx) → discard - Cert/config (717xxx) → cold
Windows Observations
hostname: L-N41999.LA.AD.CHLA.ORG msg: ...4670...Success Audit... (Event ID 4670 — object permissions changed) msg: ...4688...Success Audit... (Event ID 4688 — process creation) msg: ...4673...Failure Audit... (Event ID 4673 — privileged service called)
-
Event ID is tab-delimited (position 4 in
#011-separated msg) -
Success AuditvsFailure Auditvisible in raw msg -
Only 1 host sending:
L-N41999.LA.AD.CHLA.ORG
Parse Event ID from tab-delimited msg: - 4625 (failed logon) → hot - 4624 (successful logon) → cold - 4688 (process creation) → cold - 4673 (Failure Audit on privileged service) → hot - 4670 (object permissions) → cold
Switch Observations
-
Zero traffic on Sunday — switch may not be generating events
-
switch_nameandlocationare hardcoded for Duque-TrackA-9300 — breaks when second switch onboarded -
Phase 2: derive switch identity from hostname field, not static tag
Catch-All Observations
-
2008 records but 0 live events observed — historical Monad agent
MetricsExtensionheartbeats -
NOR exclusion is working — all classified sources excluded correctly
Phase 2 Tasks
-
Test Monad
jqtransform operation — parse%ASA-X-severity from msg field -
Test Monad
rename_key/create_key_value_if_key_valuefor conditional tagging -
Build ASA 3-tier fan-out: asa-enrich → hot (VPN/ACL) / cold (cert/config) / discard (connection lifecycle)
-
Build Windows 3-tier fan-out: win-enrich → hot (failed logon, priv escalation) / cold (success logon, process) / discard (noise)
-
Build Switch fan-out: derive switch identity from hostname, remove hardcoded switch_name/location
-
Attach KV Lookup enrichment to chlxsyslog-pipeline (API — ask Kenneth)
-
Replace 27 ISE edge conditions with 3 tier-based conditions via KV enrichment
-
Monday: verify ASA volume with weekday traffic, sample VPN/ACL/failover events
-
Set up cron for KV store repopulation (24h TTL)
VM Inventory and State
sudo virsh list --all
sudo virsh list --all | awk 'NR>2 {print $2": "$3}'
sudo virsh list --all | grep -i k3s
sudo virsh list --all | grep -i WLC
sudo virsh list --all | grep -i ipsk
sudo virsh list --all | grep -i ise
sudo virsh list --all --autostart
sudo virsh list --all --autostart | awk 'NR>2 {print $2}'
sudo virsh dominfo <vm>
sudo virsh domstate <vm>
# CPU time delta trick — confirm VM is actively running
t1=$(sudo virsh dominfo <vm> | awk '/CPU time/{print $3}')
sleep 5
t2=$(sudo virsh dominfo <vm> | awk '/CPU time/{print $3}')
echo "CPU time delta: $(echo "$t2 - $t1" | bc)s"
sudo virsh vcpuinfo <vm>
# Compare two VMs side-by-side
sudo virsh vcpuinfo vyos-01; sudo virsh vcpuinfo home-dc01 2>/dev/null
VM Lifecycle
sudo virsh start <vm>
sudo virsh destroy <vm> # hard stop (SIGKILL equivalent)
sudo virsh shutdown <vm> # ACPI graceful shutdown
sudo virsh reboot <vm> # ACPI reboot
sudo systemctl restart libvirtd && sudo virsh destroy <vm> && sudo virsh start <vm>
sudo virsh define /path/to/vm.xml
sudo virsh undefine <vm>
sudo virsh undefine <vm> --remove-all-storage # destructive — removes disk images
sudo virsh autostart <vm>
sudo virsh autostart --disable <vm>
for vm in vyos-01 bind-01 vault-01 ipa-01 ipsk-mgr-01 9800-WLC-01 k3s-master-01; do
sudo virsh start "$vm" && echo "$vm: started" || echo "$vm: FAILED"
sleep 5
done
for vm in vyos-01 bind-01 vault-01 ipa-01 ipsk-mgr-01 9800-WLC-01 k3s-master-01; do
sudo virsh autostart "$vm" && echo "$vm: autostart enabled"
done
Console and Remote Access
console=ttyS0 in guest kernel args)sudo virsh console <vm>
sudo virsh console <vm> --force # steal an existing console session
Ctrl+]
sudo virsh vncdisplay <vm>
XML Configuration
sudo virsh dumpxml <vm>
sudo virsh dumpxml <vm> > /tmp/<vm>.xml
sudo virsh edit <vm>
diff \
<(sudo virsh dumpxml <vm-a> | grep -A15 '<interface') \
<(sudo virsh dumpxml <vm-b> | grep -A15 '<interface')
sed -i 's|/var/lib/libvirt/images/|/mnt/onboard-ssd/libvirt/images/|g' /tmp/<vm>.xml
sed -i 's|virbr0|br-mgmt|g' /tmp/<vm>.xml
grep "source bridge" /tmp/<vm>.xml # verify
sudo virsh change-media <vm> sda --eject --config
Network Interface Inspection
sudo virsh domiflist <vm>
VNET=$(sudo virsh domiflist <vm> | awk '/br-mgmt/{print $1}')
echo "vnet: $VNET"
sudo virsh domifaddr <vm>
sudo grep 'mac address' /etc/libvirt/qemu/<vm>.xml
Bridge and VLAN Trunk Management
bridge vlan show
bridge vlan show dev <vnet>
# Dynamic — resolve vnet from VM name first
bridge vlan show dev $(sudo virsh domiflist <vm> | awk '/vnet/{print $1}')
bridge link show
bridge link show | grep br-mgmt
ip link show type bridge
ip link show master br-mgmt
ip link show master br-mgmt | awk -F'[ :]+' '/vnet/{print $2}'
VNET=<vnet>
sudo bridge vlan add vid 100 dev "$VNET" pvid untagged
for vid in 10 20 30 40 110 120; do
sudo bridge vlan add vid "$vid" dev "$VNET"
done
sudo bridge vlan del vid 1 dev "$VNET" pvid untagged
sudo bridge vlan show dev "$VNET"
VNET=$(sudo virsh domiflist <vm> | awk '/br-mgmt/{print $1}')
sudo bridge vlan add vid 100 dev "$VNET" pvid untagged
for vid in 10 20 30 40 110 120; do
sudo bridge vlan add vid "$vid" dev "$VNET"
done
sudo bridge vlan del vid 1 dev "$VNET" pvid untagged
sudo bridge vlan show dev "$VNET"
diff \
<(bridge vlan show dev vnet16) \
<(bridge vlan show dev vnet17)
for port in eno{1..8}; do
state=$(cat /sys/class/net/$port/operstate 2>/dev/null || echo "missing")
speed=$(cat /sys/class/net/$port/speed 2>/dev/null || echo "?")
master=$(ip link show "$port" 2>/dev/null | awk -F'master ' '{print $2}' | awk '{print $1}')
printf "%-6s %-6s %5sMbps %s\n" "$port" "$state" "$speed" "${master:-[none]}"
done
libvirt Hook (VLAN Auto-Enforcement)
The /etc/libvirt/hooks/qemu hook fires on VM start and applies the correct VLAN trunk to
the VM’s vnet automatically. It uses MAC address matching to identify the interface.
sudo cat /etc/libvirt/hooks/qemu
sudo /etc/libvirt/hooks/qemu <vm> started begin -
sudo systemctl restart libvirtd
sudo virsh destroy <vm> && sudo virsh start <vm>
sleep 5
journalctl -t "libvirt-hook[<vm>]" --no-pager -n 30
journalctl -t "libvirt-hook" --no-pager -n 50
journalctl -t "libvirt-hook" -f
journalctl -t "libvirt-hook[<vm>]" --since "10 minutes ago" --no-pager
sudo cp /etc/libvirt/hooks/qemu /etc/libvirt/hooks/qemu-$(date +%Y-%m-%d)-bak
sudo ls /etc/libvirt/hooks/
NFS Storage Mounts
sudo mkdir -p /mnt/nas/{vms,isos,backups}
sudo mount -t nfs nas-01:/volume1/vms /mnt/nas/vms
sudo mount -t nfs nas-01:/volume1/isos /mnt/nas/isos
sudo mount -t nfs nas-01:/volume1/backups /mnt/nas/backups
cat << 'EOF' | sudo tee -a /etc/fstab
nas-01:/volume1/vms /mnt/nas/vms nfs defaults,_netdev,nofail,x-systemd.device-timeout=10 0 0
nas-01:/volume1/isos /mnt/nas/isos nfs defaults,_netdev,nofail,x-systemd.device-timeout=10 0 0
nas-01:/volume1/backups /mnt/nas/backups nfs defaults,_netdev,nofail,x-systemd.device-timeout=10 0 0
EOF
grep nfs /etc/fstab
sudo systemctl daemon-reload
mount | grep nas
df -h /mnt/nas/vms /mnt/nas/isos /mnt/nas/backups
VM Provisioning
Import from Existing Disk Image
sudo virt-install \
--name <vm> \
--memory <MB> \
--vcpus <n> \
--import \
--disk /path/to/<vm>.qcow2,bus=virtio \
--os-variant rocky9 \
--network bridge=br-mgmt,model=virtio \
--graphics vnc,listen=0.0.0.0 \
--noautoconsole
ISE (Cisco ISE — bare metal install from ISO)
sudo qemu-img create -f qcow2 /mnt/onboard-ssd/vms/ise-01.qcow2 300G
sudo virt-install \
--name ise-01 \
--memory 16384 \
--vcpus 4,cpuset=6-9 \
--cpu host-passthrough \
--machine q35 \
--cdrom /mnt/onboard-ssd/vms/Cisco-ISE-3.5.0.527.SPA.x86_64.iso \
--disk path=/mnt/onboard-ssd/vms/ise-01.qcow2,format=qcow2,bus=virtio \
--os-variant rhel8.0 \
--network bridge=br-mgmt,model=virtio \
--graphics vnc,listen=0.0.0.0 \
--video virtio \
--noautoconsole
Cloud-Init VM (Ubuntu Noble)
# Download base image
curl -LO https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
# Create overlay disk
sudo qemu-img create -f qcow2 -F qcow2 \
-b /mnt/onboard-ssd/isos/noble-server-cloudimg-amd64.img \
/mnt/onboard-ssd/libvirt/images/<vm>.qcow2 20G
# Generate cloud-init seed ISO
cat > /tmp/cloud-init.yml << 'CLOUDINIT'
#cloud-config
hostname: <vm>
fqdn: <vm>.inside.domusdigitalis.dev
users:
- name: evanusmodestus
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
ssh_authorized_keys:
- <your-pubkey>
package_update: true
CLOUDINIT
cloud-localds /tmp/cloud-init.iso /tmp/cloud-init.yml
# Install
sudo virt-install \
--name <vm> \
--memory 4096 \
--vcpus 2 \
--import \
--disk path=/mnt/onboard-ssd/libvirt/images/<vm>.qcow2,format=qcow2 \
--disk path=/tmp/cloud-init.iso,device=cdrom \
--network bridge=br-mgmt,model=virtio \
--os-variant ubuntu24.04 \
--graphics vnc,listen=0.0.0.0 \
--noautoconsole
Bulk Define from Saved XML Definitions
for xml in /mnt/nas/backups/kvm-01-rebuild/vm-definitions/*.xml; do
vm=$(basename "$xml" .xml)
echo "Importing: $vm"
sudo virsh define "$xml"
done
Bulk Re-path and Define (after storage migration)
for vm in vault-01 bind-01; do
sed -i 's|/var/lib/libvirt/images/|/mnt/onboard-ssd/libvirt/images/|g' /tmp/$vm.xml
sudo virsh define /tmp/$vm.xml
sudo virsh start "$vm"
done
Vault SSH CA Bootstrap (new hypervisor node)
# Pull Vault SSH CA public key and install as TrustedUserCAKeys
sudo curl -sk https://vault-01.inside.domusdigitalis.dev:8200/v1/ssh/public_key \
> /etc/ssh/vault-ca.pub
# Add to sshd_config if not already present
grep -q vault-ca.pub /etc/ssh/sshd_config || \
echo "TrustedUserCAKeys /etc/ssh/vault-ca.pub" | sudo tee -a /etc/ssh/sshd_config
sudo systemctl reload sshd
NetworkManager Bridge VLAN Filtering
sudo nmcli connection modify br-mgmt \
bridge.vlan-filtering yes \
bridge.vlan-default-pvid 0 \
bridge.vlans "100 pvid untagged, 10, 20, 30, 40, 110, 120"
nmcli c s br-mgmt | grep bridge.vlan
sudo nmcli connection add type bridge con-name br-wan ifname br-wan
sudo nmcli connection modify br-wan ipv4.method disabled ipv6.method disabled
sudo nmcli connection modify br-wan bridge.stp no bridge.forward-delay 0
sudo nmcli connection add type bridge-slave con-name br-wan-port ifname eno7 master br-wan
sudo nmcli connection delete eno7 2>/dev/null || true
sudo nmcli connection up br-wan
ip link show br-wan
bridge link show | grep eno7
AsciiDoc Dedup Utility: virsh list Commands
When a command log accumulates duplicate virsh list invocations, use awk to normalize
and deduplicate while preserving one instance of each semantically distinct form.
file="$(find . -name 'kvm-virsh-vm-terminal-management.adoc' -print -quit)"
awk '
/^[[:space:]]*sudo virsh list/ {
cmd=$0
sub(/^[[:space:]]+/, "", cmd)
count[cmd]++
}
END {
for (cmd in count)
printf "%3d %s\n", count[cmd], cmd
}
' "$file" | sort -nr
cp "$file" "$file.bak"
awk '
/^[[:space:]]*sudo virsh list/ {
cmd=$0
sub(/^[[:space:]]+/, "", cmd)
if (seen[cmd]++) next
}
{ print }
' "$file" > "$file.tmp" && mv "$file.tmp" "$file"
grep -nF 'sudo virsh list' "$file"
libvirt Hook — How It Works
The /etc/libvirt/hooks/qemu script is the mechanism that automatically enforces the correct
VLAN trunk on every vnet the moment a VM starts. Without it, a vnet comes up as a plain
access port on VLAN 1 — wrong for every VM on br-mgmt.
Hook Invocation Signature
libvirt calls the hook like this:
/etc/libvirt/hooks/qemu <vm-name> <action> <sub-action> <extra>
The arguments you care about:
|
e.g. |
|
|
|
|
|
usually |
Act on started (not start) — the vnet interface exists in the kernel at that point.
On start, libvirt hasn’t attached the tap yet.
Hook Structure (annotated)
This is the pattern the hook was built around during the kvm-01 rebuild:
#!/usr/bin/env bash
# /etc/libvirt/hooks/qemu
# Enforces VLAN trunk on br-mgmt vnets at VM start.
DOMAIN="$1"
ACTION="$2"
BRIDGE="br-mgmt"
TAG="libvirt-hook[$DOMAIN]"
# Only act on the 'started' event
[[ "$ACTION" != "started" ]] && exit 0
# --- Resolve vnet from MAC address in the VM's XML ---
# libvirt stores XML at /etc/libvirt/qemu/<vm>.xml
XML="/etc/libvirt/qemu/${DOMAIN}.xml"
MAC=$(grep -oP "(?<=<mac address=[\"'])[0-9a-f:]+" "$XML" | head -1)
if [[ -z "$MAC" ]]; then
logger -t "$TAG" "ERROR: could not extract MAC from $XML"
exit 1
fi
logger -t "$TAG" "MAC=$MAC"
# --- Find vnet whose kernel MAC matches ---
VNET=""
for iface in $(ip link show master "$BRIDGE" | awk -F'[ :]+' '/^[0-9]/{print $3}'); do
iface_mac=$(cat /sys/class/net/"$iface"/address 2>/dev/null)
if [[ "$iface_mac" == "$MAC" ]]; then
VNET="$iface"
break
fi
done
if [[ -z "$VNET" ]]; then
logger -t "$TAG" "ERROR: no vnet on $BRIDGE matched MAC=$MAC"
exit 1
fi
logger -t "$TAG" "VNET=$VNET matched MAC=$MAC"
# --- Apply VLAN trunk ---
bridge vlan add vid 100 dev "$VNET" pvid untagged
for vid in 10 20 30 40 110 120; do
bridge vlan add vid "$vid" dev "$VNET"
done
bridge vlan del vid 1 dev "$VNET" pvid untagged
logger -t "$TAG" "PVID set to 100 untagged, tagged: 10 20 30 40 110 120 on $VNET"
The MAC resolution loop (ip link show master br-mgmt | awk) was the fix that made
the hook reliable. Earlier attempts used awk '{print $3}' and got the wrong field — the
working version uses '{print $3}' on lines starting with a digit (interface index lines),
then reads /sys/class/net/<iface>/address to compare MACs directly.
|
Why MAC Matching, Not VM Name
libvirt assigns vnet names non-deterministically (vnet0, vnet1, …) based on
tap creation order at boot. The name changes across reboots if other VMs start in a
different order. The MAC is stable — it’s fixed in the domain XML — so MAC matching
is the correct anchor.
Troubleshooting the Hook
ls -la /etc/libvirt/hooks/qemu
sudo /etc/libvirt/hooks/qemu <vm> started begin -
# For a specific VM
journalctl -t "libvirt-hook[<vm>]" --since "10 minutes ago" --no-pager
# All hook events
journalctl -t "libvirt-hook" --no-pager -n 50 -f
# libvirtd-level hook errors (hook not found, permission denied, etc.)
journalctl -u libvirtd --since "5 minutes ago" | grep -i hook
logger -t "libvirt-hook[test]" "smoke test with brackets"
journalctl -t "libvirt-hook[test]" --no-pager -n 3
logger -t "libvirt-hook" "smoke test without brackets"
journalctl -t "libvirt-hook" --no-pager -n 3
journalctl -t matches the syslog identifier exactly. A hook that logs with
logger -t "libvirt-hook[$DOMAIN]" will NOT appear when you query -t "libvirt-hook" —
the brackets make them different identifiers. Use both patterns when unsure.
|
sudo systemctl restart libvirtd
sudo virsh destroy <vm> && sudo virsh start <vm>
sleep 5
journalctl -t "libvirt-hook[<vm>]" --no-pager -n 20
bridge vlan show dev $(sudo virsh domiflist <vm> | awk '/br-mgmt/{print $1}')
sudo grep -oP "(?<=<mac address=[\"'])[0-9a-f:]+" /etc/libvirt/qemu/<vm>.xml
cat /sys/class/net/<vnet>/address
libvirt Virtual Networks
libvirt manages its own NAT network (virbr0) separately from the host’s bridge interfaces.
Use virsh net-* to inspect it — do not confuse it with the br-mgmt Linux bridge.
# List all libvirt-managed networks (active and inactive)
sudo virsh net-list --all
# Full XML of the default NAT network
sudo virsh net-dumpxml default
# Start / stop a libvirt network
sudo virsh net-start default
sudo virsh net-destroy default
br-mgmt and br-wan are plain Linux bridges managed by NetworkManager — they are
invisible to virsh net-list. Only virbr0 (and any networks you define with
virsh net-define) appear there.
|
ARP / Neighbor Table — Finding VM IPs Without Guest Agent
When virsh domifaddr returns nothing (no guest agent, DHCP lease not visible), use the
host’s ARP/neighbor cache after pinging the broadcast or a known range:
# Check kernel neighbor table (most reliable)
ip neigh show dev br-mgmt
ip neigh | grep -i "52:54:00" # all KVM VMs share this OUI
# Filter by specific MAC
ip neigh | grep -i "52:54:00:f3"
# Classic arp table
arp -an | grep 10.50.1
# Ping to populate the neighbor cache first if needed
ping -c1 -b 10.50.1.255 2>/dev/null || true
ping -c1 10.50.1.<n>
# Then check
ip neigh show dev br-mgmt | grep "52:54:00"
VM=<vm>
MAC=$(sudo grep -oP "(?<=<mac address=[\"'])[0-9a-f:]+" /etc/libvirt/qemu/${VM}.xml | head -1)
echo "Looking for MAC: $MAC"
ip neigh | grep -i "$MAC"
brctl Legacy Commands
brctl is deprecated in favour of bridge (iproute2) but still works on Rocky 9.
Prefer bridge for new runbooks; brctl is useful when reading older documentation.
# Show bridge members (legacy)
sudo brctl show br-mgmt
sudo brctl show virbr0
# iproute2 equivalents (preferred)
bridge link show
ip link show master br-mgmt
Host Resource Snapshot
Quick one-liner to capture host capacity before provisioning a new VM:
echo "=== Kernel ===" && uname -r
echo "=== CPU ===" && nproc && lscpu | grep -E 'Socket|Core|Thread|NUMA'
echo "=== Memory ===" && free -h
echo "=== Disk ===" && df -h /mnt/onboard-ssd /var/lib/libvirt/images 2>/dev/null
echo "=== VMs ===" && sudo virsh list --all
echo "=== vCPU load ===" && sudo virsh vcpuinfo vyos-01 2>/dev/null | grep -E 'VCPU|CPU:'
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT
df -h /mnt/onboard-ssd
VM=<vm>
t1=$(sudo virsh dominfo "$VM" | awk '/CPU time/{print $3}')
sleep 5
t2=$(sudo virsh dominfo "$VM" | awk '/CPU time/{print $3}')
echo "$VM CPU time delta: $(echo "$t2 - $t1" | bc)s over 5s"
VyOS ISO — Fetch Latest Nightly Build
VYOS_LATEST=$(curl -s \
https://api.github.com/repos/vyos/vyos-nightly-build/releases/latest \
| grep -oP '"tag_name":\s*"\K[^"]+')
echo "Latest tag: $VYOS_LATEST"
curl -Lo /mnt/onboard-ssd/isos/vyos-rolling-latest.iso \
"https://github.com/vyos/vyos-nightly-build/releases/download/${VYOS_LATEST}/vyos-${VYOS_LATEST}-generic-amd64.iso"
SSH Config — Propagate to Other Machines
SSH Config — Propagate Modular Architecture to Other Machines
The modular SSH config (built 2026-06-20, activated 2026-06-21) is live on modestus-aw. Other machines still have the old monolithic symlink or no config at all.
What Changed in dots-quantum
Monolithic ~/.ssh/config replaced with Include-based modular architecture:
config → global defaults + Include config.d/* config.d/00-exceptions.conf → legacy cipher overrides (Azure) config.d/10-git-services.conf → GitHub, GitLab, Codeberg, Bitbucket config.d/20-d000-key-hierarchy.conf → shared key chain (no IPs) config.d/30-cisco-defaults.conf → Cisco/ISE auth (9800-wlc-*, 9300-*, 3560cx-*) config.d/40-k3s-defaults.conf → k3s auth settings config.d/50-vm-defaults.conf → VM auth settings config.d/80-d000-hosts.conf → host→IP mappings (generated from DNS, encrypted in git)
Git Tracks vs Gitignored
| File | Tracked? | Why |
|---|---|---|
|
✅ Tracked (encrypted) |
Main config contains |
|
✅ Tracked (plaintext) |
No secrets — patterns, usernames, auth settings only |
|
✅ Tracked (encrypted) |
Contains host→IP mappings |
|
❌ Gitignored |
Decrypted locally on each machine |
|
❌ Gitignored |
Generated fresh from DNS on each machine |
Propagation Runbook — Home Environment (d000)
For machines on the home network with access to BIND at 10.50.1.90.
git -C ~/atelier/_projects/personal/dots-quantum pull
mkdir -p ~/.ssh/{sockets,config.d} && chmod 700 ~/.ssh ~/.ssh/sockets ~/.ssh/config.d
# CHECK FIRST — does ~/.ssh/config point to dotfiles-optimus?
ls -la ~/.ssh/config
# If it shows dotfiles-optimus → must remove before dots-quantum can claim it
# stow won't overwrite another package's symlinks — it treats them as conflicts
# Option A: unstow the old package cleanly
stow -D -d ~/atelier/_projects/personal/dotfiles-optimus -t ~ base
# Option B: if dotfiles-optimus directory structure has changed or unstow fails
rm ~/.ssh/config
stow -R -d ~/atelier/_projects/personal/dots-quantum -t ~ ssh
# Verify ALL symlinks point to dots-quantum (not dotfiles-optimus)
ls -la ~/.ssh/config ~/.ssh/config.age ~/.ssh/config.d/
# config → dots-quantum/ssh/.ssh/config ← NOT dotfiles-optimus
# config.age → dots-quantum/ssh/.ssh/config.age
# config.d/* → dots-quantum/ssh/.ssh/config.d/*
age -d -i ~/.secrets/.metadata/keys/master.age.key ~/.ssh/config.age > \
~/atelier/_projects/personal/dots-quantum/ssh/.ssh/config
# Verify Include directive is present
head -5 ~/.ssh/config
# Should show: Include config.d/*
for name in kvm-0{1,2} nas-0{1,2} vyos-0{1,2} vyos-vip bind-0{1,2} ipa-0{1,2} \
keycloak-0{1,2} ipsk-mgr-0{1,2} vault-0{1,2,3} certmgr-01 \
home-dc0{1,2} 9800-wlc-0{1,2} wlc-0{1,2} k3s-master-0{1,2,3} \
ise-0{1,2} gitea-01 3560cx-01 9300-01 ipmi-0{1,2} \
wazuh wazuh-indexer wazuh-dashboard wazuh-workers wazuh-api \
prometheus alertmanager grafana modestus-{aw,razer}; do
ip=$(dig +short ${name}.inside.domusdigitalis.dev @10.50.1.90)
[[ "$ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] && printf "Host %s\n HostName %s\n\n" "$name" "$ip"
done >| ~/atelier/_projects/personal/dots-quantum/ssh/.ssh/config.d/80-d000-hosts.conf
for name in kvm-0{1,2} nas-0{1,2} vyos-0{1,2} bind-0{1,2} ipa-0{1,2} \
vault-0{1,2,3} ise-0{1,2} gitea-01 9300-01 3560cx-01; do
ip=$(ssh -G "$name" | awk '/^hostname/{print $2}')
if [[ "$ip" == "$name" ]]; then
printf "BAD %-20s ← no mapping found\n" "$name"
else
printf "GOOD %-20s → %s\n" "$name" "$ip"
fi
done
ssh kvm-01
Propagation Runbook — Work Environment (d001 / WSL)
|
DO NOT run the d000 |
Work machines cannot reach home BIND (10.50.1.90). The d000 host list is irrelevant — those are home lab hosts. Work hosts come from gopass tier d001.
# Pull
git -C ~/atelier/_projects/personal/dots-quantum pull
# Directories
mkdir -p ~/.ssh/{sockets,config.d} && chmod 700 ~/.ssh ~/.ssh/sockets ~/.ssh/config.d
# Remove stale dotfiles-optimus symlink — MUST do before stow
ls -la ~/.ssh/config | grep -q dotfiles-optimus && rm ~/.ssh/config
# Stow
stow -R -d ~/atelier/_projects/personal/dots-quantum -t ~ ssh
# Verify — CRITICAL: config must point to dots-quantum
ls -la ~/.ssh/config
# GOOD: -> ../atelier/_projects/personal/dots-quantum/ssh/.ssh/config
# BAD: -> ../atelier/_projects/personal/dotfiles-optimus/base/ssh/.ssh/config
# Decrypt
age -d -i ~/.secrets/.metadata/keys/master.age.key ~/.ssh/config.age > \
~/atelier/_projects/personal/dots-quantum/ssh/.ssh/config
# DO NOT run the d000 dig loop here. 10.50.1.90 is unreachable.
# dig will write error messages that break SSH config parsing.
# Option A: Build d001 host list from gopass
cat >| ~/atelier/_projects/personal/dots-quantum/ssh/.ssh/config.d/80-d000-hosts.conf << 'EOF'
# d001 work environment host mappings
# Source: gopass tier d001 — NOT d000 DNS
# Generated: 2026-06-22
# Add your work hosts here:
# Host jump-01
# HostName 10.x.x.x
# Host build-server
# HostName 10.x.x.x
EOF
# Option B: Leave empty — rely on FQDN or DNS resolution directly
# The modular config still works — git services, key hierarchy, Cisco
# defaults all load from config.d/10-50. Only host shortcuts are missing.
printf '# d001 work environment — no d000 hosts, use FQDN\n' \
>| ~/atelier/_projects/personal/dots-quantum/ssh/.ssh/config.d/80-d000-hosts.conf
# Check that Include is working (config.d files are being read)
ssh -G github.com | awk '/^hostname|^user|^identityfile/'
# Should show github.com settings from 10-git-services.conf
# Check that Cisco defaults load
ssh -G 9800-wlc-01 | awk '/^user|^passwordauthentication/'
# Should show user=admin, passwordauthentication=yes from 30-cisco-defaults.conf
The dotfiles-optimus Problem — Explained
On the work machine, stow -R from dots-quantum created config.d/* and config.age correctly but left ~/.ssh/config still pointing to dotfiles-optimus:
~/.ssh/config → dotfiles-optimus/base/ssh/.ssh/config ← OLD (monolithic) ~/.ssh/config.age → dots-quantum/ssh/.ssh/config.age ← NEW ~/.ssh/config.d/* → dots-quantum/ssh/.ssh/config.d/* ← NEW
Why stow didn’t fix it: Stow tracks ownership per package. The config symlink was created by the dotfiles-optimus package. Stow considers it a conflict — it won’t silently overwrite another package’s files.
The fix: rm ~/.ssh/config then stow -R again. Or stow -D the old package first to unstow cleanly. Either way, the old symlink must go before dots-quantum can claim it.
After the fix:
~/.ssh/config → dots-quantum/ssh/.ssh/config ← NEW (modular, Include config.d/*) ~/.ssh/config.age → dots-quantum/ssh/.ssh/config.age ~/.ssh/config.d/* → dots-quantum/ssh/.ssh/config.d/*
Is dotfiles-optimus still needed? If dots-quantum now manages all the stow packages that dotfiles-optimus used to (ssh, zsh, git, etc.), you can unstow dotfiles-optimus entirely on each machine and archive the repo. Check what else it manages:
# See what dotfiles-optimus has stowed
ls ~/atelier/_projects/personal/dotfiles-optimus/base/
# Compare with what dots-quantum manages
ls ~/atelier/_projects/personal/dots-quantum/
# If dots-quantum covers everything, unstow the old repo completely:
stow -D -d ~/atelier/_projects/personal/dotfiles-optimus -t ~ base
Why Generate Instead of Decrypt 80-d000-hosts.conf?
The .age file exists as a backup in git. But the correct workflow is regenerating from DNS because:
-
DNS is the authoritative source — if an IP changes, the
.agesnapshot is stale -
The
digloop is verifiable against BIND primary in real time -
Each machine gets current IPs, not a frozen snapshot from whenever the last
age -eran -
On work machines, the d000
.ageis useless — those hosts don’t exist in that environment
Prerequisites on Each Machine
| Requirement | Check Command |
|---|---|
|
|
|
|
Age private key |
|
Network access to BIND primary (d000 only) |
|
OpenSSH ≥ 9.0 (Post-Quantum KEX) |
|
dots-quantum cloned |
|
No stale dotfiles-optimus symlinks |
|
Machines to Propagate
| Machine | Status | Notes |
|---|---|---|
modestus-aw (Arch, home) |
■■■■■■■■■■ 10/10 |
Activated 2026-06-21 |
modestus-razer (Arch, home) |
□□□□□□□□□□ 0/10 |
d000 runbook: |
Work WSL — Arch instance |
■■■■■■■■■■ 10/10 |
Fixed: removed dotfiles-optimus symlink, restowed, recovered from dig loop incident. |
Work WSL — other distros |
□□□□□□□□□□ 0/10 |
d001 runbook: same flow, d001 host list from gopass. DO NOT run d000 dig loop. |
Session Output — Work WSL (2026-06-22)
$ mkdir -p ~/.ssh/{sockets,config.d} && chmod 700 ~/.ssh ~/.ssh/sockets ~/.ssh/config.d
$ stow -R -d ~/atelier/_projects/personal/dots-quantum -t ~ ssh
$ ls -la ~/.ssh/config ~/.ssh/config.age ~/.ssh/config.d/
lrwxrwxrwx - evanusmodestus 18 Dec 2025 /home/evanusmodestus/.ssh/config -> ../atelier/_projects/personal/dotfiles-optimus/base/ssh/.ssh/config
lrwxrwxrwx - evanusmodestus 6 May 09:30 /home/evanusmodestus/.ssh/config.age -> ../atelier/_projects/personal/dots-quantum/ssh/.ssh/config.age
/home/evanusmodestus/.ssh/config.d/:
lrwxrwxrwx - evanusmodestus 22 Jun 13:00 00-exceptions.conf -> ../../atelier/_projects/personal/dots-quantum/ssh/.ssh/config.d/00-exceptions.conf
lrwxrwxrwx - evanusmodestus 22 Jun 13:00 10-git-services.conf -> ../../atelier/_projects/personal/dots-quantum/ssh/.ssh/config.d/10-git-services.conf
lrwxrwxrwx - evanusmodestus 22 Jun 13:00 20-d000-key-hierarchy.conf -> ../../atelier/_projects/personal/dots-quantum/ssh/.ssh/config.d/20-d000-key-hierarchy.conf
lrwxrwxrwx - evanusmodestus 22 Jun 13:00 30-cisco-defaults.conf -> ../../atelier/_projects/personal/dots-quantum/ssh/.ssh/config.d/30-cisco-defaults.conf
lrwxrwxrwx - evanusmodestus 22 Jun 13:00 40-k3s-defaults.conf -> ../../atelier/_projects/personal/dots-quantum/ssh/.ssh/config.d/40-k3s-defaults.conf
lrwxrwxrwx - evanusmodestus 22 Jun 13:00 50-vm-defaults.conf -> ../../atelier/_projects/personal/dots-quantum/ssh/.ssh/config.d/50-vm-defaults.conf
lrwxrwxrwx - evanusmodestus 22 Jun 13:00 80-d000-hosts.conf.age -> ../../atelier/_projects/personal/dots-quantum/ssh/.ssh/config.d/80-d000-hosts.conf.age
$ age -d -i ~/.secrets/.metadata/keys/master.age.key ~/.ssh/config.age > \
~/atelier/_projects/personal/dots-quantum/ssh/.ssh/config
$ head -5 ~/.ssh/config
# ═══════════════════════════════════════════════════════════════════════════════
# SSH Client Configuration - Post-Quantum Ready
# ═══════════════════════════════════════════════════════════════════════════════
# document_uuid: "b9805b2e-3e63-4231-ae94-6df5551b8522"
# id: "ARS-SEC-SSH-001"
$ for name in kvm-0{1,2} nas-0{1,2} ...; do
ip=$(dig +short ${name}.inside.domusdigitalis.dev @10.50.1.90)
[ -n "$ip" ] && printf "Host %s\n HostName %s\n\n" "$name" "$ip"
done >| ~/atelier/_projects/personal/dots-quantum/ssh/.ssh/config.d/80-d000-hosts.conf
# RESULT: dig returned error messages instead of empty output:
# Host kvm-01
# HostName ;; communications error to 10.50.1.90#53: host unreachable
#
# SSH then failed on every operation:
# /home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 2:
# keyword hostname extra arguments at end of line
#
# ROOT CAUSE: The d000 dig loop targets 10.50.1.90 (home BIND primary).
# That IP is unreachable from the d001 work network. `dig +short` printed
# the error to stdout. `[ -n "$ip" ]` only checks non-empty — the error
# string passed the guard and was written as HostName values.
#
# LESSON: NEVER run the d000 dig loop on d001 machines. The d000 host list
# is home-lab-only. Work machines need a d001 host list from gopass, or
# leave 80-d000-hosts.conf empty.
#
# FIX APPLIED: regex guard in runbook:
# [[ "$ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]
# This rejects anything that isn't an IPv4 address.
# Wiped the garbage
printf '# d001 work environment — no d000 hosts\n' \
>| ~/.ssh/config.d/80-d000-hosts.conf
# Verified SSH works
ssh -G github.com | awk '/^hostname/'
# hostname github.com ← GOOD
# Verified git works
ssh -T git@github.com
rm ~/.ssh/config
stow -R -d ~/atelier/_projects/personal/dots-quantum -t ~ ssh
ls -la ~/.ssh/config # Now points to dots-quantum
2026-06-22 13:11 - updates to configuration and status
❯ hostnamectl
Failed to query product UUID, ignoring: Access denied
Failed to query hardware serial, ignoring: Access denied
Static hostname: L-N62420
Icon name: computer-container
Chassis: container 📦
Machine ID: e6bfa315292e4771bef94d8e4cf17fdf
Boot ID: d683db94eadc4ff2aa2335e89350eb2f
Virtualization: wsl
Operating System: Arch Linux
Kernel: Linux 6.6.87.2-microsoft-standard-WSL2
Architecture: x86-64
evanusmodestus domus-captures main 26.2 ✓
❯ ssh -G github.com | awk '/^hostname|^user|^identityfile/'
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 2: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 3: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 4: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 5: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 8: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 9: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 10: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 11: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 14: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 15: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 16: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 17: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 20: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 21: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 22: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 23: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 26: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 27: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 28: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 29: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 32: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 33: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 34: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 35: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 38: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 39: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 40: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 41: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 44: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 45: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 46: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 47: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 50: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 51: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 52: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 53: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 56: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 57: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 58: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 59: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 62: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 63: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 64: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 65: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 68: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 69: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 70: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 71: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 74: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 75: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 76: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 77: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 80: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 81: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 82: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 83: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 86: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 87: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 88: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 89: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 92: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 93: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 94: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 95: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 98: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 99: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 100: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 101: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 104: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 105: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 106: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 107: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 110: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 111: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 112: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 113: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 116: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 117: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 118: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 119: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 122: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 123: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 124: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 125: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 128: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 129: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 130: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 131: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 134: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 135: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 136: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 137: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 140: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 141: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 142: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 143: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 146: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 147: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 148: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 149: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 152: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 153: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 154: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 155: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 158: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 159: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 160: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 161: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 164: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 165: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 166: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 167: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 170: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 171: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 172: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 173: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 176: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 177: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 178: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 179: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 182: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 183: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 184: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 185: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 188: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 189: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 190: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 191: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 194: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 195: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 196: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 197: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 200: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 201: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 202: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 203: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 206: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 207: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 208: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 209: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 212: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 213: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 214: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 215: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 218: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 219: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 220: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 221: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 224: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 225: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 226: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 227: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 230: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 231: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 232: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 233: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 236: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 237: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 238: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 239: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 242: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 243: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 244: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 245: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 248: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 249: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 250: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 251: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 254: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 255: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 256: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 257: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 260: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 261: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 262: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 263: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 266: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 267: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 268: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 269: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: terminating, 180 bad configuration options
❯ ssh -G github.com | awk '/^hostname|^user|^identityfile/'
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 2: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 3: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 4: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 5: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 8: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 9: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 10: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 11: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 14: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 15: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 16: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 17: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 20: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 21: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 22: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 23: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 26: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 27: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 28: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 29: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 32: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 33: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 34: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 35: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 38: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 39: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 40: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 41: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 44: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 45: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 46: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 47: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 50: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 51: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 52: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 53: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 56: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 57: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 58: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 59: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 62: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 63: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 64: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 65: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 68: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 69: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 70: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 71: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 74: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 75: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 76: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 77: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 80: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 81: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 82: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 83: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 86: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 87: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 88: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 89: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 92: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 93: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 94: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 95: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 98: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 99: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 100: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 101: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 104: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 105: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 106: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 107: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 110: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 111: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 112: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 113: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 116: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 117: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 118: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 119: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 122: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 123: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 124: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 125: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 128: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 129: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 130: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 131: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 134: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 135: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 136: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 137: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 140: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 141: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 142: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 143: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 146: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 147: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 148: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 149: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 152: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 153: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 154: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 155: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 158: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 159: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 160: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 161: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 164: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 165: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 166: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 167: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 170: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 171: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 172: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 173: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 176: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 177: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 178: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 179: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 182: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 183: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 184: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 185: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 188: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 189: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 190: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 191: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 194: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 195: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 196: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 197: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 200: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 201: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 202: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 203: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 206: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 207: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 208: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 209: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 212: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 213: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 214: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 215: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 218: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 219: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 220: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 221: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 224: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 225: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 226: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 227: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 230: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 231: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 232: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 233: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 236: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 237: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 238: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 239: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 242: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 243: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 244: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 245: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 248: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 249: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 250: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 251: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 254: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 255: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 256: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 257: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 260: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 261: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 262: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 263: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 266: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 267: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 268: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 269: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: terminating, 180 bad configuration options
❯ ssh -G github.com | awk '/^hostname|^user|^identityfile/'
❯ ssh -T git@github.com
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 2: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 3: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 4: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 5: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 8: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 9: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 10: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 11: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 14: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 15: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 16: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 17: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 20: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 21: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 22: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 23: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 26: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 27: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 28: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 29: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 32: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 33: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 34: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 35: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 38: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 39: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 40: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 41: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 44: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 45: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 46: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 47: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 50: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 51: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 52: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 53: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 56: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 57: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 58: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 59: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 62: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 63: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 64: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 65: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 68: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 69: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 70: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 71: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 74: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 75: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 76: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 77: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 80: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 81: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 82: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 83: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 86: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 87: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 88: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 89: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 92: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 93: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 94: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 95: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 98: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 99: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 100: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 101: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 104: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 105: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 106: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 107: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 110: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 111: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 112: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 113: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 116: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 117: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 118: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 119: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 122: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 123: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 124: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 125: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 128: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 129: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 130: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 131: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 134: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 135: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 136: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 137: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 140: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 141: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 142: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 143: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 146: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 147: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 148: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 149: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 152: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 153: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 154: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 155: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 158: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 159: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 160: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 161: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 164: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 165: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 166: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 167: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 170: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 171: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 172: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 173: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 176: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 177: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 178: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 179: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 182: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 183: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 184: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 185: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 188: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 189: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 190: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 191: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 194: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 195: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 196: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 197: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 200: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 201: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 202: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 203: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 206: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 207: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 208: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 209: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 212: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 213: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 214: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 215: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 218: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 219: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 220: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 221: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 224: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 225: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 226: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 227: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 230: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 231: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 232: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 233: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 236: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 237: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 238: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 239: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 242: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 243: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 244: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 245: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 248: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 249: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 250: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 251: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 254: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 255: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 256: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 257: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 260: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 261: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 262: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 263: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf line 266: keyword hostname extra arguments at end of line
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 267: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 268: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: line 269: Bad configuration option: ;;
/home/evanusmodestus/.ssh/config.d/80-d000-hosts.conf: terminating, 180 bad configuration options
evanusmodestus domus-captures main 26.2 ✗
❯
Conditional Attributes — Reference & Implementation Plan
Conditional Attributes — Reference Capture & Implementation Plan
What Was Built
Created partials/reference/docs-as-code/conditional-attributes.adoc via heredoc:
tee docs/modules/ROOT/partials/reference/docs-as-code/conditional-attributes.adoc << 'EOF'
... (355 lines)
EOF
This is a complete AsciiDoc conditional preprocessor reference covering:
-
ifdef/ifndef/ifevaldirectives with OR/AND logic -
Environment-switched attributes (
env-d001vsenv-d000with safe fallback) -
Master
partials/attributes.adocpattern — single source of truth for both prose and diagrams -
Kroki diagram integration — attributes resolve before the diagram renderer sees them
-
Attribute precedence chain: page < antora.yml < playbook < CLI flag
-
@soft-set vs hard-set in Antora -
Build commands for each target environment
-
Debug blocks that strip from production builds
Why This Matters
This solves the hardcoded-values-in-diagrams problem we identified today. The resolution order is:
antora-playbook.yml / CLI -a flag
↓
partials/attributes.adoc (ifdef selects env-specific values)
↓
page content ({attribute} renders in prose)
↓
Kroki block (sees resolved literals, not tokens)
↓
SVG output (diagram contains real values)
For Antora builds (docs/): Kroki blocks with subs=attributes+ resolve attributes before rendering. No hardcoded IPs, hostnames, or UUIDs in diagram source.
For standalone builds (data/): build-adoc -a env-d000 passes the flag at CLI. Same ifdef conditionals work. Diagrams in external .dot/.d2 files still need the README attribute map or envsubst — but inline Kroki blocks in the assembler would resolve correctly.
Implementation Plan — 3 Active Projects
| Project | Tier | What Changes | Status |
|---|---|---|---|
monad-pipeline |
d001 (Antora + standalone) |
|
□□□□□□□□□□ 0/10 |
keylogger |
d000 (standalone only) |
|
□□□□□□□□□□ 0/10 |
thinkpad-t16g |
d000 (standalone + Antora dual) |
|
□□□□□□□□□□ 0/10 |
How to Use the Reference
The reference partial is not meant to be included directly into project pages. It documents the patterns — copy the relevant ifdef blocks into your project’s partials/attributes.adoc.
# Before (no conditional — always sets values)
:ise-hostname: ise-02.inside.domusdigitalis.dev
# After (environment-switched)
:ise-hostname: ise.example.internal
[graphviz,subs=attributes+]
....
digraph {
ise [label="{ise-hostname}"]
}
....
build-adoc $(find data/d000/projects/keylogger -maxdepth 1 -name 'keylogger.adoc') html --variant light-cyan -a env-d000
Security Benefit
The ifdef + fallback pattern is defense in depth at the rendering layer:
| Scenario | What Renders |
|---|---|
|
Real production values — pipeline IDs, rsyslog IP, billing account, hostnames |
|
Real homelab values — domus hostnames, local IPs |
|
Zeroed UUIDs ( |
Even if an attacker obtains the .adoc source files, building without the correct -a env-* flag produces a document with no usable infrastructure data. This complements the existing layers:
-
Layer 1: Encryption —
d001 closeencrypts partials at rest (.age) -
Layer 2: Gitignore — plaintext data files blocked from tracking
-
Layer 3: Conditional rendering — production values require explicit build flag
-
Layer 4: Scrub rules — personal terms blocked by pre-push audit
No single layer is sufficient. Together they ensure that source, storage, and output are all protected independently.
Standard Established
This reference joins STD-004 (AsciiDoc Conventions) and STD-015 (Antora Attributes). The rule:
-
Every
partials/attributes.adocMUST useifdefenvironment blocks — no naked attribute definitions that assume a single environment -
Every Antora diagram SHOULD use Kroki blocks with
subs=attributes+— no hardcoded values in diagram source -
Every standalone project SHOULD accept
-a env-*at build time — same attributes, same conditionals, same source
Keylogger Project — Init
Keylogger Project — Init
Project Scaffolded
mkdir -p data/d000/projects/keylogger/{src,output,docs,include} \
&& touch data/d000/projects/keylogger/{Makefile,README.adoc,src/keylogger.c}
data/d000/projects/keylogger/
├── docs/ # Build notes, partials as you learn
├── include/ # Header files (.h)
├── Makefile # Build rules
├── output/ # Compiled binary (gitignored)
├── README.adoc # Project overview
└── src/
└── keylogger.c # Main source
What This Project Teaches
| Concept | How the Keylogger Uses It |
|---|---|
|
Open |
|
Read |
C structs |
|
|
|
File permissions |
|
|
Monitor multiple input devices simultaneously |
|
Trace your own program: |
|
|
Makefile |
|
Signal handling |
|
First Milestone — 50 Lines
Read /dev/input/event*, print key press/release events to stdout. No filtering, no mapping, no features. Just prove you can read kernel input events from C.
# Find your keyboard device
cat /proc/bus/input/devices | awk '/keyboard/I,/^$/'
# Find the event number
ls -la /dev/input/by-id/ | grep -i kbd
# Run with sudo (needs input device access)
sudo ./output/keylogger /dev/input/eventN
thinkpad-t16g — Fixed Assembler (Same Session)
The thinkpad-t16g assembler in data/d000/projects/ was using {antora-partials} — a relative path reaching into docs/modules/ROOT/partials/. That’s Antora’s tree. Standalone build-adoc projects must be self-contained.
Fix applied:
-
Copied 28 partials from
docs/modules/ROOT/partials/projects/thinkpad-t16g/intodata/d000/projects/thinkpad-t16g/partials/ -
Rewrote assembler:
include::{antora-partials}/…→include::partials/… -
Added
:toc: leftand:toclevels: 3for standalone rendering -
xref:cross-references to Antora pages remain — render as plain text in standalone build, not broken
build-adoc data/d000/projects/thinkpad-t16g/thinkpad-t16g.adoc html --variant light-cyan \
&& firefox data/d000/projects/thinkpad-t16g/output/thinkpad-t16g.html &
Status
| Item | Status |
|---|---|
Keylogger directory scaffolded |
■■■■■■■■■■ 10/10 |
Keylogger assembler + partials |
■■■■■■■■■■ 10/10 |
|
■■■■■■■■■■ 10/10 |
thinkpad-t16g assembler fixed |
■■■■■■■■■■ 10/10 — standalone partials, no Antora cross-reference |
First |
□□□□□□□□□□ 0/10 |
Key code → character mapping |
□□□□□□□□□□ 0/10 |
Multi-device |
□□□□□□□□□□ 0/10 |
|
□□□□□□□□□□ 0/10 |
Project Compliance Audit — STD-001 + STD-025
Project Compliance Audit — STD-001 + STD-025
Inventory of all standalone projects against the new standard: assembler, ifdef attributes, metadata, summary, diagrams.
Gold Standard (fully compliant)
| Project | Tier | Build Command |
|---|---|---|
keylogger |
d000 |
|
thinkpad-t16g |
d000 |
|
linux-research |
d001 |
|
monad-pipeline |
d001 |
|
research-segmentation |
d001 |
|
Has Assembler — Missing Pieces (high priority)
| Project | Tier | asm | attr | meta | sum | diag |
|---|---|---|---|---|---|---|
bms-controller-segmentation |
d001 |
PASS |
FAIL |
PASS |
✅ |
PASS |
ise-acme-cert-automation |
d001 |
PASS |
✅ |
PASS |
✅ |
FAIL |
siem-qradar-to-sentinel |
d001 |
PASS |
✅ |
FAIL |
❌ |
PASS |
abnormal-security |
d001 |
PASS |
✅ |
FAIL |
❌ |
PASS |
bms-device-inventory |
d001 |
PASS |
FAIL |
❌ |
PASS |
PASS |
ise-annual-cert-renewal |
d001 |
PASS |
✅ |
FAIL |
❌ |
FAIL |
mandiant-remediation |
d001 |
PASS |
✅ |
FAIL |
❌ |
FAIL |
mschapv2-migration |
d001 |
PASS |
✅ |
FAIL |
❌ |
FAIL |
lingua-ponte |
d000 |
PASS |
FAIL |
❌ |
PASS |
PASS |
ender3-v3se |
d000 |
PASS |
FAIL |
PASS |
FAIL |
FAIL |
backups |
d000 |
PASS |
FAIL |
PASS |
FAIL |
FAIL |
Has Assembler — Needs Full Restructure
| Project | Tier |
|---|---|
asa-vpn-okta-to-entra |
d001 |
disaster-recovery |
d001 |
dmz-migration |
d001 |
downtime-computers |
d001 |
ed-sewer-pump |
d001 |
firewall-audit |
d001 |
ipsk-manager-ha |
d001 |
ise-34-migration |
d001 |
ise-hardware-refresh |
d001 |
isolation-malicious-hosts |
d001 |
murus-portae |
d001 |
network-diagram-library |
d001 |
rad-projects |
d001 |
tcp-clocks |
d001 |
No Assembler — Needs Full Scaffold
| Project | Tier |
|---|---|
resume-workshop |
d000 |
security-assessment |
d000 |
subscription-tracker |
d000 |
terminal-warfare |
d000 |
Antora Projects with Data Twins (dual-build)
Projects that exist in both docs/modules/ROOT/pages/projects/ (Antora) and data/d00x/projects/ (standalone). Both builds should work independently.
| Project | Antora Pages | Data Twin | Aligned? |
|---|---|---|---|
thinkpad-t16g |
25 |
d000 |
■■■■■■■■■■ 10/10 |
linux-research |
4 |
d001 |
■■■■■■■■■■ 10/10 |
abnormal-security |
10 |
d001 |
□□□□□□□□□□ 0/10 |
asa-vpn-okta-to-entra |
13 |
d001 |
□□□□□□□□□□ 0/10 |
bms-device-inventory |
3 |
d001 |
□□□□□□□□□□ 0/10 |
siem-qradar-to-sentinel |
8 |
d001 |
□□□□□□□□□□ 0/10 |
ender3-v3se |
11 |
d000 |
□□□□□□□□□□ 0/10 |
mandiant-remediation |
9 |
d001 |
□□□□□□□□□□ 0/10 |
mschapv2-migration |
6 |
d001 |
□□□□□□□□□□ 0/10 |
disaster-recovery |
3 |
d001 |
□□□□□□□□□□ 0/10 |
downtime-computers |
4 |
d001 |
□□□□□□□□□□ 0/10 |
firewall-audit |
4 |
d001 |
□□□□□□□□□□ 0/10 |
ipsk-manager-ha |
4 |
d001 |
□□□□□□□□□□ 0/10 |
ise-34-migration |
4 |
d001 |
□□□□□□□□□□ 0/10 |
ise-annual-cert-renewal |
10 |
d001 |
□□□□□□□□□□ 0/10 |
ise-hardware-refresh |
3 |
d001 |
□□□□□□□□□□ 0/10 |
murus-portae |
3 |
d001 |
□□□□□□□□□□ 0/10 |
network-diagram-library |
1 |
d001 |
□□□□□□□□□□ 0/10 |
research-segmentation |
4 |
d001 |
■■■■■■■■■■ 10/10 |
lingua-ponte |
3 |
d000 |
□□□□□□□□□□ 0/10 |
Antora-Only — High-Value Data Twin Candidates
Projects that only exist in Antora but would benefit from a standalone data/ twin for offline PDF, field use, or ifdef security.
| Project | Antora Pages | Why |
|---|---|---|
ise-home-rotation |
12 |
Active ISE work — standalone PDF for field use |
eve-ng-lab |
11 |
Lab topology — standalone for offline reference |
rhel9-workstation |
17 |
Deployment record — same pattern as thinkpad-t16g |
zfold7-mobile |
15 |
Device setup — portable reference |
domus-mail-lab |
13 |
Email config — sensitive, benefits from |
edu-rhcsa |
27 |
Largest education project — PDF handouts |
edu-cissp |
15 |
Exam prep — portable study material |
Statistics
| Metric | Count |
|---|---|
Total Antora projects |
76 |
Total data/ projects |
34 |
Gold standard (fully compliant) |
5 |
Has assembler, missing pieces |
11 |
Has assembler, needs full restructure |
14 |
No assembler at all |
4 |
Dual-build (Antora + data) |
20 |
Antora-only, high-value twin candidates |
7 |
Audit Command
# Re-run this audit anytime
for dir in data/d00{0,1}/projects/*/; do
slug=$(basename "$dir")
asm=$(find "$dir" -maxdepth 1 -name '*.adoc' -not -name 'README.adoc' -not -name '*.age' | head -1)
attr=$(find "$dir/partials" -name 'attributes.adoc*' 2>/dev/null | head -1)
meta=$(find "$dir/partials" -name 'metadata.adoc*' 2>/dev/null | head -1)
sum=$(find "$dir/partials" -name 'summary.adoc*' 2>/dev/null | head -1)
diag=$([ -d "$dir/diagrams" ] && echo "Y" || echo "")
printf "%-35s asm:%-3s attr:%-3s meta:%-3s sum:%-3s diag:%-3s\n" \
"$slug" \
"$([ -n "$asm" ] && echo '✅' || echo '❌')" \
"$([ -n "$attr" ] && echo '✅' || echo '❌')" \
"$([ -n "$meta" ] && echo '✅' || echo '❌')" \
"$([ -n "$sum" ] && echo '✅' || echo '❌')" \
"$([ -n "$diag" ] && echo '✅' || echo '❌')"
done
Data Tier Audit
Data Tier Audit — Encryption & Structure
Ran a full audit of data/ as an AsciiDoc-adjacent system to docs/modules/.
Three categories of findings: security gaps, missing tooling, and structural drift.
Security Findings
| Finding | Detail | Severity |
|---|---|---|
Plaintext residue on disk |
260 files exist in both plaintext and |
HIGH |
BMS diagram sources tracked in plaintext |
|
HIGH |
|
World-readable (755). Combined with plaintext residue, any local user can read decrypted personal legal/medical documents. |
MEDIUM |
|
|
MEDIUM |
Remediation Plan
| Action | Command / Approach | Status |
|---|---|---|
Bulk-clean plaintext residue |
Review, then |
□□□□□□□□□□ 0/10 |
Encrypt BMS diagram sources |
Move |
□□□□□□□□□□ 0/10 |
Fix d000/personal permissions |
|
□□□□□□□□□□ 0/10 |
Create |
Model on |
□□□□□□□□□□ 0/10 |
Pre-commit hook for residue detection |
Hook that runs |
□□□□□□□□□□ 0/10 |
Structural Drift — d000
CLAUDE.md documents 5 subdirectories. Actual: 16. Undocumented directories:
-
commands/,correspondencia/,creative/,cursus/,daily-issues/ -
infrastructure/(duplicatesinfra/),investigations/,lpl-study/ -
palaestra/,scripts/,shell-history/,templates/
Decision needed: update CLAUDE.md to reflect reality, or consolidate directories.
STD-001 Non-Compliance in d001 Projects
22 of 24 projects lack metadata.adoc. 23 lack summary.adoc. 21 lack root assembler .adoc. Only bms-controller-segmentation is fully compliant. This is technical debt — not a security issue — but blocks build-antora-page.sh for most d001 projects.
Root Cause
The d001 close workflow targets partials/*.adoc only. It does not:
-
Scan
diagrams/,output/,scripts/,config-snapshots/for non-.agesensitive files -
Clean up plaintext after re-encryption
-
Warn when plaintext residue exists
The d000 close function has the same limitation — .adoc-only scope.
Both functions need a broader sweep: any non-README, non-.age file in a d00x project directory should be flagged during close.
Project Tracker Reconciliation
Project Tracker Reconciliation
Cross-referenced data/project-master.yaml, project-master.adoc, and codes.adoc to ensure all work projects are tracked and billable.
Query Toolkit
Full script with 5 sections (57 drills): indexes, billing, filters, deps, reports.
bash docs/modules/ROOT/examples/yq-drills/06-project-master.sh
bash docs/modules/ROOT/examples/yq-drills/06-project-master.sh indexes
bash docs/modules/ROOT/examples/yq-drills/06-project-master.sh billing
bash docs/modules/ROOT/examples/yq-drills/06-project-master.sh filters
bash docs/modules/ROOT/examples/yq-drills/06-project-master.sh deps
bash docs/modules/ROOT/examples/yq-drills/06-project-master.sh reports
bash docs/modules/ROOT/examples/yq-drills/00-run-drill.sh 06
Script Source
#!/usr/bin/env bash
# YQ DRILL 06: PROJECT MASTER — REAL DATA QUERIES
# Usage: bash 06-project-master.sh [section]
# bash 06-project-master.sh # Run all sections
# bash 06-project-master.sh indexes # Pre-built index queries only
# bash 06-project-master.sh billing # PeopleSoft / time submission only
# bash 06-project-master.sh filters # Dynamic yq filters on projects[]
# bash 06-project-master.sh deps # Dependency chains and blockers
# bash 06-project-master.sh reports # Aggregate reports
#
# Data source: data/project-master.yaml (48 projects, 4 operational codes)
# Companion: partials/worklog/daily-notes/2026-06-22/project-tracker-reconciliation.adoc
set -euo pipefail
readonly YAML="data/project-master.yaml"
readonly DIVIDER="=================================================================="
readonly SUBDIV="------------------------------------------------------------------"
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
die() { printf "FATAL: %s\n" "$1" >&2; exit 1; }
header() {
printf "\n%s\n %s\n%s\n\n" "$DIVIDER" "$1" "$DIVIDER"
}
drill() {
local label="$1"; shift
local cmd="$*"
printf "%s\n" "$SUBDIV"
printf " %s\n" "$label"
printf " CMD: %s\n" "$cmd"
printf "%s\n\n" "$SUBDIV"
eval "$cmd"
printf "\n"
}
# ---------------------------------------------------------------------------
# Validate
# ---------------------------------------------------------------------------
command -v yq > /dev/null || die "yq not found — install with: pacman -S yq"
if [[ ! -f "$YAML" ]]; then
# Try from repo root
if [[ -f "$HOME/atelier/_bibliotheca/domus-captures/$YAML" ]]; then
cd "$HOME/atelier/_bibliotheca/domus-captures"
else
die "$YAML not found. Run from domus-captures root."
fi
fi
yq '.' "$YAML" > /dev/null || die "$YAML is invalid YAML"
# ---------------------------------------------------------------------------
# Section: INDEXES — pre-built arrays, no filters needed
# ---------------------------------------------------------------------------
run_indexes() {
header "SECTION 1: PRE-BUILT INDEX QUERIES"
drill "1.01 — All P0 projects" \
"yq '.by_priority.P0[]' $YAML"
drill "1.02 — All P1 projects" \
"yq '.by_priority.P1[]' $YAML"
drill "1.03 — All P2 projects" \
"yq '.by_priority.P2[]' $YAML"
drill "1.04 — Overdue projects" \
"yq '.by_status.overdue[]' $YAML"
drill "1.05 — Blocked projects" \
"yq '.by_status.blocked[]' $YAML"
drill "1.06 — Active projects" \
"yq '.by_status.active[]' $YAML"
drill "1.07 — Behind schedule" \
"yq '.by_status.behind[]' $YAML"
drill "1.08 — TODO (not started)" \
"yq '.by_status.todo[]' $YAML"
drill "1.09 — Complete" \
"yq '.by_status.complete[]' $YAML"
drill "1.10 — Security auth projects" \
'yq '"'"'.by_category["security-auth"][]'"'"' '"$YAML"
drill "1.11 — Security compliance projects" \
'yq '"'"'.by_category["security-compliance"][]'"'"' '"$YAML"
drill "1.12 — Network infrastructure projects" \
'yq '"'"'.by_category["network-infra"][]'"'"' '"$YAML"
drill "1.13 — SIEM projects" \
"yq '.by_category.siem[]' $YAML"
drill "1.14 — Platform/cloud projects" \
'yq '"'"'.by_category["platform-cloud"][]'"'"' '"$YAML"
drill "1.15 — Deployments" \
"yq '.by_category.deployments[]' $YAML"
drill "1.16 — Documentation projects" \
"yq '.by_category.documentation[]' $YAML"
drill "1.17 — Evan's projects" \
"yq '.by_owner.evan[]' $YAML"
drill "1.18 — Vanessa Ahn's projects (bracket notation for hyphens)" \
'yq '"'"'.by_owner["vanessa-ahn"][]'"'"' '"$YAML"
drill "1.19 — Tony Sun's projects" \
'yq '"'"'.by_owner["tony-sun"][]'"'"' '"$YAML"
drill "1.20 — Alexander Urasaki's projects" \
'yq '"'"'.by_owner["alexander-urasaki"][]'"'"' '"$YAML"
drill "1.21 — Blocker map (what blocks what)" \
"yq '.blockers' $YAML"
drill "1.22 — Aging past 90 days" \
"yq '.aging.critical[]' $YAML"
drill "1.23 — Dependency chains" \
"yq '.dependency_chains[]' $YAML"
}
# ---------------------------------------------------------------------------
# Section: BILLING — PeopleSoft codes, time submission
# ---------------------------------------------------------------------------
run_billing() {
header "SECTION 2: PEOPLESOFT / TIME SUBMISSION"
drill "2.01 — Admin codes (account, fund, dept, PC unit)" \
"yq '.metadata.peoplesoft' $YAML"
drill "2.02 — Projects WITH PeopleSoft codes (billable today)" \
"yq -r '.projects[] | select(.peoplesoft.code != null) | \"\(.slug)\t\(.peoplesoft.code)\t\(.peoplesoft.combo // \"-\")\"' $YAML | column -t"
drill "2.03 — Billable index (shortcut)" \
"yq '.billable.with_code[]' $YAML"
drill "2.04 — Needs code assignment" \
"yq '.billable.needs_code[]' $YAML"
drill "2.05 — Uses parent code (bill to parent)" \
"yq '.billable.uses_parent[]' $YAML"
drill "2.06 — Not billable yet (blocked/todo with no code)" \
"yq '.billable.not_billable_yet[]' $YAML"
drill "2.07 — BMS sub-projects with their own codes" \
"yq '.projects[] | select(.sub_projects) | .sub_projects[]' $YAML"
drill "2.08 — Operational codes (admin/meetings/support)" \
"yq '.operational_codes[]' $YAML"
drill "2.09 — Count: billable vs needs-code vs not-yet" \
"printf 'billable: %d\nneeds_code: %d\nuses_parent: %d\nnot_billable: %d\n' \
\$(yq '.billable.with_code | length' $YAML) \
\$(yq '.billable.needs_code | length' $YAML) \
\$(yq '.billable.uses_parent | length' $YAML) \
\$(yq '.billable.not_billable_yet | length' $YAML)"
drill "2.10 — Projects with PM assigned" \
"yq -r '.projects[] | select(.peoplesoft.pm != null) | \"\(.slug)\t\(.peoplesoft.pm)\"' $YAML | column -t"
drill "2.11 — Projects with combo codes" \
"yq -r '.projects[] | select(.peoplesoft.combo != null) | \"\(.slug)\t\(.peoplesoft.code)\t\(.peoplesoft.combo)\"' $YAML | column -t"
drill "2.12 — Blank timesheet weeks" \
"for f in docs/modules/ROOT/partials/trackers/work/peoplesoft-codes/timesheet-*.adoc; do
blank=\$(grep -c '_____' \"\$f\" 2>/dev/null || true)
printf '%s\tblank: %d\n' \"\$(basename \"\$f\" .adoc)\" \"\$blank\"
done"
}
# ---------------------------------------------------------------------------
# Section: FILTERS — dynamic yq queries on projects[]
# ---------------------------------------------------------------------------
run_filters() {
header "SECTION 3: DYNAMIC FILTERS ON projects[]"
drill "3.01 — Total project count" \
"yq '.projects | length' $YAML"
drill "3.02 — All slugs (sorted)" \
"yq -r '.projects[].slug' $YAML | sort"
drill "3.03 — All unique statuses" \
"yq -r '.projects[].status' $YAML | sort -u"
drill "3.04 — All unique categories" \
"yq -r '.projects[].category' $YAML | sort -u"
drill "3.05 — All unique priorities" \
"yq -r '.projects[].priority' $YAML | sort -u"
drill "3.06 — All unique owners (flattened)" \
"yq -r '.projects[].owners[]' $YAML | sort -u"
drill "3.07 — All unique tools in use" \
"yq -r '.projects[].tools[]' $YAML | sort -u"
drill "3.08 — Projects with d001 encrypted data" \
"yq -r '.projects[] | select(.d001 != null) | \"\(.slug)\t\(.d001)\"' $YAML | column -t"
drill "3.09 — Projects with Antora docs" \
"yq -r '.projects[] | select(.docs != null) | \"\(.slug)\t\(.docs)\"' $YAML | column -t"
drill "3.10 — Projects with NO d001 AND no docs (undocumented)" \
"yq -r '.projects[] | select(.d001 == null and .docs == null) | .slug' $YAML"
drill "3.11 — Projects by status count" \
"yq -r '.projects[].status' $YAML | sort | uniq -c | sort -rn"
drill "3.12 — Projects by category count" \
"yq -r '.projects[].category' $YAML | sort | uniq -c | sort -rn"
drill "3.13 — Projects by priority count" \
"yq -r '.projects[].priority // \"unset\"' $YAML | sort | uniq -c | sort -rn"
drill "3.14 — Projects with milestones" \
"yq '.projects[] | select(.milestones) | {\"slug\": .slug, \"milestones\": .milestones}' $YAML"
drill "3.15 — Projects originated in June 2026 (test() regex, not glob)" \
"yq -r '.projects[] | select(.origin != null and (.origin | test(\"^2026-06\"))) | \"\(.slug)\t\(.origin)\"' $YAML | column -t"
drill "3.16 — Projects with days > 90 (aging)" \
"yq -r '.projects[] | select(.days != null and .days > 90) | \"\(.days)d\t\(.priority)\t\(.slug)\"' $YAML | sort -rn | column -t"
drill "3.17 — Projects with days > 60 and no PeopleSoft code" \
"yq -r '.projects[] | select(.days != null and .days > 60 and .peoplesoft.code == null) | \"\(.days)d\t\(.slug)\"' $YAML | sort -rn | column -t"
drill "3.18 — P0 projects that are NOT active (stuck)" \
"yq -r '.projects[] | select(.priority == \"P0\" and .status != \"active\") | \"\(.status)\t\(.slug)\"' $YAML | column -t"
drill "3.19 — Projects that block others (from projects[] depends_on)" \
"yq -r '.projects[] | select(.blocks | length > 0) | \"\(.slug) blocks: \(.blocks | join(\", \"))\"' $YAML"
drill "3.20 — Projects that depend on something" \
"yq -r '.projects[] | select(.depends_on | length > 0) | \"\(.slug) needs: \(.depends_on | join(\", \"))\"' $YAML"
drill "3.21 — Full detail for a single project (e.g. ise-patch-10)" \
"yq '.projects[] | select(.slug == \"ise-patch-10\")' $YAML"
drill "3.22 — JSON output (Python yq already outputs JSON)" \
"yq '.projects[] | select(.slug == \"abnormal-security\")' $YAML"
drill "3.23 — TSV export: slug, priority, status, days, code" \
"yq -r '.projects[] | \"\(.slug)\t\(.priority // \"-\")\t\(.status)\t\(.days // \"-\")\t\(.peoplesoft.code // \"-\")\"' $YAML | column -t | head -20"
drill "3.24 — Projects using ISE tools" \
"yq -r '.projects[] | select(.tools[] == \"ise-ers\" or .tools[] == \"ise-dataconnect\" or .tools[] == \"ise-radius\") | .slug' $YAML 2>/dev/null || echo '(none match — tools[] empty on some entries)'"
drill "3.25 — Multi-owner projects" \
"yq -r '.projects[] | select(.owners | length > 1) | \"\(.slug)\t\(.owners | join(\", \"))\"' $YAML | column -t"
}
# ---------------------------------------------------------------------------
# Section: DEPS — dependency chains and blocker analysis
# ---------------------------------------------------------------------------
run_deps() {
header "SECTION 4: DEPENDENCIES & BLOCKERS"
drill "4.01 — All dependency chains" \
"yq '.dependency_chains[] | \"\(.label): \(.chain | join(\" → \"))\"' $YAML"
drill "4.02 — Blocker map" \
"yq '.blockers' $YAML"
drill "4.03 — What does ise-patch-10 block?" \
'yq '"'"'.blockers["ise-patch-10"][]'"'"' '"$YAML"
drill "4.04 — What does k3s-nat block?" \
'yq '"'"'.blockers["k3s-nat"][]'"'"' '"$YAML"
drill "4.05 — What does linux-research block?" \
'yq '"'"'.blockers["linux-research"][]'"'"' '"$YAML"
drill "4.06 — What does monad-pipeline block?" \
'yq '"'"'.blockers["monad-pipeline"][]'"'"' '"$YAML"
drill "4.07 — What does ise-annual-cert-renewal block?" \
'yq '"'"'.blockers["ise-annual-cert-renewal"][]'"'"' '"$YAML"
drill "4.08 — Projects with zero blockers and zero dependencies (independent)" \
"yq -r '.projects[] | select((.depends_on | length == 0) and (.blocks | length == 0)) | .slug' $YAML"
drill "4.09 — Blocked projects with their blockers" \
"yq -r '.projects[] | select(.status == \"blocked\") | \"\(.slug)\tblocked by: \(.depends_on | join(\", \"))\"' $YAML | column -t"
drill "4.10 — Chain depth: projects that block something that blocks something else" \
"yq -r '.dependency_chains[] | select(.chain | length > 2) | \"\(.chain | join(\" → \"))\t(\(.label))\"' $YAML | column -t"
}
# ---------------------------------------------------------------------------
# Section: REPORTS — aggregate views
# ---------------------------------------------------------------------------
run_reports() {
header "SECTION 5: AGGREGATE REPORTS"
drill "5.01 — Portfolio summary" \
"printf 'Total projects: %d\nWith PS code: %d\nNeeds code: %d\nOverdue: %d\nBlocked: %d\nActive: %d\nComplete: %d\nOperational codes: %d\n' \
\$(yq '.projects | length' $YAML) \
\$(yq '.billable.with_code | length' $YAML) \
\$(yq '.billable.needs_code | length' $YAML) \
\$(yq '.by_status.overdue | length' $YAML) \
\$(yq '.by_status.blocked | length' $YAML) \
\$(yq '.by_status.active | length' $YAML) \
\$(yq '.by_status.complete | length' $YAML) \
\$(yq '.operational_codes | length' $YAML)"
drill "5.02 — Metadata" \
"yq '.metadata' $YAML"
drill "5.03 — Weekly time submission checklist" \
"printf '=== Billable (have codes) ===\n'
yq -r '.projects[] | select(.peoplesoft.code != null) | \" ✓ \(.name) [\(.peoplesoft.code)]\"' $YAML
printf '\n=== Bill to parent ===\n'
yq -r '.billable.uses_parent[]' $YAML | while read -r slug; do printf ' → %s\n' \"\$slug\"; done
printf '\n=== NEEDS CODE (hours are lost) ===\n'
yq -r '.billable.needs_code[]' $YAML | while read -r slug; do printf ' ✗ %s\n' \"\$slug\"; done"
drill "5.04 — Risk register: overdue + aging + blocked P0s" \
"printf '%s\n' '=== OVERDUE ==='
yq -r '.projects[] | select(.status == \"overdue\") | \" \(.days)d \(.slug) \(.details)\"' $YAML
printf '\n%s\n' '=== AGING > 90d ==='
yq -r '.projects[] | select(.days != null and .days > 90) | \" \(.days)d \(.slug)\"' $YAML | sort -rn
printf '\n%s\n' '=== BLOCKED P0 ==='
yq -r '.projects[] | select(.priority == \"P0\" and .status == \"blocked\") | \" \(.slug) needs: \(.depends_on | join(\", \"))\"' $YAML"
drill "5.05 — Full TSV export (pipe to file or column)" \
"printf 'slug\tpriority\tstatus\tdays\tcategory\tcode\n'
yq -r '.projects[] | \"\(.slug)\t\(.priority // \"-\")\t\(.status)\t\(.days // \"-\")\t\(.category)\t\(.peoplesoft.code // \"-\")\"' $YAML | column -t"
drill "5.06 — YAML validation" \
"yq '.' $YAML > /dev/null && echo 'YAML valid — 0 errors' || echo 'YAML BROKEN'"
drill "5.07 — Index consistency check (with_code count vs actual)" \
"printf 'Index says: %d billable\n' \$(yq '.billable.with_code | length' $YAML)
printf 'Actual: %d have .peoplesoft.code\n' \$(yq '[.projects[] | select(.peoplesoft.code != null)] | length' $YAML)
index=\$(yq '.billable.with_code | length' $YAML)
actual=\$(yq '[.projects[] | select(.peoplesoft.code != null)] | length' $YAML)
if [[ \"\$index\" -eq \"\$actual\" ]]; then
echo 'CONSISTENT ✓'
else
echo 'DRIFT DETECTED — indexes out of sync with projects[]'
fi"
}
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
main() {
printf "%s\n" "$DIVIDER"
printf " PROJECT MASTER — YQ QUERY TOOLKIT\n"
printf " Data: %s (%d projects)\n" "$YAML" "$(yq '.projects | length' "$YAML")"
printf " Date: %s\n" "$(date +%Y-%m-%d)"
printf "%s\n" "$DIVIDER"
local section="${1:-all}"
case "$section" in
indexes) run_indexes ;;
billing) run_billing ;;
filters) run_filters ;;
deps) run_deps ;;
reports) run_reports ;;
all)
run_indexes
run_billing
run_filters
run_deps
run_reports
;;
*)
printf "Unknown section: %s\n\n" "$section"
printf "Sections: indexes | billing | filters | deps | reports | all\n"
exit 1
;;
esac
printf "\n%s\n DONE — %d drills executed\n%s\n" "$DIVIDER" "$(grep -c '^ drill ' "$0")" "$DIVIDER"
}
main "$@"
Quick Reference — Most Used Queries
yq '.billable.with_code[]' data/project-master.yaml
yq '.billable.needs_code[]' data/project-master.yaml
yq '.by_priority.P0[]' data/project-master.yaml
yq '.blockers' data/project-master.yaml
yq '.aging.critical[]' data/project-master.yaml
yq '.by_owner["vanessa-ahn"][]' data/project-master.yaml
bash docs/modules/ROOT/examples/yq-drills/06-project-master.sh reports
Changes Made
-
data/project-master.yaml— addedpeoplesoft:fields to every project. 7 pre-built index arrays (by_status,by_priority,by_category,by_owner,billable,blockers,aging). 11 new projects. Total: 48 projects + 4 operational codes. -
project-master.adoc— added 11 missing projects + new Deployments & Inventory section. -
codes.adoc— expanded to all 48 projects. Flagged NEEDS CODE where hours are billed without a PeopleSoft code. -
examples/yq-drills/06-project-master.sh— 57 drills across 5 sections. Registered in drill runner.
Gap Summary
| Metric | Value | Notes |
|---|---|---|
Projects with PS codes |
8 |
Can submit time today |
Active projects without codes |
28 |
Hours can’t land in PeopleSoft |
Estimated unbillable hours/week |
~19 (~47%) |
Change Mgmt, ISE Policy, Cert Renewal, Tube System, Admin |
Stale priority files (p0/p1/p2.adoc) |
3 |
Last updated April/May — contradict master |
Next Steps
-
Get PeopleSoft codes for ISE Annual Cert Renewal, Tube System, Change Management, ISE Policy / General Support
-
Confirm which parent codes cover sub-projects (Monad → SIEM 000018281? rsyslog → SIEM?)
-
Create missing timesheets for weeks of 06-08, 06-15, 06-22
-
Decide: deprecate
p0.adoc/p1.adoc/p2.adocor sync with master
Git Glob Pathspec — Filter by File Type
Git Glob Pathspec — Filter by File Type
git status --short -- ':(glob)**/*.svg' ':(glob)**/*.png'
firefox $(git status --short -- ':(glob)**/*.svg' ':(glob)**/*.png' | awk '{print $2}')
:(glob) is a git pathspec magic — it tells git to interpret /*.svg as a glob pattern, not a literal path. Without it, doesn’t recurse. The --short flag gives clean M path/to/file output that awk '{print $2}' can extract.
|
# All modified AsciiDoc files
git status --short -- ':(glob)**/*.adoc'
# All modified shell scripts
git status --short -- ':(glob)**/*.sh'
# All modified files in a specific project
git status --short -- ':(glob)data/d001/projects/monad-pipeline/**'
# Diff only diagram sources (dot + d2)
git diff --stat -- ':(glob)**/*.dot' ':(glob)**/*.d2'
# Stage only SVG outputs (after diagram render)
git add -- ':(glob)**/*.svg'
The power: git pathspec lets you slice your working tree by file type without find | grep. One command, no pipes, git-native.
CLI — Searching Daily Notes & Partials
CLI — Searching Daily Notes & Partials
The Problem
Needed to find build-antora-page.sh invocations and --theme usage across daily note partials. The worklog tree has hundreds of .adoc files across date-organized directories — manual browsing doesn’t scale.
Pattern Escalation — Narrow to Broad
grep -rl 'build-antora-page' docs/modules/ROOT/partials/worklog/
grep -rn -C3 'build-antora-page' docs/modules/ROOT/partials/worklog/
grep -rPn 'theme\s+(light-cyan|catppuccin|mocha)' docs/modules/ROOT/partials/
find docs/modules/ROOT/partials/worklog/daily-notes -name '*.adoc' \
-exec grep -l 'build-antora-page\|build-adoc\|--theme\|pdf' {} \;
find docs/modules/ROOT/partials/worklog/daily-notes -name '*.adoc' \
-exec awk '/build-antora-page|build-adoc|--theme/{print FILENAME": "$0}' {} \;
find docs/modules/ROOT/partials -name '*.adoc' -exec \
awk '/^\[source,bash\]/{block=1; buf=""} \
block{buf=buf"\n"$0} \
/^----$/ && block>1{if(buf~/build-antora-page/) print FILENAME":"buf; block=0} \
block{block++}' {} \;
How the state machine works: [source,bash] sets block=1 and starts buffering. Each line appends to buf. When the closing ---- arrives (block>1 skips the opening fence), check if the buffer contains the target. Print if yes. Reset. One pass, arbitrary block size, no temp files.
find docs/modules/ROOT/partials/worklog -name '*.adoc' -exec \
sed -n '/\[source,bash\]/,/^----$/{/build-antora-page/p}' {} +
grep -rn 'build-antora-page\|build-adoc' data/ --include='*.adoc'
grep -rn 'build-antora-page' docs/modules/ROOT/partials/worklog/daily-notes/ \
| awk -F: '{print $1, $3}' | sort
Tool Affordances — When to Reach for What
| Tool | Affordance | When to Use |
|---|---|---|
|
"Which files contain this?" |
First pass — locate the files before reading them |
|
"What’s around the match?" |
Quick context without opening the file |
|
"Match complex patterns" |
PCRE alternation, lookaheads, |
|
"Search a specific tree with fine-grained file filters" |
When |
|
"Extract structured content around a match" |
Code blocks, tables, multi-line patterns — awk tracks state |
|
"Print between delimiters" |
AsciiDoc fences, config sections, log ranges |
|
"Extract variable-length blocks by content" |
When sed address ranges aren’t enough — buffer + conditional print |
Lesson Learned: tree Takes Paths, Not Rendered Text
tree $(find -type d -name 'daily-notes' | xargs tree | grep 2026-06-22)
# Error: "└──": No such file or directory
# Error: "2026-06-22.adoc": No such file or directory
Why it broke: The inner xargs tree renders a tree diagram with unicode box-drawing characters (├──, └──). grep filters those text lines. The outer tree $(…) tries to open └── and 2026-06-22.adoc as literal filesystem paths — they’re not paths, they’re display text.
# Direct — you know the path
tree docs/modules/ROOT/partials/worklog/daily-notes/2026-06/2026-06-22/
# find locates, tree renders — one step
find -type d -name '2026-06-22' -exec tree {} \;
# find locates, command substitution feeds the path to tree
tree $(find -type d -name '2026-06-22')
The principle: tree consumes paths. find produces paths. tree output is display text — never feed it back into another command that expects paths. The pipeline is find → tree, never tree → grep → tree.
Antora vs Standalone Build
Antora vs Standalone Build
Antora builds the entire site to HTML. There is no single-page PDF export through the Antora pipeline. antora generate is all or nothing.
Antora Pipeline (HTML only, full site)
cd ~/atelier/_bibliotheca/domus-docs
make
make serve
Output: docs.domusdigitalis.dev (via Cloudflare Pages on push) or localhost:8000 (local).
Single-Page PDF (build-antora-page.sh)
build-antora-page.sh resolves partial$ and example$ includes into a flat .adoc, then passes to build-adoc for PDF rendering. This is the only way to get a PDF of an Antora page.
make export-today
make pdf FILE=docs/modules/ROOT/pages/2026/06/WRKLOG-2026-06-22.adoc
./scripts/build-antora-page.sh docs/modules/ROOT/pages/2026/06/WRKLOG-2026-06-22.adoc pdf --theme light-cyan
make export FILE=docs/modules/ROOT/pages/2026/06/WRKLOG-2026-06-22.adoc
make export-month
How the Three-Layer Resolution Works
make pdf FILE=pages/.../WRKLOG-2026-06-22.adoc
→ build-antora-page.sh
→ rewrites partial$ → docs/modules/ROOT/partials/...
→ rewrites example$ → docs/modules/ROOT/examples/...
→ assembles flat .adoc (all includes inlined)
→ passes to build-adoc
→ asciidoctor-pdf with light-cyan theme
→ output/ directory
Layer 1 (worklog shell) includes Layer 2 (daily notes index), which includes Layer 3 (concern partials). build-antora-page.sh flattens the entire chain.
What Does NOT Work
# WRONG — no such option
antora generate --page WRKLOG-2026-06-22.adoc
# WRONG — build-adoc cannot resolve partial$ syntax
build-adoc docs/modules/ROOT/pages/2026/06/WRKLOG-2026-06-22.adoc pdf
Available Themes
| Theme | Use |
|---|---|
|
Default — worklogs, daily notes, project docs |
|
Domus Digitalis branded |
|
Minimal |
|
Requires Noto Sans font dir |
make pdf FILE=docs/modules/ROOT/pages/2026/06/WRKLOG-2026-06-22.adoc PDF_THEME=catppuccin
Language ↔ Project Map
Language ↔ Project Relationship Map
What You Already Have
21 projects, 34 documentation repos, 55 education directories. Six languages already in active use. This isn’t a plan — it’s an inventory of what exists.
The Language Map
LANGUAGE WHERE IT LIVES WHAT IT DOES FOR YOU
────────────── ────────────────────────────────────────── ─────────────────────────────────
Bash/Shell dots-quantum (200+ functions in .zshrc) Your daily driver — d000/d001,
email-config (OAuth2 automation) encryption workflows, dig loops,
ansible-linux-automation (backup/restore) git push patterns, pipeline
ollama-local (GPU model scripts) composition. ALREADY STRONG.
data/d000/education/c/ (build scripts)
data/shared/scripts/
Python netapi (23 vendor integrations) Work deliverables — ISE ERS,
netapi-tui (terminal automation toolkit) DataConnect, Monad pipeline,
domus-cli (SSH orchestration) API automation, data processing.
domus-api (FastAPI backend) ALREADY PRODUCING.
ise-automation (Ansible modules)
terminal-warfare (raycasting engine)
association-engine (knowledge graph)
ollama-local (LLM query scripts)
Lua domus-nvim (~90 plugins, 5-layer arch) Your editor IS Lua. Your terminal
instrumentum-nvim (reusable components) config IS Lua. Wireshark
domus-minimus (lightweight nvim) dissectors ARE Lua. Every time
wezterm config you edit init.lua, you're
tmux-quantum (shell integration) programming. NOT A BEGINNER.
SQL ISE DataConnect queries (d001) Every database you touch — ISE,
Monad pipeline transforms QRadar, Sentinel, inventory.
data/shared/ (query libraries) ALREADY WRITING QUERIES.
data/d001/projects/*/scripts/*.sql
C data/d000/education/c/ (11-phase curriculum) The foundation beneath every
Keylogger project (evdev, /proc) tool you use. strace, /proc,
syscalls, buffer overflows.
COMMITTED — 11 PHASES.
JavaScript/TS domus-digitalis (Next.js 15 + React 19) The web app. Full stack with
obsidian-asciidoc-viewer (React plugin) PostgreSQL + Redis. EXISTS
but not your primary domain.
HCL domus-terraform Infrastructure as Code. EXISTS.
Rust crypta (educational roguelike) Embryonic. After Go.
Go (minimal — 13 lines) FUTURE — after C.
How the Languages Connect Through Your Projects
┌─────────────┐
│ You (CLI) │
└──────┬───────┘
│
┌──────────────┼──────────────┐
│ │ │
┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐
│ Bash │ │ Python │ │ Lua │
│ (compose) │ │ (deliver) │ │(configure)│
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
┌───────────────┤ ┌────────┤ ┌─────────┤
│ │ │ │ │ │
dots-quantum d000/d001 netapi Monad domus- wezterm
email-config workflows netapi- pipeln nvim tmux-
ollama-local dig loops tui domus- instru- quantum
backup/restore ISE-auto api mentum
assoc-eng
│ │ │
└──────────────┼──────────────┘
│
┌──────┴───────┐
│ SQL │
│ (query) │
└──────┬───────┘
│
ISE DataConnect, Monad transforms,
QRadar, Sentinel, inventory systems
│
┌──────┴───────┐
│ C │
│ (understand) │
└──────┬───────┘
│
strace, /proc, syscalls, evdev (keylogger),
buffer overflows (CVE-2026-55200 makes sense),
implement grep/wc/find, kernel modules
│
┌──────┴───────┐
│ Go │
│ (build) │
└──────┬───────┘
│
Static CLI binaries, infrastructure tools,
Vault/Consul/Docker plugins, network servers
The Keylogger — Where It Fits
The keylogger project connects C, the kernel, and your security work:
Keyboard HW → IRQ → kernel input subsystem → /dev/input/eventN → evdev → your C program
↓
struct input_event {
struct timeval time;
__u16 type; // EV_KEY
__u16 code; // KEY_A
__s32 value; // 1=press, 0=release
}
What you learn building it:
-
open(),read()syscalls — reading raw device files -
structpacking — C structs mapping kernel data structures -
/dev/input/— the Linux input subsystem -
ioctl()— device control (grab exclusive access) -
File permissions —
/dev/input/event*requires root orinputgroup -
select()/poll()— multiplexing I/O from multiple devices -
straceto see your own program’s syscalls -
Security implications — why input devices need permission controls
This is week 23–28 material (C + syscalls) but you’re already drawn to it. That’s fine — motivation beats curriculum order.
What Fascinates You — The Common Thread
You listed: APIs, system calls, arrays, bash scripting, mathematics, Python, Go, Lua, looping, pipelines.
The common thread is transformation. Every one of those is about taking data in one form and producing it in another:
| Concept | Transformation |
|---|---|
Pipelines |
|
APIs |
|
Syscalls |
|
Arrays |
|
Loops |
|
SQL |
|
Regex |
|
C structs |
|
You’re not learning 8 different things. You’re learning one thing — how data flows through systems — expressed in 8 different syntaxes. The keylogger is keyboard events flowing through the kernel. The Monad pipeline is syslog data flowing through transforms. The grep \| awk \| sort pipeline is text flowing through filters. Same pattern. Different layer.
Ecosystem Statistics
| Metric | Count |
|---|---|
Personal projects |
21 |
Documentation repos |
34 |
Education directories |
55 |
Languages in active use |
6 (Bash, Python, Lua, SQL, JS/TS, HCL) |
Languages in curriculum |
3 more (C, Go, Rust) |
Total repos under ~/atelier |
55+ |
Vendor integrations (netapi) |
23 |
Neovim plugins managed |
~90 |
Literary study directories |
32 |
Natural languages studied |
5+ (English, Spanish, Latin, Hebrew, Italian) |
This Is Not Crazy
An engineer who:
-
writes ISE automation in Python
-
composes data pipelines in bash
-
configures their editor in Lua
-
queries infrastructure in SQL
-
studies C to understand the kernel beneath
-
plans Go for infrastructure tooling
-
builds a keylogger to understand input events
-
reads Cervantes in the original Spanish
-
studies Latin rhetoric
-
practices violin
…is not scattered. Every item on that list feeds the others. The Spanish feeds the Latin. The Latin feeds the rhetoric. The rhetoric feeds the documentation. The C feeds the strace. The strace feeds the debugging. The debugging feeds the incident response. The incident response feeds the automation. The automation feeds the Python. The Python feeds the APIs. The APIs feed the pipelines.
It’s one system. You just see more of it than most people.
Unix Tool Panorama + CLI CTF
The Unix Text Processing Lineage
Every tool below exists because someone needed to solve a problem that the previous tools couldn’t. The progression isn’t arbitrary — it’s an argument about where complexity should live.
The Lineage
ERA TOOL/CONCEPT BORN CREATOR WHAT IT SETTLED
─────────── ────────────── ───── ───────────────── ──────────────────────────────────────
=== LANGUAGES (live inside the tools) ===
Substrate regex (BRE) 1956 Stephen Kleene Pattern matching — the grammar of search
regex (ERE) 1968 POSIX Extended: +, ?, |, () without escaping
regex (PCRE) 1997 Philip Hazel Lookahead, lookbehind, \K, non-greedy, Unicode
glob patterns 1971 Bell Labs Shell filename expansion: *, ?, [a-z], **
brace expan. 1978 csh / Bill Joy {a,b,c} and {1..10} — generate sequences
param expan. 1977 Bourne Shell ${var:-default}, ${var##*/}, ${#var} — string ops in-shell
exit codes 1971 Unix v1 0=success, 1-255=failure — the boolean of pipes
signals 1971 Unix v1 SIGTERM, SIGKILL, SIGHUP, trap — process communication
file descr. 1971 Unix v1 0=stdin, 1=stdout, 2=stderr, 2>&1, /dev/fd/
=== TOOLS (operate on streams) ===
Primordial ed 1969 Ken Thompson Line-addressable editing (no screen)
grep 1973 Ken Thompson Pattern search — born from ed's g/re/p
egrep 1979 Alfred Aho ERE support (now grep -E)
sort 1970 Bell Labs Lexicographic & numeric ordering
uniq 1970 Bell Labs Adjacent duplicate elimination
wc 1971 Bell Labs Counting: lines, words, bytes
cat 1971 Bell Labs Concatenate streams
head/tail 1971 Bell Labs First/last N lines
tee 1974 Bell Labs Split a stream: file + stdout
tr 1973 Bell Labs Character-level transliteration
Classical sed 1974 Lee McMahon Stream editing — ed without interaction
awk 1977 Aho/Weinberger/ Column processing, pattern-action,
Kernighan BEGIN/END blocks, associative arrays
find 1974 Bell Labs Filesystem traversal with predicates
cut/paste 1975 Bell Labs Column extraction / horizontal merge
diff 1974 Hunt/McIlroy Line-by-line comparison
comm 1973 Bell Labs Set operations on sorted files
xargs 1978 Bell Labs Stdin → arguments (batching, parallel)
file 1973 Bell Labs Content-type detection (not extension)
strings 1978 Bell Labs Extract printable from binary
test / [ 1971 Bell Labs Conditional evaluation in shell
expr 1971 Bell Labs Arithmetic and string ops (before $(()))
Shell sh (Bourne) 1977 Stephen Bourne Scripting: variables, loops, functions, here-docs
csh 1978 Bill Joy Brace expansion, history, aliases
ksh 1983 David Korn Arrays, arithmetic, coprocess
bash 1989 Brian Fox GNU Bourne-again — interactive + scripting
zsh 1990 Paul Falstad Programmable completion, glob qualifiers, zmv
Imperial vi/vim 1976 Bill Joy / Bram Modal editing — the survivor
less 1983 Mark Nudelman Paginated viewing (more, but less)
perl 1987 Larry Wall "Swiss Army chainsaw" — regex + I/O
GNU coreutils 1983+ FSF Long flags, -i for in-place, --color
strace 1991 Paul Kranenburg Syscall tracing — see what the kernel sees
ltrace 1997 Juan Cespedes Library call tracing
lsof 1994 Vic Abell List open files — process ↔ file map
System /proc 1984 Tom Killian Kernel data as files — process, memory, network
/sys 2003 Patrick Mochel Device/driver data as files
/dev/fd 1977 Bell Labs File descriptors as paths
ss 2001 Alexey Kuznetsov Socket statistics (replaced netstat)
ip 1999 Alexey Kuznetsov Network config (replaced ifconfig)
journalctl 2011 Lennart Poettering Structured log querying
systemctl 2010 Lennart Poettering Service management
Modern jq 2012 Stephen Dolan JSON query language (like awk for JSON)
yq 2017 Mike Farah YAML/XML query (jq syntax for YAML)
ripgrep (rg) 2016 Andrew Gallant grep + .gitignore + speed + Unicode
fd 2017 David Peter find + .gitignore + speed + color
fzf 2013 Junegunn Choi Fuzzy finder — stdin → interactive pick
bat 2018 David Peter cat + syntax highlighting + git diff
eza 2023 Community ls + git status + color + tree
delta 2019 Dan Davison diff + syntax highlighting + side-by-side
sd 2018 Gregory Neverov sed + modern regex (no backslash hell)
hyperfine 2018 David Peter Benchmarking — statistical CLI timing
tree 1996 Steve Baker Directory structure visualization
stat 1986 BSD Inode metadata: times, size, permissions
numfmt 2013 GNU Byte → human-readable conversion
column 1989 BSD Tabular alignment
Binary xxd 1990 Juergen Weigert Hex dump and reverse — binary ↔ hex
od 1971 Bell Labs Octal dump (the original hex viewer)
hexdump 1986 BSD Flexible format hex/ascii dump
binwalk 2010 Craig Heffner Firmware extraction, embedded file detection
objdump 1990 GNU Disassemble binary → assembly
Network tcpdump 1988 Van Jacobson Packet capture at the wire
tshark 1998 Gerald Combs Wireshark CLI — deep protocol dissection
ngrep 2001 Jordan Ritter grep for network packets
curl 1997 Daniel Stenberg HTTP/FTP/SFTP client — the universal fetcher
nc (netcat) 1996 Hobbit Raw TCP/UDP — the "Swiss Army knife of networking"
ss 2001 Alexey Kuznetsov Socket statistics — what's connected, what's listening
dig 1997 ISC DNS query tool — authoritative lookups
nmap 1997 Gordon Lyon Port scanning, service detection, scripting
The Design Argument
Each era makes the same argument differently:
| Era | Philosophy |
|---|---|
Primordial |
One tool, one job. Compose with pipes. |
Classical |
Pattern-action processing. |
Imperial |
The general-purpose escape hatch. When pipelines get unwieldy, |
Modern |
Speed + ergonomics + awareness. |
The lesson: don’t reach for perl when awk suffices. Don’t reach for awk when grep suffices. But do reach for jq over grep when the data is JSON — the tool should match the structure.
Capture the Flag — Your Codebase, Your Tools
All challenges use your actual domus-captures repository. No synthetic data. Answers use the tools from the lineage above — escalating from primordial through modern.
Rules:
-
Every answer must be a single pipeline (no scripts, no temp files)
-
Multiple correct answers exist — find the one that fits the tool’s affordance
-
Difficulty marked: 🟢 warm-up, 🟡 working, 🔴 senior, ⚫ architect
Round 1 — Reconnaissance (grep, find, wc)
🟢 FLAG 1: How many .adoc partials exist in today’s daily notes?
find docs/modules/ROOT/partials/worklog/daily-notes/2026-06/2026-06-22 -name '*.adoc' -type f | wc -l
🟢 FLAG 2: Which of today’s partials mentions "CVSS"?
grep -rl 'CVSS' docs/modules/ROOT/partials/worklog/daily-notes/2026-06/2026-06-22/
🟢 FLAG 3: How many total lines did you write today across all 19 partials?
find docs/modules/ROOT/partials/worklog/daily-notes/2026-06/2026-06-22 -name '*.adoc' -type f \
-exec cat {} + | wc -l
🟡 FLAG 4: Which partial is the largest by line count? Show filename and count, sorted.
find docs/modules/ROOT/partials/worklog/daily-notes/2026-06/2026-06-22 -name '*.adoc' -type f \
-exec wc -l {} + | sort -rn | head -20
Round 2 — File Intelligence (stat, find -printf, file)
🟡 FLAG 5: Which partial was modified most recently? Show ISO timestamp + filename.
find docs/modules/ROOT/partials/worklog/daily-notes/2026-06/2026-06-22 -name '*.adoc' -type f \
-printf '%T+ %p\n' | sort -r | head -1
🟡 FLAG 6: Show the birth time (creation time) of the CVE summary partial.
stat --format='Birth: %w' docs/modules/ROOT/partials/worklog/daily-notes/2026-06/2026-06-22/cve-2026-55200-summary.adoc
🟡 FLAG 7: List all of today’s partials sorted by size in bytes, with human-readable sizes.
find docs/modules/ROOT/partials/worklog/daily-notes/2026-06/2026-06-22 -name '*.adoc' -type f \
-printf '%s\t%p\n' | sort -rn | numfmt --to=iec --field=1
🔴 FLAG 8: Which partials were created AFTER 12:00 today? (birth time filter)
find docs/modules/ROOT/partials/worklog/daily-notes/2026-06/2026-06-22 -name '*.adoc' -type f \
-newerBt '2026-06-22 12:00' -printf '%T+ %p\n' | sort
-newerBt compares birth time (B) against a timestamp (t). Not all filesystems support birth time — ext4 does, tmpfs does not.
|
Round 3 — Content Extraction (awk, sed, cut)
🟡 FLAG 9: Extract every AsciiDoc section heading (lines starting with ====) from today’s partials. Show filename:heading.
find docs/modules/ROOT/partials/worklog/daily-notes/2026-06/2026-06-22 -name '*.adoc' -type f \
-exec awk '/^====/{print FILENAME": "$0}' {} \;
🟡 FLAG 10: Count how many [source,bash] code blocks exist across today’s partials.
grep -rc '\[source,bash\]' docs/modules/ROOT/partials/worklog/daily-notes/2026-06/2026-06-22/ \
| awk -F: '{sum+=$2} END{print sum, "code blocks"}'
🔴 FLAG 11: Extract every URL from the CVE teams-response partial. URLs only, one per line, deduplicated.
grep -oP 'https?://[^\[, \]]+' \
docs/modules/ROOT/partials/worklog/daily-notes/2026-06/2026-06-22/cve-2026-55200-teams-response.adoc \
| sort -u
🔴 FLAG 12: Build a table showing: partial name | section count | code block count | line count. Aligned columns.
printf "%-45s %8s %8s %8s\n" "PARTIAL" "SECTIONS" "BLOCKS" "LINES"
printf "%-45s %8s %8s %8s\n" "-------" "--------" "------" "-----"
for f in docs/modules/ROOT/partials/worklog/daily-notes/2026-06/2026-06-22/*.adoc; do
name=$(basename "$f")
sections=$(grep -c '^====' "$f" 2>/dev/null)
blocks=$(grep -c '\[source,' "$f" 2>/dev/null)
lines=$(wc -l < "$f")
printf "%-45s %8d %8d %8d\n" "$name" "$sections" "$blocks" "$lines"
done
Round 4 — Cross-File Analysis (awk state machines, comm, diff)
🔴 FLAG 13: Which d001 projects are mentioned in today’s partials but NOT in project-master.yaml? (Set difference.)
comm -23 \
<(grep -rohP '(?<=projects/)[a-z0-9-]+' docs/modules/ROOT/partials/worklog/daily-notes/2026-06/2026-06-22/ | sort -u) \
<(grep -oP '^\s+slug:\s*\K\S+' data/project-master.yaml | sort -u)
comm -23 shows lines only in file 1. Process substitution <(…) avoids temp files. (?⇐projects/) is a PCRE lookbehind — match projects/ but don’t include it.
🔴 FLAG 14: Extract every complete [source,bash] block from the triage-script partial. Print with line numbers.
awk '/^\[source,bash\]/{start=NR} start && /^----$/{if(fence++) {fence=0; start=0; print "---"} else {next}} start{printf "%4d %s\n", NR, $0}' \
docs/modules/ROOT/partials/worklog/daily-notes/2026-06/2026-06-22/cve-2026-55200-triage-script.adoc
⚫ FLAG 15: Produce a dependency graph: which partials include:: other partials? Show as parent → child pairs.
grep -rn 'include::partial\$worklog/daily-notes/2026-06/2026-06-22/' \
docs/modules/ROOT/partials/worklog/daily-notes/2026-06/2026-06-22/ \
| awk -F: '{
parent=$1; sub(/.*\//, "", parent)
match($0, /2026-06-22\/([^[]+)/, a)
if(a[1]) printf " %s → %s\n", parent, a[1]
}'
Round 5 — Modern Tools (ripgrep, fd, fzf, jq, yq)
🟡 FLAG 16: Use yq to count how many projects in project-master.yaml have status "active".
yq '[.projects[] | select(.status == "active")] | length' data/project-master.yaml
🔴 FLAG 17: Use fzf to interactively pick a partial from today, then open it in $EDITOR.
find docs/modules/ROOT/partials/worklog/daily-notes/2026-06/2026-06-22 -name '*.adoc' \
| fzf --preview 'head -40 {}' | xargs "$EDITOR"
🔴 FLAG 18: Use process substitution + diff to compare today’s section headings vs yesterday’s.
diff \
<(grep -rh '^====' docs/modules/ROOT/partials/worklog/daily-notes/2026-06/2026-06-21/ | sort) \
<(grep -rh '^====' docs/modules/ROOT/partials/worklog/daily-notes/2026-06/2026-06-22/ | sort)
⚫ FLAG 19: One pipeline — find today’s largest partial, extract its section headings, show them numbered with awk.
find docs/modules/ROOT/partials/worklog/daily-notes/2026-06/2026-06-22 -name '*.adoc' -type f \
-printf '%s %p\n' | sort -rn | awk 'NR==1{print $2}' \
| xargs grep '^====' | awk -F: '{gsub(/^=+ /, "", $2); printf "%2d. %s\n", NR, $2}'
Three stages: find -printf gets sizes, sort -rn | awk 'NR==1' picks the largest, xargs grep extracts headings, final awk numbers them. No temp files, no variables — pure pipeline.
Scoring
| Flags Captured | Rank | Next Step |
|---|---|---|
1–5 |
Recruit |
Master |
6–10 |
Operator |
Regex: BRE → ERE → PCRE. |
11–14 |
Specialist |
|
15–17 |
Engineer |
|
18–19 |
Architect |
Multi-stage pipelines from memory. Teach others what you know. |
The Full Landscape
The lineage table, the CTF flags, and the curriculum arc together form a single reference. This is the map of the territory:
SUBSTRATE (languages that live inside the tools)
│
├── regex ──────── BRE → ERE → PCRE (the grammar of search)
├── glob ──────── *, ?, [a-z], ** (filename expansion)
├── brace exp ─── {a,b}, {1..10} (sequence generation)
├── param exp ─── ${var##*/}, ${var:-x} (string ops without tools)
├── exit codes ── $?, &&, ||, set -e (the boolean of pipes)
├── signals ───── trap, SIGTERM, SIGHUP (process communication)
└── file desc ─── 0/1/2, 2>&1, >()/< () (stream routing)
TOOLS (operate on streams)
│
├── Search ────── grep → egrep → ripgrep
├── Traverse ──── find → fd → tree
├── Transform ─── sed → awk → perl → jq/yq
├── Compare ───── diff → comm → delta
├── Measure ───── wc → stat → du → hyperfine
├── Route ─────── tee → xargs → parallel
└── Inspect ───── file → strings → xxd → binwalk → objdump
SYSTEMS (the kernel beneath)
│
├── /proc ─────── process state as files
├── /sys ──────── device state as files
├── strace ────── syscall visibility
├── lsof ──────── process ↔ file map
└── C ─────────── write the tools yourself
NETWORK (packets and protocols)
│
├── tcpdump ───── capture at the wire
├── tshark ────── deep protocol dissection
├── ss ─────────── socket state
├── dig ─────────── DNS query
├── nmap ────────── service discovery
└── nc ──────────── raw TCP/UDP
Self-Teaching with --help and man — The Recursive Method
The tools teach themselves. man pages are plain text — which means every tool in the lineage works on them. You learn grep by grepping the grep man page.
The Basics — How to Read
# --help is the cheat sheet (fits in a terminal)
grep --help 2>&1 | grep '\-o'
# man is the textbook (complete, examples, edge cases)
man grep | grep -A5 '^\s*-o,'
man awk
# Type /pattern to search forward
# Type ?pattern to search backward
# Type n for next match, N for previous
# Type q to quit
# Section 1: user commands (what you want 90% of the time)
# Section 5: file formats (config files)
# Section 7: conventions (regex, ascii, signal)
# Section 8: admin commands (root-level)
man 1 find # the command
man 7 regex # POSIX regex syntax reference
man 5 ssh_config # SSH config file format
man ascii # the ASCII table — indispensable for hex/CTF work
Mining Man Pages with the Tools Themselves
man grep | grep -i 'count'
man awk | grep -oP '\b(NR|NF|FS|RS|OFS|ORS|FILENAME|FNR|ARGV|ARGC)\b' | sort -u
man find | awk '/^EXAMPLES/,/^[A-Z]/{print}'
grep --help 2>&1 | grep -oP '^\s*-\S+' | sort
diff <(find --help 2>&1 | grep -oP '^\s*-\S+' | sort) \
<(grep --help 2>&1 | grep -oP '^\s*-\S+' | sort)
# "I need to compare two files" — which tools mention "compare"?
apropos compare
# "I need to manipulate timestamps"
apropos timestamp
# "I need to process columns"
apropos column
# Extract just the OPTIONS section from the find man page
man find | awk '/^OPTIONS$/,/^[A-Z]/' | head -80
# Extract just EXAMPLES from awk
man awk | awk '/^EXAMPLES/,/^[A-Z]/' | head -40
# Extract every flag + its description from grep
man grep | awk '/^ -/{flag=$0; getline; print flag, $0}'
The recursive beauty: you’re using awk to parse the awk man page. You’re using grep -oP to extract patterns from the grep documentation. The act of learning is the practice.
Daily Practice Standard — Palaestra Digitalis
Palaestra: the ancient training ground. Five minutes. Every day. No exceptions.
The Format
Each day’s daily notes includes one palaestra-YYYY-MM-DD.adoc partial:
partials/worklog/daily-notes/YYYY-MM-DD/palaestra.adoc
Structure
==== Palaestra — YYYY-MM-DD
===== Challenge
[description of the challenge — what to find, extract, transform, compare]
===== Constraints
* Tools allowed: [specific tools for this challenge]
* Time limit: 5 minutes
* Bonus: [harder variant using a more advanced tool]
===== My Answer
[source,bash]
\----
# Your solution here — filled in during practice
\----
===== Instructor Answer
[%collapsible]
=====
[source,bash]
\----
# Reference solution with explanation
\----
=====
===== What I Learned
* [one line — the affordance, the flag, the pattern that stuck]
Curriculum Arc
The challenges follow a deliberate progression tied to real work:
| Phase | Tools + Substrate | Challenge Source |
|---|---|---|
Weeks 1–2 |
grep, find, wc, sort, uniq, head/tail, tee |
Search your own worklogs, count patterns, find files. Learn to read |
Weeks 3–4 |
Regex deep dive: BRE ( |
Regex drills against your own AsciiDoc. Extract IPs, dates, URLs, headings. Build patterns from man pages. |
Weeks 5–6 |
PCRE: |
ISE log parsing with |
Weeks 7–10 |
awk (fields, printf, arrays, BEGIN/END, state machines), sed (address ranges, hold buffer, in-place), cut/paste/tr |
Extract structured data from AsciiDoc. Transform configs with sed. awk state machines for code block extraction. Redirect patterns. |
Weeks 11–14 |
stat, file, strings, xxd, diff, comm, process substitution |
File forensics: birth time, inode analysis. Set operations with |
Weeks 15–18 |
jq, yq, xargs -P, find -exec, pipeline composition, fzf, curl |
API response parsing. Parallel operations. Multi-stage pipelines. Write shell functions that compose tools. |
Weeks 19–22 |
strace, ltrace, lsof, /proc, /sys |
Trace what tools actually do. |
Weeks 23–28 |
C (stdio, syscalls, sockets, /proc parsing) |
Write the tools — implement grep, wc, find in C. Parse |
Weeks 29–34 |
ss, ip, tcpdump, tshark, ngrep, dig, nmap, nc |
Network forensics. Capture and parse traffic. DNS enumeration. Port scanning. Raw socket communication with |
Weeks 35–40 |
binwalk, hexdump, objdump, xxd (advanced), file signatures, encoding detection |
Binary analysis. Extract embedded files. Identify encodings. Reverse-engineer file formats. |
Weeks 41+ |
External CTF challenge sets, DFIR scenarios, incident simulations |
Real flags. Real time limits. The palaestra becomes the arena. Teach others. |
How It Gets Generated
Each session, the challenge is drawn from one of three sources:
-
Today’s work — something you actually did, reframed as a CLI puzzle
-
Man page exploration — "find a flag in
man findthat you’ve never used, then use it" -
Curriculum progression — the next step in the arc above
The challenge appears at the start of the session. You solve it. You document what you learned. Five minutes. Then you do your real work — which is itself more practice.
The Commitment
This is not a suggestion. This is a standard. It joins STD-014 (Worklog System) as part of the daily notes architecture:
-
make new-dayscaffolds the worklog shell -
The palaestra partial is added to the daily notes index
-
The challenge is generated at session start
-
The answer is filled in during practice
-
The learning is captured for the permanent record
Every day. Even if it’s one grep flag you didn’t know existed. The compound effect of 365 five-minute sessions is not five minutes — it’s a different engineer.
Annual Review — Finalization
Annual Review — Finalization & Submission
All 24 cells populated. 3 Exemplary / 5 Effective. C239 intentionally blank. Follow the submission runbook below — steps 0 through 9 in order. Toolkit scripts for inspection and editing follow after the runbook.
Submission Runbook — Start to End
# HOME — push latest commits
git -C ~/atelier/_bibliotheca/domus-captures push origin main
# WORK — pull and open this file
git -C ~/atelier/_bibliotheca/domus-captures pull
v $(find docs -path '*/2026-06-22/annual-review.adoc')
python3 -m venv /tmp/xlsx-env
/tmp/xlsx-env/bin/pip install openpyxl -q
decrypt-file data/d000/cursus/annual-review/chla/2026/my-annual-review-2026.xlsx.age
Softens ownership language — you built the pipeline and analysis, not driving the entire migration.
XLSX="data/d000/cursus/annual-review/chla/2026/my-annual-review-2026.xlsx"
/tmp/xlsx-env/bin/python3 << PYEOF | tee /tmp/annual-review-v2-applied.txt
import openpyxl
wb = openpyxl.load_workbook('${XLSX}')
ws = wb['SelfEval']
changes = [
('C40',
'SIEM Migration: QRadar to Microsoft Sentinel',
'SIEM Migration Planning: QRadar to Microsoft Sentinel'),
('I49',
'Architecting QRadar-to-Sentinel SIEM migration',
'Building the collection and analysis tier for QRadar-to-Sentinel SIEM migration'),
('C66',
'Drive the SIEM migration to Microsoft Sentinel to operational capability with validated log coverage across ISE, ASA, Windows, and firewall sources.',
'Advance the SIEM collection and ETL pipeline toward operational capability, supporting validated log coverage across ISE, ASA, Windows, and firewall sources.'),
]
for cell, old, new in changes:
val = ws[cell].value
if old in val:
ws[cell] = val.replace(old, new)
print(f"[{cell}] ✅ replaced:")
print(f" v1: {old}")
print(f" v2: {new}")
else:
print(f"[{cell}] ⚠ pattern not found — already applied or changed")
wb.save('${XLSX}')
print("\nSaved.")
PYEOF
XLSX="data/d000/cursus/annual-review/chla/2026/my-annual-review-2026.xlsx"
/tmp/xlsx-env/bin/python3 << PYEOF | tee /tmp/annual-review-final-check.txt
import openpyxl
wb = openpyxl.load_workbook('${XLSX}')
ws = wb['SelfEval']
cells = [
('D32', 'Name', 'value'),
('D36', 'Title', 'value'),
('J91', 'Job Skills', 'value'),
('J109', 'Outcomes', 'value'),
('J127', 'Service', 'value'),
('J162', 'Collaboration', 'value'),
('J170', 'Empathy', 'value'),
('J178', 'Accountability', 'value'),
('J187', 'Growth', 'value'),
('J196', 'Service Care', 'value'),
('C40', 'Contributions', 'len'),
('G40', 'Meaningful', 'len'),
('I49', 'Indiv Goal', 'len'),
('C66', 'New Indiv Goal', 'len'),
('C71', 'New Team Goal', 'len'),
('C102', 'Skills Comments', 'len'),
('C120', 'Outcomes Cmt', 'len'),
('C207', 'Values Comments', 'len'),
('C232', 'Leader Support', 'len'),
('C239', 'Additional', 'len'),
]
optional = {'C239'}
empty = []
for cell, label, mode in cells:
raw = ws[cell].value
if mode == 'len':
chars = len(str(raw or ''))
print(f"{cell:5s} {label:20s} {chars} chars")
if chars == 0 and cell not in optional:
empty.append(cell)
else:
print(f"{cell:5s} {label:20s} {raw}")
if raw is None and cell not in optional:
empty.append(cell)
if empty:
print(f"\n⚠ EMPTY: {', '.join(empty)}")
else:
print("\n✅ All required cells populated (C239 blank by design)")
PYEOF
XLSX="data/d000/cursus/annual-review/chla/2026/my-annual-review-2026.xlsx"
PATTERN="siem|sentinel|monad|architect|drive the"
/tmp/xlsx-env/bin/python3 << PYEOF | tee /tmp/annual-review-siem-check.txt
import openpyxl, re
wb = openpyxl.load_workbook('${XLSX}')
ws = wb['SelfEval']
pattern = re.compile(r'${PATTERN}', re.IGNORECASE)
for cell in ['C40', 'I49', 'C66']:
val = str(ws[cell].value or '')
hits = [line.strip() for line in val.split('\n') if pattern.search(line)]
if hits:
print(f"\n[{cell}]")
for h in hits:
print(f" {h}")
PYEOF
xdg-open data/d000/cursus/annual-review/chla/2026/my-annual-review-2026.xlsx
Verify I49 and I57 — confirm [Yes] / [In Progress] render correctly (strip brackets if literal text).
rm data/d000/cursus/annual-review/chla/2026/my-annual-review-2026.xlsx.age
encrypt-file data/d000/cursus/annual-review/chla/2026/my-annual-review-2026.xlsx
rm data/d000/cursus/annual-review/chla/2026/my-annual-review-2026.xlsx
git -C ~/atelier/_bibliotheca/domus-captures add data/d000/cursus/annual-review/chla/2026/my-annual-review-2026.xlsx.age
git -C ~/atelier/_bibliotheca/domus-captures commit -m "docs(d000): update annual review xlsx with v2 SIEM framing (encrypted)"
git -C ~/atelier/_bibliotheca/domus-captures push origin main
| Cell | v1 (before) | v2 (after) |
|---|---|---|
C40 |
"SIEM Migration: QRadar to Microsoft Sentinel" |
"SIEM Migration Planning: QRadar to Microsoft Sentinel" |
I49 |
"Architecting QRadar-to-Sentinel SIEM migration" |
"Building the collection and analysis tier for QRadar-to-Sentinel SIEM migration" |
C66 |
"Drive the SIEM migration to Microsoft Sentinel…" |
"Advance the SIEM collection and ETL pipeline toward operational capability…" |
Toolkit — Inspection & Editing Scripts
The scripts below are for ad-hoc inspection and editing. Not part of the submission flow — use as needed.
Discover — Dump All Cells (any xlsx)
XLSX="data/d000/cursus/annual-review/chla/2026/my-annual-review-2026.xlsx"
/tmp/xlsx-env/bin/python3 << PYEOF | tee /tmp/xlsx-dump-$(date +%F).txt
import openpyxl
wb = openpyxl.load_workbook('${XLSX}')
for sheet_name in wb.sheetnames:
ws = wb[sheet_name]
print(f"\n=== {sheet_name} ({ws.max_row} rows x {ws.max_column} cols) ===")
for row in ws.iter_rows(max_row=ws.max_row, max_col=ws.max_column, values_only=False):
for cell in row:
if cell.value is not None:
raw = cell.value
if hasattr(raw, 'text'):
val = f"={raw.text}"
else:
val = str(raw)
chars = f"({len(val)} chars)" if len(val) > 70 else ""
preview = val[:70] + "..." if len(val) > 70 else val
print(f" [{cell.coordinate}] {preview} {chars}")
PYEOF
Then search the dump:
PATTERN="ISE|TEAP|CVE|Exemplary|Effective"
grep -Pin "$PATTERN" /tmp/xlsx-dump-$(date +%F).txt
Read All Cells — Full Text (any xlsx)
XLSX="data/d000/cursus/annual-review/chla/2026/my-annual-review-2026.xlsx"
/tmp/xlsx-env/bin/python3 << PYEOF | tee /tmp/xlsx-full-$(date +%F).txt
import openpyxl
wb = openpyxl.load_workbook('${XLSX}')
for sheet_name in wb.sheetnames:
ws = wb[sheet_name]
print(f"\n{'=' * 70}")
print(f"=== {sheet_name} ({ws.max_row} rows x {ws.max_column} cols) ===")
print(f"{'=' * 70}")
for row in ws.iter_rows(max_row=ws.max_row, max_col=ws.max_column, values_only=False):
for cell in row:
if cell.value is not None:
raw = cell.value
if hasattr(raw, 'text'):
val = f"={raw.text}"
else:
val = str(raw)
if len(val) <= 70:
print(f" [{cell.coordinate}] {val}")
else:
print(f"\n[{cell.coordinate}] ({len(val)} chars)")
print(val)
PYEOF
Read Single Cell
CELL=C40
/tmp/xlsx-env/bin/python3 << PYEOF | tee /tmp/annual-review-${CELL}.txt
import openpyxl
wb = openpyxl.load_workbook('data/d000/cursus/annual-review/chla/2026/my-annual-review-2026.xlsx')
ws = wb['SelfEval']
cell = '${CELL}'
print(f"[{cell}] ({len(str(ws[cell].value or ''))} chars)")
print(ws[cell].value)
PYEOF
Search Cells by Keyword
PATTERN="siem|sentinel|monad|cost|pipeline|qradar|etl|dcr|cribl|log source"
XLSX="data/d000/cursus/annual-review/chla/2026/my-annual-review-2026.xlsx"
/tmp/xlsx-env/bin/python3 << PYEOF | tee /tmp/annual-review-search.txt
import openpyxl, re
wb = openpyxl.load_workbook('${XLSX}')
ws = wb['SelfEval']
pattern = re.compile(r'${PATTERN}', re.IGNORECASE)
cells = ['C40', 'G40', 'I49', 'I57', 'C66', 'C71', 'C102', 'C120', 'C207', 'C232']
for cell in cells:
val = str(ws[cell].value or '')
hits = [line.strip() for line in val.split('\n') if pattern.search(line)]
if hits:
print(f"\n{'=' * 70}")
print(f"[{cell}]")
print(f"{'=' * 70}")
for h in hits:
print(f" {h}")
PYEOF
Edit a Cell
CELL=C66
XLSX="data/d000/cursus/annual-review/chla/2026/my-annual-review-2026.xlsx"
TMP="/tmp/annual-review-${CELL}.txt"
# 1. Extract current value to temp file
/tmp/xlsx-env/bin/python3 << PYEOF
import openpyxl
wb = openpyxl.load_workbook('${XLSX}')
val = wb['SelfEval']['${CELL}'].value or ''
with open('${TMP}', 'w') as f:
f.write(val)
print(f"BEFORE [${CELL}] ({len(val)} chars)")
PYEOF
# 2. Edit in your editor
${EDITOR:-nvim} "$TMP"
# 3. Write back and verify
/tmp/xlsx-env/bin/python3 << PYEOF | tee /tmp/annual-review-edit-${CELL}.txt
import openpyxl
with open('${TMP}') as f:
new_val = f.read()
wb = openpyxl.load_workbook('${XLSX}')
ws = wb['SelfEval']
old_len = len(str(ws['${CELL}'].value or ''))
ws['${CELL}'] = new_val
wb.save('${XLSX}')
wb2 = openpyxl.load_workbook('${XLSX}')
final = wb2['SelfEval']['${CELL}'].value or ''
print(f"[${CELL}] {old_len} -> {len(final)} chars")
print(final)
PYEOF
Cell Map Reference
| Cell | Content | Status |
|---|---|---|
D32 |
Name |
PASS |
D36 |
Title |
PASS |
C40 |
Professional Contributions |
✅ 5,158 chars |
G40 |
Most Meaningful |
✅ 879 chars |
I49 |
Individual Goal Assessment |
✅ 3,436 chars |
G50 |
Individual Goal Y/N |
✅ Yes |
I57 |
Team Goal Assessment |
✅ 850 chars |
G58 |
Team Goal Y/N |
✅ In Progress |
C66 |
New Individual Goal |
✅ 390 chars |
C71 |
New Team Goal |
✅ 231 chars |
J91 |
Job Skills |
✅ Exemplary |
C102 |
Job Skills Comments |
✅ 1,581 chars |
J109 |
Outcomes |
✅ Exemplary |
C120 |
Outcomes Comments |
✅ 1,656 chars |
J127 |
Service |
✅ Effective |
C139 |
Service Comments |
✅ 1,126 chars |
J162 |
Collaboration |
✅ Effective |
J170 |
Empathy |
✅ Effective |
J178 |
Accountability |
✅ Effective |
J187 |
Growth |
✅ Exemplary |
J196 |
Responsibility |
✅ Effective |
C207 |
Values Comments |
✅ 1,936 chars |
C232 |
Leader Support |
✅ 567 chars |
C239 |
Additional Comments |
— Blank (intentional) |
Tested — WSL Arch to Windows Excel Workflow
Verified 2026-06-22. Steps 0-7 confirmed working from WSL Arch on work machine.
-
Pull, decrypt, v2 apply, verify, SIEM check — all passed
-
cpto/mnt/c/fails if Excel holds the file lock — close first, then copy -
invoke-itemin PowerShell opens xlsx in Excel
Close the file in Excel before copying — cp fails with "Permission denied" if Excel holds the lock.
# Copy from WSL to Windows Downloads
cp data/d000/cursus/annual-review/chla/2026/my-annual-review-2026.xlsx /mnt/c/Users/erosado/Downloads/excel/
# Open in Excel from PowerShell
invoke-item .\Downloads\excel\my-annual-review-2026.xlsx
PowerShell — Work Computer Commands
PowerShell — Work Computer Commands
Invoke-Item C:\Users\erosado\Downloads\excel\my-annual-review-2026.xlsx
Get-Process | Where-Object { $_.ProcessName -like "*teams*" } | Stop-Process -Force
Get-Process | Where-Object { $_.ProcessName -like "*excel*" } | Stop-Process -Force
Get-Process | Where-Object { $_.ProcessName -like "*zoom*" } | Stop-Process -Force
# gps = Get-Process, ? = Where-Object, spps = Stop-Process
gps | ? { $_.ProcessName -like "*teams*" } | spps -Force
# Even shorter — Get-Process accepts -Name with wildcards directly
Stop-Process -Name "*teams*" -Force
Stop-Process -Name "*excel*" -Force
Stop-Process -Name "*zoom*" -Force
# Invoke-Item ≈ xdg-open
xdg-open ~/Downloads/excel/my-annual-review-2026.xlsx
# Get-Process | Where-Object | Stop-Process ≈ pkill
pkill -fi 'teams'
pkill -fi 'excel'
pkill -fi 'zoom'
# Or the pipeline version (closer to the PowerShell pattern)
ps aux | awk '/[t]eams/{print $2}' | xargs kill -9
Opening Files from Terminal — Invoke-Item / ii
Invoke-Item (alias ii) is PowerShell’s "double-click" — opens a file with its default Windows application. This is how you stay in the terminal and still use native Windows apps.
# Full cmdlet
Invoke-Item C:\Users\erosado\Downloads\excel\my-annual-review-2026.xlsx
# Alias — same thing, 2 characters
ii C:\Users\erosado\Downloads\excel\my-annual-review-2026.xlsx
# Access WSL filesystem via \\wsl$ UNC path — opens in Windows Chrome/Edge, not wslg
ii "\\wsl$\archlinux04\home\evanusmodestus\atelier\_bibliotheca\domus-captures\data\d001\projects\monad-pipeline\output\monad-pipeline.html"
This is the key pattern: \\wsl$\<distro>\ exposes the entire WSL filesystem to Windows. ii hands the path to Windows, which opens it with the native browser — full GPU rendering, no Wayland compositor overhead. The WSLg browser works but this is faster and uses your actual browser profile (bookmarks, extensions, sessions).
ii .
ii "https://docs.domusdigitalis.dev"
# wslview is the WSL equivalent of xdg-open — routes to Windows default handler
wslview output/monad-pipeline.html
# Or use explorer.exe directly from WSL bash
explorer.exe "$(wslpath -w output/monad-pipeline.html)"
# Open current directory in Windows Explorer from WSL
explorer.exe .
Productivity Patterns — Stay in the Terminal
# pushd remembers where you were, popd goes back
pushd C:\Users\erosado\Downloads\excel
ii my-annual-review-2026.xlsx
popd
# You're back where you started
# Find the most recent .xlsx in Downloads and open it
Get-ChildItem ~\Downloads -Filter *.xlsx -Recurse |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1 |
ForEach-Object { ii $_.FullName }
# Direct — no pipeline needed when you know the name
Stop-Process -Name "*teams*" -Force
Stop-Process -Name "*excel*" -Force
Stop-Process -Name "*zoom*" -Force
# Reusable function — add to $PROFILE
function Kill-App { param([string]$Name) Stop-Process -Name "*$Name*" -Force -ErrorAction SilentlyContinue }
Kill-App teams
Kill-App excel
Kill-App zoom
Get-Process | Sort-Object WorkingSet64 -Descending |
Select-Object -First 10 Name, @{N='MB';E={[math]::Round($_.WorkingSet64/1MB)}} |
Format-Table -AutoSize
# Copy command output to clipboard (paste into Teams/email)
Get-Process | Where-Object { $_.CPU -gt 100 } | Set-Clipboard
# Paste clipboard content into a variable
$data = Get-Clipboard
# Copy file
Copy-Item .\report.pdf ~\Desktop\
# Move and rename
Move-Item .\draft.docx ~\Documents\final-report.docx
# Recursive copy — entire directory
Copy-Item .\output\ ~\Desktop\monad-output\ -Recurse
Unix ↔ PowerShell Rosetta Stone
| Task | Unix (bash) | PowerShell |
|---|---|---|
Open file in default app |
|
|
Kill process by name |
|
|
Find files by name |
|
|
Find files by content |
|
|
Sort by size |
|
|
Disk usage |
|
|
Copy to clipboard |
|
|
View file with line numbers |
|
|
Watch a log |
|
|
Count lines |
|
|
PowerShell pipes objects ($_.ProcessName). Unix pipes text (awk '{print $2}'). Same pattern — filter then act — but PowerShell knows the structure, Unix requires you to parse it. Trade-off: PowerShell is verbose but self-documenting. Unix is terse but requires you to know the column layout.
|
Work (CHLA)
| CHARGE TIME IN PEOPLESOFT - CRITICAL. Do this NOW before anything else. |
Critical (P0)
| Project | Description | Owner | Status | Due | Blocker |
|---|---|---|---|---|---|
Linux Research (Xianming Ding) |
EAP-TLS for Linux workstations, dACL, UFW |
Evan |
BEHIND (72 days overdue) |
02-24 |
Certificate "password required" - nmcli fix documented |
iPSK Manager |
Pre-shared key automation |
Ben Castillo |
BEHIND |
— |
DB replication issues |
MSCHAPv2 Migration |
Legacy auth deprecation — 6,227 devices, 5 waves. 6 batch SQL queries + 3-API endpoint profile script added (05-11). Report due. |
Evan |
25% — Report due, batch queries ready |
05-30 |
Report to turn in |
Research Segmentation |
All endpoints to Untrusted VLAN |
Evan |
BLOCKED |
— |
CISO decision pending |
Disaster Recovery |
ISE DR scoping — dot1x closed mode = total blackout |
Evan |
Scoping |
— |
— |
Mandiant Remediation |
Copy 4/16 findings, Guest ACL lab, Q2 assessment |
Evan |
Active |
— |
— |
SIEM QRadar → Sentinel |
Full SIEM platform transition. Monad console error resolved 05-12. Secrets configured. Blocked on DCR creation (Rule ID + Stream Name). Azure private network policy unresolved. |
Evan |
Active — blocked on DCR |
Q2 2026 |
Victor/Mauricio: create DCR, resolve Azure network policy |
Abnormal Security |
AI email platform — ESA cutover. CR assigned, CAB May 12 15:00. Implementation May 14 10:00. |
Evan |
Active — CAB today 15:00 |
05-14 |
Pre-CAB checklist: confirm Tyler, Jason, Sarah |
High Priority (P1)
| Project | Description | Owner | Status | Target |
|---|---|---|---|---|
ISE 3.4 Migration |
Upgrade from 3.2p9 |
Evan |
Blocked — maintenance window needed |
Q2 2026 |
Switch Upgrades |
IOS-XE fleet update (C9300, 3560CX) |
Evan |
Pending |
Q2 2026 |
Spikewell BYOD VPN |
dACL SQL, AD group integration |
Evan |
Active |
— |
Strongline Gateway |
MAC capture, Identity Group setup — 37 days aging |
Evan |
Active — David Rukiza assigned |
— |
Abnormal Security |
AI email security platform research, ESA cutover timeline |
Evan |
Newly assigned |
— |
DMZ Migration |
External services audit behind NetScaler |
Evan |
Audit phase |
— |
Firewall Audit (murus-portae) |
EtherChannel query, prefilter, policy assignments |
Evan |
Scoping — ASA API creds needed |
— |
iPSK Manager HA |
Server 2 config, TLS, SQL security audit |
Evan |
In progress |
— |
Sentinel KQL |
Build proficiency, distinguish from team |
Evan |
Onboarding |
— |
VNC Blocking |
Block and eliminate VNC protocol enterprise-wide |
Evan |
Active — Phase 0 (Discovery) |
Mid-June 2026 |
Strategic (P2)
| Project | Description | Owner | Status |
|---|---|---|---|
HHS Regulatory Compliance |
New HHS security policies implementation |
TBD |
NOT STARTED |
InfoSec Reporting Dashboard |
PowerBI metrics for executives |
TBD |
NOT STARTED |
EDR Migration (AMP → Defender) |
Endpoint protection consolidation |
TBD |
NOT STARTED |
Azure Legacy Migration |
Modern landing zone |
Team |
In Progress |
ChromeOS EAP-TLS |
SCEP + Victor, Paul testing |
Victor |
In Progress |
P0 — Critical / Blocking
Security & Compliance
-
ISE 3.2 Patch 10 upgrade — CVE-2026-20147 CVSS 9.9 / CVE-2026-20148. Propose maintenance window once patch confirmed on software.cisco.com.
-
ISE Advisory sa-ise-rce-traversal-8bYndVrZ — check Patch 10 availability
-
Mandiant Remediation — findings status tracked. Working session prep + defensive posture documented (comms-2026-04-24). Copy 4/16 updates into Excel at work. Guest ACL lockdown (WIR-M-01) pending lab validation. appendix-todos updated with MSCHAPv2 milestones.
-
Guest ACL update — guest redirect ACL work needed. Lab validate GUEST_CWA_REDIRECT_MAX_SECURITY in d000, then joint CR with NE. On today’s task list.
-
Disaster Recovery & Downtime Procedures — ISE top priority (dot1x closed mode = SPOF for network access)
-
ISE DR: Document failover sequence — PAN, MnT, PSN priority order
-
ISE DR: RADIUS dead-server detection on WLCs/switches — critical-auth VLAN fallback
-
ISE DR: Backup/restore procedures — scheduled config backups, tested restores
-
FTD/FMC DR: FMC loss = no policy management
-
Network DR: Core/distribution switch failure, STP reconvergence, HSRP failover
-
Document RTO/RPO per system
-
SIEM Migration (QRadar → Sentinel)
-
SIEM QRadar → Sentinel Migration — LEAD ROLE. 4 collection iterations (Apr 16, 17, 17-streamlined, 20-streamlined). Python chart pipeline built (
qradar-charts.py). Migration XLSX generated. Verification pending. Comms sent Apr 23.-
d001 artifacts: 8 JSON exports, 2 CSV inventories, migration XLSX, top5 source SVG/PNG, verification doc
-
Dependency: Monad pipeline for log source transition
-
Dependency: Sentinel KQL proficiency for query migration
-
-
Monad Pipeline Evaluation (origin: 2026-03-11) — lead role. Console error RESOLVED 05-12. 06-09: Architecture decision — rsyslog (CHLXSYSLOG01) as collection tier → Monad → Sentinel. ISE lab → rsyslog → Monad 6-step execution guide created with 10 API calls. ASA lab logs already flowing through rsyslog. DCR still needed — Victor + Mauricio.
-
Sentinel KQL — build proficiency, distinguish from team. Azure portal access acquired.
-
QRadar log source report — run AQL queries, fetch JSON, generate Python Excel
Active Deployments & Migrations
-
MSCHAPv2 Migration — 6-sheet Standard Report ready. Migration window 05-04 to 05-30 CLOSED. Confirm final report status and next steps with team. 6,227 MSCHAPv2 devices, 14,249 EAP-TLS/TEAP (70% migrated).
-
MSCHAPv2 weekly cadence — recurring Wednesday call established (first 04-22). Completed 2026-04-22.
-
MSCHAPv2 ownership matrix — sent in scoping email 4/24 with manager callouts (@Albert, @John). Completed 2026-04-24.
-
TCP Clocks deployment — Batch 1: 7 clocks validated (OUI 40:AC:8D). Batch 2 (06-09): 9 new MACs (OUI 40:AC:BD) added via ERS. 1 one-off reassigned. New switch deployed without RADIUS/AAA — clocks can’t authenticate. Switch onboarding template + 3 validation queries documented. ERS queries self-contained with
ersfunction. -
SRT Research VLAN — confirm roles with Tony Sun: Tony implementor, Evan tester. CAB approved 04-21.
-
Downtime Computers enforcement — draft ISE AuthZ rule: medigate_724 + Wireless = DenyAccess. Separate CR. d001: DC queries, audit CSVs (v1-v3), wireless violations report delivered 04-21.
-
Enterprise Linux 802.1X — standardize Shahab/Ding deployment (CISO priority). Overdue since 02-24. Blocked by nmcli cert fix. 06-15: Assembler build fixed (26 errors → 0). Segmentation document extension to 06-16.
d001 open linux-researchto work on it. -
Abnormal Security — CR-2026-05-07. Implemented 05-13. 06-09 update: Full policy review — 20-section EOP validation commands rebuilt, Hoxhunt SCL-1 investigation (intentional bypass confirmed), sclizer junk folder triage (~800 emails), Outlook reactions audit added, Connect-ExchangeOnline msalruntime fix documented. ESA migration expansion in progress — priority to move off ESA to full environment.
-
Team: Cox/William, Landeros/Jason, Rosado/Evan, Naranjo/Mauricio, Sandoval/Carlos
-
-
ASA VPN: Okta RADIUS → Entra SAML — (NEW 06-09) 5-phase migration plan built. ASA baseline captured (2 tunnel groups: CHLA_CORPORATE_USERS, CHLA_BYOD_USERS). 6 ISE policy screenshots. Tony Sun (ASA), Justin Halbmann (Entra/Okta), Evan (ISE). VPN cert expires 07-28. PDF deliverable ready. Share with team this week.
Tube System Upgrade (NEW — 06-01)
-
Tube System Upgrade — iTrack 3528165. 15x 10" TS stations need MAC addresses added to ISE identity group IoT_Onboard. MACs received from vendor (C8:1A:FE:20:xx:xx series). Station list spans ICU (CTICU, PICU, BMT, NICU, NICCU), ED, Surgery, Trauma, Pharmacy. Vendor contact: John Genest. Rationale: manufacturer no longer supports current system; failure risks delayed/missed patient care.
BMS Controller Segmentation (MIGRATED — 06-09)
-
BMS Controller Segmentation — Full migration from Principia LaTeX to d001. 12 partials, 5 Mermaid diagrams, 4 legacy PDFs, ISE screenshots.
d001 open bms-controller. Completed 2026-06-09.
BMS Device Inventory (NEW — 04-24)
-
BMS Device Inventory — 72 devices discovered across 37 switches (04-24). Profile-driven architecture (Claroty/Medigate). 16 queries built. Phase 0 complete. Next: cross-reference with Visio diagrams, classify by function, begin D2 diagrams. Cleanup: delete 4 orphaned test groups, migrate 4 retire-dACL devices, investigate 3 null-profile devices.
VNC Blocking (NEW — 05-11)
-
VNC Blocking — block and eliminate VNC enterprise-wide. Due mid-June 2026. Phase 0: discovery. January AQL query baseline to incorporate. Cross-reference BMS inventory for VNC-capable devices.
Investigations & Audits
-
Murus Portae (WAF) — Phase 0 discovery in progress. FMC cert expired. d001: DMZ NetScaler WAF investigation, zone map, architecture D2 diagrams (v1+v2 SVGs), FMC REST API reference guide, ops script. FMC API returning zero ACP rules — under investigation.
-
Firewall audit — FMC discovery inventory done (d001: fmc-discovery-2026-04-16). EtherChannel query, prefilter, policy assignments pending.
-
IoT Dr. Kim devices — RECURRING. All 4 MACs validated in IoT_iPSK_VLAN1620_Misc (04-24). v2 validation queries built with 7 deep analysis queries (group flapping, credential leakage, profile drift, NAS tracking, remediation timeline, deny audit, OUI scan). Revalidate — confirm no flapping since 04-24.
-
IoT device validation queries — v2 created with partials architecture, 16 queries across ERS/MnT/DataConnect/FMC. Completed 2026-04-24.
Stale Blockers (carried via carryover tracker)
-
k3s NAT verification — rule 170, 10.42.0.0/16 pod network (origin: 2026-03-09). 92 days. Blocks Wazuh indexer recovery → blocks SIEM visibility. Decide: test or defer to Q3.
-
Strongline Gateway VLAN fix — 8 devices wrong identity group (origin: 2026-03-16). 85 days. David Rukiza assigned — follow up on status.
Administrative
-
PeopleSoft — track time for current week
-
iTrack tickets — close open tickets
-
KQL library — build initial queries in codex + d001
-
Linux Research project — finalize and review
-
Tax filing 2025 (MFJ) — see encrypted case file in
data/d000/personal/for details and action items
P1 — Important
-
MSCHAPv2 action-item tracker — owner/status/next-steps per workstream
-
ISE admin MFA enforcement — recommendation tied to advisory (interim control pending Patch 10)
-
DMZ Migration — external services audit behind NetScaler. Linked to Murus Portae investigation.
-
Vocera/Wyse iTrack RCA — complete root cause report
-
GCC ISE Support — 3/4 nodes restored, PSN-04 deferred
-
Wazuh indexer recovery — blocked by k3s NAT (origin: 2026-03-09)
-
Vocera EAP-TLS Supplicant Fix (origin: 2026-03-12)
-
iPSK Manager HA — blocked by DB replication (Ben Castillo)
-
ISE 3.4 Migration — depends on Patch 10 completion first
-
Git history scrub — murus-portae-output.md + ise-analytics CSVs
-
Encrypt
prep-cmds-2026-04-15.adoc— plaintext committed to git -
ISE MnT Messaging Service — enable UDP syslog delivery (maintenance window needed)
Infrastructure (Personal)
-
Borg backups — test and validate on ALL systems (Razer, P16g, vault-01, bind-01, kvm-01, kvm-02)
-
Borg — verify backup script paths updated from dotfiles-optimus to dots-quantum
-
Borg — create initial archive for ThinkPad P16g if none exists
-
Libvirt VLAN hook debug on both KVMs
-
Te1/0/2 cable replacement and re-test
-
Vault Raft cluster — verify vault-01 rejoined
-
Fix EAP-TLS keyring/secrets issue on Razer workstation
Completed (confirmed — do not delete, archive only)
-
CR-2026-04-15 SRT Research VLAN — submitted to iTrack. Completed 2026-04-15.
-
CAB presentation 4/21 — SRT Research VLAN 233 → CHLA-Research. APPROVED. Completed 2026-04-21.
-
Downtime Computers wireless audit — 45 computers, 16 violating, v3 report delivered. Completed 2026-04-21.
-
Git identity fix — dots-quantum/git/.gitconfig email corrected. Completed 2026-04-21.
-
MSCHAPv2 10:30 meeting — next steps + ACL coordination. Completed 2026-04-17.
Service Requests (SR)
| SR# | Request | Requestor | Opened | Status |
|---|---|---|---|---|
3508542 |
Zoll cards connection issue |
— |
— |
STALE — verify in iTrack |
3508524 |
Disable dot1x on (2) network ports - 5th floor 3250 Wilshire (PXE-boot imaging issues) |
— |
— |
STALE — verify in iTrack (issues persisted after disable) |
3528165 |
Tube System Upgrade — 15 stations, MAC addresses for ISE IoT_Onboard identity group |
Genest, John (vendor contact) |
2026-06-01 |
NEW — MACs received, need ISE onboarding |
Incidents (INC)
| INC# | Priority | Description | Opened | SLA | Status |
|---|---|---|---|---|---|
1911859 |
— |
Strongline Gateways in Miscellaneous Subnet |
— |
— |
STALE — verify in iTrack (related to carryover P0) |
Change Requests - Emergency (ECAB)
| CR# | Description | Opened | Scheduled | Status |
|---|---|---|---|---|
No emergency changes |
Change Requests - Normal
| CR# | Description | Opened | Scheduled | Status |
|---|---|---|---|---|
No normal changes |
Change Requests - Scheduled/Standard
| CR# | Description | Opened | Window | Status |
|---|---|---|---|---|
No scheduled changes |
Change Requests - Root Cause / Post-Incident
| CR# | Description | Related INC | Opened | Status |
|---|---|---|---|---|
100451 |
Vocera Phones and Wyse devices went off network |
— |
— |
STALE — verify in iTrack |
Session Accomplishments (Claude Code)
Day-specific accomplishments here.
Personal
In Progress
| Project | Description | Status | Notes |
|---|---|---|---|
k3s Platform |
Production k3s cluster on kvm-01 |
Active |
Prometheus, Grafana, Wazuh deployed |
Wazuh Archives |
Enable archives indexing in Filebeat |
Active |
PVC fix pending |
kvm-02 Hardware |
Supermicro B deployment |
Active |
Hardware ready, RAM upgrade done |
Planned
| Project | Description | Target | Blocked By |
|---|---|---|---|
Vault HA (3-node) |
vault-02, vault-03 on kvm-02 |
Q2 2026 (slipped from Q1) |
kvm-02 deployment |
k3s HA (3-node) |
Control plane HA |
Q2 2026 (slipped from Q1) |
kvm-02 deployment |
ArgoCD GitOps |
k3s GitOps deployment |
After k3s stable |
— |
MinIO S3 |
Object storage for k3s |
After ArgoCD |
— |
Personal asset management (YAML + CLI + AsciiDoc) |
Q2 2026 |
Schema approved |
Active — Infrastructure
| Task | Details | Priority | Status | Due |
|---|---|---|---|---|
Wazuh agent deployment |
Deploy agents to all infrastructure hosts |
P2 |
Pending |
After archives fix |
k3s Platform |
Production k3s cluster on kvm-01 |
P1 |
In Progress |
— |
Wazuh Archives |
Enable archives indexing in Filebeat, PVC fix |
P1 |
In Progress |
— |
kvm-02 Hardware |
Supermicro B deployment, RAM upgrade done |
P1 |
In Progress |
— |
Active — Security & Encryption
| Task | Details | Priority | Status | Due |
|---|---|---|---|---|
Configure 4th YubiKey |
SSH FIDO2 keys |
P1 |
TODO |
— |
Cold storage M-DISC backup |
age-encrypted archives |
P1 |
TODO |
After YubiKey setup |
Active — Development & Tools
| Task | Details | Priority | Status | Due |
|---|---|---|---|---|
netapi Commercialization |
Go CLI rewrite with Cobra-style argument discovery, package for distribution |
P0 |
Active |
— |
Ollama API Service |
FastAPI (17 endpoints), productize — config audit, doc tools, runbook gen |
P0 |
Active |
— |
Shell functions (fe, fec, fef) |
File hunting helpers |
P3 |
TODO |
— |
Active — Documentation
| Task | Details | Priority | Status | Due |
|---|---|---|---|---|
D2 Catppuccin Mocha styling |
domus-* spoke repos (177 files total) |
P3 |
In Progress |
— |
Active — Financial
| Task | Details | Priority | Status | Due |
|---|---|---|---|---|
Amazon order history import |
Download CSV from Privacy Central → parse with awk → populate subscriptions tracker |
P1 |
Waiting |
Pending Amazon data export (requested 2026-04-04) |
Active — Education
| Task | Details | Priority | Status | Due |
|---|---|---|---|---|
No active education tasks — see education trackers |
Active — Personal & Life Admin
| Task | Details | Priority | Status | Due |
|---|---|---|---|---|
ThinkPad T16g Setup |
Arch install, stow dotfiles, Ollama stack, netapi dev env |
P0 |
Pending |
— |
P50 Arch to Ubuntu migration |
P2 |
In Progress |
— |
|
X1 Carbon Ubuntu installs |
2 laptops, LUKS encryption |
P2 |
In Progress |
— |
P50 Steam Test |
Test Flatpak Steam + apt cleanup of broken i386 packages |
P3 |
Pending |
— |
Documentation Sites
-
docs.domusdigitalis.dev - Private documentation hub
-
docs.architectus.dev - Public portfolio site
Education
Claude Code Mastery
| Resource | Details | Progress | Status |
|---|---|---|---|
Claude Code Full Course (4 hrs) |
Nick Saraev - YouTube comprehensive course |
26:49 / 4:00:00 |
IN PROGRESS |
Claude Code Certification |
Anthropic official certification (newly released) |
Not started |
GOAL |
Active Tracks (Focus)
-
Don Quijote - Primera Parte
Skills Mastery (Critical)
-
Regex Mastery - 10-module curriculum
-
AsciiDoc Docs - Documentation format
-
Antora Docs - Documentation pipeline
Certification Deadlines
-
CISSP - July 12, 2026 (10-week plan active — Week 1)
-
RHCSA 9 - Q3 2026 (after CISSP)
-
LPIC-1 - Renewal required (blocks LPIC-2)
Spanish C1 Certification Goals
| Certification | Provider | Target | Status | Strategy |
|---|---|---|---|---|
Instituto Cervantes / UNAM / Salamanca |
Q2 2026 |
ACTIVE |
Computer-based, faster results - take FIRST |
|
Q3/Q4 2026 |
PLANNED |
After SIELE success, harder exam |
||
2027 |
FUTURE |
Mastery level - requires extensive immersion |
| SIELE is computer-adaptive, results in 3 weeks. DELE is paper-based, results in 3-4 months. Do SIELE first to validate readiness. |
Don Quijote Writing Practice - DELE C1/C2 Initiative
Method:
-
Read chapter in original Spanish
-
Write personal analysis/understanding en espanol
-
AI review for grammar, vocabulary, register
-
Build comprehensive understanding of literary elements
Today’s Study
-
Focus: CISSP (41 days to July 12 exam — schedule exam today 06-01), MSCHAPv2 migration wrap-up
-
Secondary: RHCSA curriculum, Spanish SIELE C1
-
CISSP — Security & Risk Management (continuing). Schedule exam this afternoon.
-
RHCSA — continue curriculum phase
-
Spanish — Don Quijote reading + analysis (DTLA study day)
-
MSCHAPv2 — migration window closed 05-30, review final report
Regex Training (CRITICAL)
-
Status: 52 days carried over (since 2026-03-16)
-
Priority: After PeopleSoft, before Quijote
-
Session: Character classes, word boundaries
Infrastructure
Documentation Sites
| Site | URL | Status | Actions Needed |
|---|---|---|---|
Domus Digitalis |
Active |
Validate, harden, improve |
|
Architectus |
Active |
Public portfolio site - maintain |
HA Deployment Status
| System | Description | Status | Notes |
|---|---|---|---|
VyOS HA |
vyos-01 (kvm-01) + vyos-02 (kvm-02) with VRRP VIP |
✅ COMPLETE |
2026-03-07 - pfSense decommissioned |
BIND DNS HA |
bind-01 (kvm-01) + bind-02 (kvm-02) with AXFR |
✅ COMPLETE |
Zone transfer operational |
Vault HA |
Raft cluster (vault-01/02/03) |
✅ COMPLETE |
Integrated with PKI |
Keycloak Rebuild |
keycloak-01 corrupted, rebuild from scratch |
🔄 NEXT |
Priority P3 - SSO broken |
FreeIPA HA |
ipa-02 replica planned |
📋 PLANNED |
Linux auth redundancy |
AD DC HA |
home-dc02 replication |
📋 PLANNED |
Windows auth redundancy |
iPSK Manager HA |
ipsk-mgr-02 with MySQL replication |
📋 PLANNED |
PSK portal redundancy |
ISE HA |
PAN HA (ise-01 reconfigure) |
⏳ DEFERRED |
Wait until ise-02 stable |
ISE 3.5 Migration |
Upgrade path: 3.2p9 → 3.4 (P1) → 3.5 (target) |
📋 PLANNED |
After 3.4 Migration completes (Q2 2026) |
Single Points of Failure (CRITICAL)
| These systems have NO redundancy - outage impacts production. |
| System | Impact if Down | Mitigation |
|---|---|---|
ISE (ise-02) |
All 802.1X stops - wired and wireless auth fails |
ise-01 reconfiguration deferred until ise-02 stable |
Keycloak (keycloak-01) |
SAML/OIDC SSO broken (ISE admin, Grafana, etc.) |
NEXT PRIORITY - Rebuild runbook |
FreeIPA (ipa-01) |
Linux auth, sudo rules, HBAC fails |
ipa-02 replica planned |
AD DC (home-dc01) |
Windows auth, Kerberos, GPO fails |
home-dc02 replica planned |
iPSK Manager |
Self-service PSK portal unavailable |
ipsk-mgr-02 with MySQL replication planned |
Validation Tasks
| Task | Details | Status |
|---|---|---|
docs.domusdigitalis.dev validation |
Test all cross-references, search, rendering |
TODO |
docs.domusdigitalis.dev hardening |
HTTPS, CSP headers, security review |
TODO |
docs.architectus.dev validation |
Public site content review |
TODO |
Hub-spoke sync verification |
All components building correctly |
Ongoing |
Quick Commands
Git & GitHub CLI
gh repo create <name> --private --source . --remote origin --push
gh repo clone EvanusModestus/PowerShell ~/atelier/_projects/work/PowerShell
gh repo clone defaults to SSH. If key is passphrase-protected, load agent first: eval "$(ssh-agent -s)" && ssh-add ~/.ssh/id_ed25519_github
|
for repo in ~/atelier/_bibliotheca/domus-*/ ~/atelier/_projects/personal/domus-*/; do
[ -d "$repo/.git" ] || continue
name=$(basename "$repo")
git -C "$repo" log --since="2026-04-06" --until="2026-04-07" --format="%h %aI %s" 2>/dev/null |
awk -v r="$name" '{print r, $0}'
done
git log --oneline -- $(find . -name "*.adoc" -type f -newermt "$(date +%F)")
git restore --staged data/d001/api/ise-dataconnect/output/output-2026-04-24
Safe — removes from staging area only. Working tree is untouched. Use when you accidentally git add a plaintext or output file.
gh CLI — repo discovery and filtering
gh repo list --limit 100 --json name,description \
| jq -r '.[] | select(.name | test("domus|antora|asciidoc"; "i")) | "\(.name)\t\(.description)"'
gh repo list --limit 100 --json name,description,updatedAt \
| jq -r 'sort_by(.updatedAt) | reverse | .[:20] | .[] | "\(.updatedAt[:10])\t\(.name)\t\(.description)"'
gh repo list --limit 100 --json name,diskUsage \
| jq -r '.[] | "\(.diskUsage)\t\(.name)"' | sort -rn | head -10
gh repo clone EvanusModestus/<repo-name> ~/atelier/_bibliotheca/<repo-name>
find & grep
find . -name "*.adoc" -type f -newermt "$(date +%F)" | sort
-mtime 0 means "last 24 hours", not "today". -newermt "$(date +%F)" compares against midnight — exact.
|
find . -iname "*mschap*" -type f | sort
find . -type f \( -iname "*ise*" -o -iname "*mschap*" \) | sort
find . -type f -iregex '.*\(ise\|mschap\).*'
find . -type f -iname "*meeting*" \
-not -path "*/node_modules/*" \
-not -path "*/.git/*" \
-not -path "*/build/*"
find .drafts -type f -printf '%T@ %Tc %p\n' | sort -rn | awk '{$1="";print}' | head -3
grep -rl "pattern" . --include="*.adoc" # file count (which files)
grep -rn "pattern" . --include="*.adoc" # line matches (every occurrence)
grep -rc "pattern" . --include="*.adoc" | grep -v ':0$' # match count per file
grep -rn -E 'git init|gh repo create' docs/ --include='*.adoc' -B2 -A2
Search codex by content — which files contain a command?
find docs/modules/ROOT/examples/codex/powershell -type f -name "*.adoc" \
-exec grep -l 'Get-Process\|Start-Process\|pipeline\|Where-Object' {} \;
Pattern: find -exec grep -l returns only filenames with matches — like grep -rl but with find’s `-type f -name filtering. Use \| for OR in grep basic regex. Swap the pattern for any cmdlet or keyword to locate coverage across the codex.
find docs/modules/ROOT -name "powershell" -type d \
-exec sh -c 'echo "$1: $(find "$1" -type f | wc -l) files"' _ {} \;
for f in $(find docs/modules/ROOT/examples/codex/powershell -name "*.adoc" -type f); do
base=$(basename "$f")
dir_parent=$(basename $(dirname "$f"))
grep -rq "$dir_parent/$base" docs/modules/ROOT/pages/codex/powershell/ \
docs/modules/ROOT/examples/codex/powershell/*.adoc 2>/dev/null \
|| echo "ORPHAN: $f"
done
find → grep → open in nvim
nvim $(find -path '*oauth*' -name '*.adoc' -type f \
-exec grep -l 'timeout\|expire\|reconfig\|token' {} \;)
Command substitution $(…) feeds all matches as arguments to nvim — opens every hit as a buffer. :bn/:bp to cycle, :ls to list. One file? Opens directly. Five files? All loaded, ready to navigate.
nvim $(find docs/modules/ROOT -name '*.adoc' -type f \
-exec grep -l 'token.*expire\|oauth.*refresh' {} \;)
find -path '*oauth*' -name '*.adoc' -type f \
-exec grep -l 'timeout\|expire' {} \; \
-exec nvim {} \;
Trailing \| in grep patterns matches empty string — every file matches. Always end with a term, not a pipe: 'timeout\|expire\|token' not 'timeout\|expire\|token\|'.
|
Trace Antora partial inclusion chains
grep -rl 'commands/shell' docs/modules/ROOT/partials/
grep -rl 'quick-commands' docs/modules/ROOT | wc -l
file="commands/shell"
grep -rl "$file" docs/modules/ROOT/partials/ | while read f; do
parent=$(basename "$f" .adoc)
echo "$file -> $parent"
grep -rl "$parent" docs/modules/ROOT/pages/ | while read p; do
echo " -> $(basename "$p")"
done
done
Pattern: grep -rl finds which files contain the string. Chain two passes — first finds the assembler partial, second finds every page that includes it. Works for any partial in the Antora include hierarchy.
Multi-pattern file search — worklog partial discovery
find docs/modules/ROOT -name "*urgent.adoc*" -type f
find docs/modules/ROOT -name "*morning.adoc*" -type f
find docs/modules/ROOT -type f -regextype posix-extended \
-regex '.*(urgent|morning|work-chla|personal|education|infrastructure|quick-commands|related)\.adoc' \
| sort
Pattern: -regextype posix-extended enables | alternation without escaping. One process, one sort — versus 8 separate finds. The sort deduplicates visually and groups by path.
find docs/modules/ROOT -type f -name "*.adoc" \
| grep -E 'urgent|morning|work-chla|personal|education|infrastructure|quick-commands|related'
Trade-off: the pipeline version is more readable but spawns two processes. The regex version is a single find — faster on large trees, same result.
Cross-repo literary term search — bibliotheca-wide discovery
When searching for a term across the entire _bibliotheca (multiple repos, mixed file types), these patterns escalate from narrow to broad.
grep -rn --include='*.adoc' -c 'sanchuelo' . | grep -v ':0$'
grep -rl --include='*.adoc' -i 'sanchuelo' ~/atelier/_bibliotheca/ | sort
grep -rn --include='*.adoc' -i -B1 -A1 'sanchuelo' ~/atelier/_bibliotheca/domus-captures/
grep -rl -i 'sanchuelo' ~/atelier/_bibliotheca/ --include='*.txt' --include='*.adoc' | sort
find ~/atelier/_bibliotheca/ -type f \( -name '*.adoc' -o -name '*.txt' \) -print0 \
| xargs -0 grep -li 'sanchuelo' | sort
grep -rl -i 'sanchuelo' ~/atelier/_bibliotheca/ --include='*.adoc' --include='*.txt' | xargs nvim
Pattern escalation: #1 confirms the term exists and where. #2 expands to all repos. #3 shows context without opening files. #4 adds plain text sources (Quijote .txt originals). #5 is the safe version for automation. #6 opens everything for editing.
Trade-off: grep -r --include is faster for known file types. find | xargs grep is safer for paths with spaces and more extensible (add -name '*.md' etc.). For literary searches across the bibliotheca, #4 or #5 is usually the right starting point — the source texts are .txt, not .adoc.
Search daily notes — find commands, builds, and patterns across worklogs
Source: 2026-06-22 — searching for build-antora-page invocations and theme usage across partials
# Find files — filenames only
grep -rl 'build-antora-page' docs/modules/ROOT/partials/worklog/
# 3 lines of context around each hit
grep -rn -C3 'build-antora-page' docs/modules/ROOT/partials/worklog/
grep -rPn 'theme\s+(light-cyan|catppuccin|mocha)' docs/modules/ROOT/partials/
find docs/modules/ROOT/partials/worklog/daily-notes -name '*.adoc' \
-exec grep -l 'build-antora-page\|build-adoc\|--theme\|pdf' {} \;
find docs/modules/ROOT/partials/worklog/daily-notes -name '*.adoc' \
-exec awk '/build-antora-page|build-adoc|--theme/{print FILENAME": "$0}' {} \;
find docs/modules/ROOT/partials -name '*.adoc' -exec \
awk '/^\[source,bash\]/{block=1; buf=""} \
block{buf=buf"\n"$0} \
/^----$/ && block>1{if(buf~/build-antora-page/) print FILENAME":"buf; block=0} \
block{block++}' {} \;
The awk state machine: [source,bash] sets block=1 and starts buffering. Each line appends to buf. When the closing ---- arrives (and block>1 to skip the opening fence), check if the buffer contains the target command. If yes, print filename + entire block. Reset. One pass, arbitrary block size, no temp files.
find docs/modules/ROOT/partials/worklog -name '*.adoc' -exec \
sed -n '/\[source,bash\]/,/^----$/{/build-antora-page/p}' {} +
grep -rn 'build-antora-page\|build-adoc' data/ --include='*.adoc'
grep -rn 'build-antora-page' docs/modules/ROOT/partials/worklog/daily-notes/ \
| awk -F: '{print $1, $3}' | sort
Pattern escalation: grep -rl → "where is it". grep -rn -C3 → "what’s around it". find -exec awk → "extract the structured block". sed address ranges → "print between delimiters". Each tool has a different affordance — grep finds, awk extracts structure, sed filters ranges, find -exec scales across trees.
Email thread analysis — extract people, dates, commitments, silence
grep -P '(@\w+|^From:.*<)' comms.adoc
grep -nP '\d{1,2}/\d{1,2}/\d{2,4}|20\d{2}-\d{2}-\d{2}' comms.adoc
grep -niP '(I can |I will |I.ll |we will |we.ll )' comms.adoc
grep -niP '(\?|need to confirm|need to validate|TBD|pending)' comms.adoc
comm — set difference (who hasn’t replied)
# All recipients
grep -oP '<\K[^>]+' comms.adoc | sort -u > /tmp/all-recipients
# All senders
grep -P '^From:' comms.adoc | grep -oP '<\K[^>]+' | sort -u > /tmp/replied
# Who's silent — follow-up targets
comm -23 /tmp/all-recipients /tmp/replied
comm -23 outputs lines only in file 1 (recipients not in senders). Requires sorted input. grep -oP '<\K[^>]+' uses PCRE lookbehind — match < but don’t include it, capture until >.
Sort find results by modification time (newest first)
find discovers files but has no sort. Chain -printf with sort to order by mtime.
awk '{print $2}' truncates filenames with spaces — Familia Romana_ Lingva… becomes Familia. Always use the null-safe or sub() variants below for real data.
|
# Sort by mtime, strip epoch prefix — handles spaces in filenames
find ~/Downloads -maxdepth 1 -name '*.pdf' -printf '%T@ %p\n' | sort -rn | awk '{sub(/^[^ ]+ /,""); print}'
sub(/[ ]+ /,"") removes everything up to and including the first space (the epoch). {print $2} would split on every space — fatal for Familia Romana_ Lingva Latina.
# ISO 8601 timestamps — readable and lexicographically sortable
find ~/Downloads -maxdepth 1 -name '*.pdf' -printf '%T+ %p\n' | sort -r | head -20
%T+ renders YYYY-MM-DD+HH:MM:SS — no epoch math needed, still sorts correctly as text.
# Null-delimited: survives any filename (newlines, quotes, unicode)
find ~/Downloads -maxdepth 1 -name '*.pdf' -printf '%T@\t%p\0' | sort -zrn | awk -v RS='\0' -F'\t' '{print $2}'
-printf '%T@\t%p\0' — tab separates epoch from path, null terminates. sort -z sorts null-delimited records. awk -v RS='\0' -F'\t' reads null-terminated, splits on tab — $2 is now the full path regardless of spaces.
# GNU stat equivalent — works where -printf is unavailable
find ~/Downloads -maxdepth 1 -name '*latin*' -exec stat --format='%Y %n' {} + | sort -rn | awk '{sub(/^[^ ]+ /,""); print}'
-exec … {} + batches all files into one stat call (faster than \;). On macOS, use stat -f '%m %N' instead of --format='%Y %n'.
File intelligence — size, type, duplicates, age
Beyond finding files — interrogating them.
# Size in bytes (-printf %s), human-readable via numfmt
find ~/Downloads -type f -printf '%s\t%p\n' | sort -rn | head -10 | numfmt --to=iec --field=1
numfmt --to=iec --field=1 converts the first field from bytes to K/M/G. sort -rn on raw bytes is exact — ls -lhS rounds and sometimes mis-sorts.
# Files sharing a byte count — likely duplicates (confirm with md5sum)
find ~/Downloads -type f -printf '%s %p\n' | awk '{seen[$1]++; files[$1]=files[$1] "\n " $0} END {for (s in seen) if (seen[s]>1) print files[s]}'
# md5sum only files with duplicate sizes (two-pass: fast then precise)
find ~/Downloads -type f -printf '%s\n' | sort | uniq -d | while read -r size; do
find ~/Downloads -type f -size "${size}c" -exec md5sum {} +
done | sort | uniq -w32 -D
Two-pass: first find duplicate sizes (cheap), then md5sum only those (expensive). uniq -w32 -D compares first 32 chars (the hash) and prints all duplicates.
# Count files by MIME type (not extension — extensions lie)
find ~/Downloads -type f -exec file --mime-type -b {} + | sort | uniq -c | sort -rn
file --mime-type -b reports actual content type. -b suppresses filename. A .pdf that’s really text/html is a failed download.
# Files not accessed in 30 days — candidates for cleanup
find ~/Downloads -maxdepth 1 -type f -atime +30 -printf '%A+ %s\t%p\n' | sort | numfmt --to=iec --field=2
-atime 30` = access time older than 30 days. `-printf '%A' shows last access. Useful for Downloads cleanup without deleting something you just renamed.
# Which subdirectories consume the most space?
find . -maxdepth 1 -type d -exec du -sh {} + 2>/dev/null | sort -rh | head -20
Batch operations — rename, move, transform
# Dry run — show what would change (remove echo to execute)
find ~/Downloads -maxdepth 1 -type f -name '* *' -print0 | while IFS= read -r -d '' f; do
dir=$(dirname "$f")
base=$(basename "$f" | tr ' ' '-' | tr '[:upper:]' '[:lower:]')
echo mv "$f" "$dir/$base"
done
IFS= read -r -d '' — the holy trinity for null-safe filename reading. IFS= prevents whitespace trimming. -r prevents backslash interpretation. -d '' reads until null.
# Sort Downloads chaos into folders by type
find ~/Downloads -maxdepth 1 -type f -print0 | while IFS= read -r -d '' f; do
ext="${f##*.}"
case "$ext" in
pdf|epub) dest="books" ;;
jpg|png|svg) dest="images" ;;
sh|py|rb) dest="scripts" ;;
*) dest="other" ;;
esac
mkdir -p ~/Downloads/"$dest"
echo mv "$f" ~/Downloads/"$dest"/
done
${f##.} — parameter expansion: strip longest match of . from front, leaving only the extension. No basename or awk needed.
# Convert all epubs in a directory to asciidoc via pandoc
find . -name '*.epub' -type f -exec sh -c '
for epub; do
adoc="${epub%.epub}.adoc"
pandoc -f epub -t asciidoc "$epub" -o "$adoc" \
&& printf " → %s (%s lines)\n" "$adoc" "$(wc -l < "$adoc")" \
|| printf " ✗ failed: %s\n" "$epub"
done
' _ {} +
-exec sh -c '…' _ {} + — batch mode. _ fills $0 (script name, discarded). All matched files become $1, $2, … iterated by for epub. One sh invocation, not one per file.
xargs power patterns
# Checksum all PDFs in parallel (4 processes)
find ~/Downloads -name '*.pdf' -print0 | xargs -0 -P4 md5sum
-P4 runs 4 md5sum processes simultaneously. -print0 | xargs -0 is the null-safe pipeline — no filename can break it.
# Compare files pairwise with diff
find . -name '*.adoc' -print0 | xargs -0 -n2 diff --brief
-n2 feeds two arguments per invocation. Useful for pairwise comparisons, copy operations (-n2 with cp), or any command taking exactly two args.
# Backup every config file: cp <file> <file>.bak
find /etc -maxdepth 1 -name '*.conf' -print0 | xargs -0 -I{} cp {} {}.bak
-I{} replaces {} with each filename. Slower than + batching (one cp per file) but necessary when the filename must appear in a specific position.
Process substitution — diff without temp files
# What files exist in study-A but not study-B?
diff <(find data/d000/education/ciceron-study -type f -name '*.adoc' | sort) \
<(find data/d000/education/latin-study -type f -name '*.adoc' | sort)
<(cmd) creates a file descriptor from command output. diff sees two "files" — no temp files created, no cleanup needed.
# Side-by-side: file type census of two directories
paste <(find dir1 -type f -exec file --mime-type -b {} + | sort | uniq -c | sort -rn) \
<(find dir2 -type f -exec file --mime-type -b {} + | sort | uniq -c | sort -rn)
awk, sed, jq
awk — field extraction
awk '{print $2}' file.txt
awk -F: '{print $1, $3}' /etc/passwd
awk '/\[source,json\]/{getline; if ($0 ~ /^----/) {p=1; next}} p && /^----/{p=0; next} p' file.adoc
awk '{printf "%-30s %s\n", $1, $2}' file.txt
sed — stream editing
# Before
awk 'NR==73' /etc/ssh/sshd_config
# Change
sed -i '73s/#GSSAPIAuthentication no/GSSAPIAuthentication yes/' /etc/ssh/sshd_config
# After
awk 'NR==73' /etc/ssh/sshd_config
sed -n '10,20p' file.txt
sed — line-targeted replacement (verify-before / change / verify-after)
# 1. LOCATE: find the line number
grep -n 'adoc-pdf' zsh/.zshrc
# 2. VALIDATE: read the exact line before changing
awk 'NR==1760' zsh/.zshrc
# 3. CHANGE: target by line number — only hits that line
sed -i '1760s/alias adoc-pdf=/alias build-adoc=/' zsh/.zshrc
# 4. VERIFY: confirm change AND check for collateral
grep -n 'build-adoc\|adoc-pdf' zsh/.zshrc
Without the line number prefix (1760s/), sed replaces every match in the file — a shotgun. With it, surgical. The line number comes from grep -n.
awk 'NR==1218 || NR==1760' zsh/.zshrc
# grep found the error at line 44164 — read 50 lines of context
awk 'NR>=44160 && NR<=44210' session-dump.adoc
No head | tail chains. No sed -n '44160,44210p'. One awk, two numbers.
grep -oP with \K — value extraction from key-value logs
# ISE syslog — extract failure reasons
grep -oP 'FailureReason=\K[^,;]+' /var/log/syslog | sort | uniq -c | sort -rn
# ISE — extract MAC addresses
grep -oP 'Calling-Station-ID=\K[0-9A-Fa-f:.-]+' /var/log/syslog | sort -u
# ISE — extract NAS IPs
grep -oP 'NAS-IP-Address=\K[0-9.]+' /var/log/syslog | sort -u
# ISE — extract device names
grep -oP 'NetworkDeviceName=\K[^,;]+' /var/log/syslog | sort -u
\K resets the match start — everything before \K is required context but excluded from output. [^,;]+ captures until the next delimiter. Pipe to sort -u for unique, sort | uniq -c | sort -rn for counted frequency.
# Generic form — works for any key=value log format
grep -oP 'FIELD_NAME=\K[^,;]+' logfile | sort | uniq -c | sort -rn | head -20
jq — JSON processing
curl -s localhost:8080/stats | jq '.stats.total_files'
jq '.results[] | select(.category == "standards")' response.json
jq -r '.[] | [.title, .path] | @tsv' response.json | column -t -s $'\t'
gh api "repos/EvanusModestus/domus-captures/commits?path=docs/&per_page=10" |
jq -r '.[] | "\(.commit.author.date[:10]) \(.sha[:7]) \(.commit.message | split("\n")[0])"'
Shell Patterns
xargs — when the next command reads arguments, not stdin
| Next command reads… | Use |
|---|---|
stdin ( |
pipe directly |
arguments ( |
|
-I{} placeholdermkdir -p /tmp/adoc-backup-$(date +%F) && \
find . -name "*.adoc" -type f -newermt "$(date +%F)" | \
xargs -I{} cp {} /tmp/adoc-backup-$(date +%F)/
-P4 runs 4 at a timefind .drafts -name "*.adoc" -type f | xargs -P4 -I{} asciidoctor -o /dev/null {}
find . -name "*.adoc" -type f -print0 | xargs -0 wc -l
Process substitution — <(cmd) treats output as a file
diff <(grep '|' partials/trackers/work/adhoc/carryover.adoc | head -20) \
<(git show HEAD~1:partials/trackers/work/adhoc/carryover.adoc | grep '|' | head -20)
diff <(find docs/modules/ROOT/pages/projects/chla/mschapv2-migration -name "*.adoc" -type f | sort) \
<(grep -oP 'mschapv2-migration/[^[]+\.adoc' docs/modules/ROOT/nav.adoc | sort)
Command substitution — embed output as arguments
nvim "$(find data/ -name '*.adoc' -type f -printf '%T@ %p\n' | sort -rn | awk 'NR==1{print $2}')"
wc -l $(find docs/modules/ROOT -path '*mschapv2*' -name '*.adoc' -type f)
Conditional execution — capture, test, act
files=$(find .drafts -name 'in*' -type f) && [ -n "$files" ] && nvim $files
files=$(grep -rl '\[ \]' .drafts/*.adoc) && [ -n "$files" ] && nvim $files
grep -q 'TODO\|FIXME\|\[ \]' "$file" && nvim "$file"
Pattern: $(capture) → [ -n ] tests non-empty → && only proceeds if true.
grep -q is the idempotent guard — run repeatedly, only opens when there’s work.
Decrypt and open — find .age, decrypt, nvim in one shot
files=$(find . -name "*tcp-clock*.age" -type f) && \
[ -n "$files" ] && echo "$files" | xargs -I{} decrypt-file {} && \
nvim $(echo "$files" | sed 's/\.age$//')
Pattern: find .age only (never tries plaintext), sed derives the decrypted path, guard prevents empty nvim. Change the glob to match any project.
tee_clean — color on screen, clean text in file
tee_clean() {
tee >(sed 's/\x1b\[[0-9;]*m//g' > "$1")
}
# Color output on terminal, stripped in file
jq -C '.' data.json | tee_clean output.json
xq -C '.' data.xml | tee_clean output.json
# Wrap a whole block
{
echo "=== Summary ==="
jq -C '.[] | .name' data.json
} | tee_clean summary.txt
The >(cmd) is process substitution — tee writes to stdout AND to the subshell pipe. sed strips ANSI escape sequences (\x1b\[[0-9;]*m) before they hit the file.
Dependency check — verify toolchain in one shot
for cmd in asciidoctor asciidoctor-pdf pandoc rouge d2 mmdc age; do
printf "%-20s %s\n" "$cmd" "$(command -v $cmd >/dev/null 2>&1 && echo 'OK' || echo 'MISSING')"
done
Pattern: command -v checks if binary exists on PATH. >/dev/null 2>&1 suppresses output — we only care about exit code. Swap the tool list for any project’s dependencies.
printf safety — dashes as data, not options
--- as invalid optionprintf '---\n\n'
--- as dataprintf '%s\n\n' '---'
Kill stuck SSH sessions
lsof -i TCP -n -P | awk '/ssh.*ESTABLISHED/ {print $2, $9}'
lsof -i TCP -n -P | awk '/ssh.*kvm-01.*ESTABLISHED/ {print $2}' | sort -u | xargs kill
lsof -i TCP -n -P | awk '/ssh.*ESTABLISHED/ {print $2}' | sort -u | xargs kill
lsof -i TCP -n -P lists all TCP connections. awk filters for SSH + ESTABLISHED, prints only the PID ($2). sort -u deduplicates (multiple file descriptors per process). xargs kill sends SIGTERM to each.
File Descriptors & Redirection
The three file descriptors
| FD | Name | Purpose |
|---|---|---|
0 |
stdin |
input to the command |
1 |
stdout |
normal output (valid results) |
2 |
stderr |
error messages |
Split stdout and stderr into separate files
find / -name "*.conf" 1>results.txt 2>errors.txt
Suppress errors — 2>/dev/null
find / -name "*.conf" 2>/dev/null
Merge stderr into stdout — 2>&1
command 2>&1 | grep "pattern"
This sends both stdout and stderr through the pipe. Without 2>&1, only stdout reaches grep — errors print to the terminal and bypass the pipeline.
Heredoc patterns
cat <<'EOF'
Line 1
Line 2
EOF
git commit -m "$(cat <<'EOF'
feat: add new feature
Multi-line description here.
EOF
)"
API & curl/jq
domus-api — Documentation System REST API
cd ~/atelier/_projects/personal/domus-api && uv run uvicorn domus_api.main:app --host 0.0.0.0 --port 8080
curl -s localhost:8080/ | jq
curl -s 'localhost:8080/search?q=mandiant' | jq
curl -s 'localhost:8080/search?q=mandiant' | jq '.results[] | {path, title, match_count}'
curl -s 'localhost:8080/pages?category=standards' | jq
curl -s localhost:8080/attributes | jq
GitHub API
gh search code "vault seal" --owner EvanusModestus --json repository,path,textMatches |
jq '.[] | {repo: .repository.full_name, file: .path, match: .textMatches[].fragment}'
gh api 'repos/EvanusModestus/domus-captures/git/trees/main?recursive=1' |
jq '[.tree[] | select(.path | endswith(".adoc"))] | length'
Domus Workflows
Read content from terminal (meeting-ready)
bat docs/modules/ROOT/pages/2026/04/WRKLOG-$(date +%Y-%m-%d).adoc
bat docs/modules/ROOT/partials/trackers/work/priorities/current.adoc
bat docs/modules/ROOT/partials/trackers/work/adhoc/carryover.adoc
bat docs/modules/ROOT/partials/projects/mandiant-remediation/summary.adoc
Search and discovery
grep -rl "MSCHAPv2" docs/modules/ROOT/ --include="*.adoc" | sort
grep -rn "pattern" docs/modules/ROOT/partials/codex/ --include="*.adoc" -B1 -A3
ls -1 docs/modules/ROOT/pages/2026/04/WRKLOG-*.adoc
Tracker aging — calculate days from origin
echo $(( ($(date +%s) - $(date -d "2026-03-09" +%s)) / 86400 ))
Encrypted data access (d001)
age --decrypt -i ~/.secrets/.metadata/keys/master.age.key \
data/d001/projects/mandiant-remediation/findings-status-2026-04-16.adoc.age \
| bat --language asciidoc
for d in data/d001/projects/*/; do
total=$(find "$d" -type f | wc -l)
plain=$(find "$d" -type f ! -name '*.age' ! -name 'README.adoc' ! -name '.gitkeep' ! -name '*.py' | wc -l)
printf "%-25s %s files %s plaintext\n" "$(basename "$d")" "$total" "$plain"
done
d000 study builds
for d in p1-cap-03{7,8,9}; do
for f in data/d000/education/quijote-study/notas/$d/*.adoc; do
d000 build "$d/$(basename "$f" .adoc)" html --variant light-cyan
done
done
d000 build p1-cap-038/texto-anotado html --variant light-cyan
d000 build p1-cap-038/texto-anotado pdf --theme light-cyan
for d in p1-cap-03{7,8,9}; do
for f in data/d000/education/quijote-study/notas/$d/*.adoc; do
d000 build "$d/$(basename "$f" .adoc)" pdf --theme light-cyan
done
done
firefox data/d000/education/quijote-study/notas/p1-cap-03{7,8,9}/output/*.html &
firefox data/d000/education/quijote-study/notas/p1-cap-03{7,8,9}/output/*.pdf &
lp data/d000/education/quijote-study/notas/p1-cap-03{7,8,9}/output/*.pdf
d000 build annotated-text pdf --theme light-cyan
d000 build lpl-study/notas/texto-anotado pdf --theme light-cyan
d000 build de-oratore/libro-i/texto-anotado html --variant light-cyan
Available themes
ls ~/atelier/_bibliotheca/domus-asciidoc-build/themes/pdf/ | sed 's/-theme\.yml//'
# base blue burgundy catppuccin creative dark don-quijote green
# learning light-cyan navy operations orange purple reference royal
~/atelier/_bibliotheca/domus-asciidoc-build/docinfo/compose.sh --list
# light dark catppuccin royal light-cyan
ISE & Network Ops
ISE ERS API — endpoint CRUD
export ISE_HOST="{ise-ip}" ISE_USER="admin" ISE_PASS="$(gopass show -o ise/admin)"
curl -sk "https://$ISE_HOST:{ise-ers-port}/ers/config/identitygroup" \
-H "Accept: application/json" -u "$ISE_USER:$ISE_PASS" | jq '.SearchResult.resources[].name'
curl -sk "https://$ISE_HOST:{ise-ers-port}/ers/config/endpoint?filter=mac.EQ.AA:BB:CC:DD:EE:FF" \
-H "Accept: application/json" -u "$ISE_USER:$ISE_PASS" | jq '.SearchResult.total'
Certificate inspection
openssl x509 -in {cert-dir}/client.pem -text -noout | head -30
openssl x509 -in {cert-dir}/client.pem -enddate -noout
Network diagnostics
ss -tlnp | grep -E ':{port-https}|:{port-ssh}|:{port-ldaps}'
nc -zv {ise-ip} {ise-ers-port}
dig {ise-hostname} +short
ISE eval rotation — backup & restore
# SSH to ISE
ssh admin@ise-02.inside.domusdigitalis.dev
# Verify NAS repo
show repository nas-01
# Get encryption key (on workstation)
dsource d000 dev/storage
echo $ISE_BACKUP_KEY
# Run backup
backup pre-rotation-2026-06 repository nas-01 ise-config encryption-key plain <KEY>
ssh admin@ise-02.inside.domusdigitalis.dev
show repository nas-01
configure terminal
repository nas-01
url nfs://10.50.1.70:/volume1/ise_backups
exit
restore <backup-filename> repository nas-01 encryption-key plain <KEY>
VyOS — VRRP & VLAN inspection
show vrrp
show configuration commands | grep vrrp | grep 'address'
show configuration commands | grep 'firewall zone' | grep 'member'
show dhcp server leases
show arp
show interfaces
CUPS printing — validation & setup
command -v lpstat && echo "CUPS present" || echo "CUPS not installed"
lpstat -r # scheduler running?
lpstat -p -d # printers + default
sudo systemctl enable --now cups # start + persist
lpinfo -v # available backends/URIs
lpinfo -m | grep -i <brand> # available drivers
sudo lpadmin -p <name> -v <uri> -m everywhere -E
lpoptions -d <name>
lp file.pdf # default printer
lp -d <name> -o sides=two-sided-long-edge file.pdf
PowerShell (from zsh)
All PowerShell commands run inside pwsh -NoLogo -Command '…' from zsh. Running them bare fails — zsh interprets $, |, () as shell syntax.
|
Process management
pwsh -NoLogo -Command 'Get-Process | Sort-Object WorkingSet64 -Descending |
Select-Object -First 5 ProcessName, Id,
@{N="MB";E={[math]::Round($_.WorkingSet64/1MB)}} | Format-Table'
pwsh -NoLogo -Command 'Get-Process | Where-Object {$_.ProcessName -like "*teams*"} | Stop-Process'
pwsh -NoLogo -Command 'Start-Process "ms-teams"'
Export to JSON (pipe to jq)
pwsh -NoLogo -Command 'Get-Process | Sort-Object WorkingSet64 -Descending |
Select-Object -First 5 ProcessName, Id,
@{N="MB";E={[math]::Round($_.WorkingSet64/1MB)}} | ConvertTo-Json' | jq '.'
Never pipe Format-Table into ConvertTo-Json — it produces layout metadata, not data. Select-Object first, then ConvertTo-Json.
|
Wi-Fi management (netsh)
netsh wlan disconnect interface="Wi-Fi"
netsh wlan show networks mode=bssid
netsh wlan connect name="CHLA-Remote" interface="Wi-Fi"
SSH from PowerShell
ssh evan@modestus-razer.inside.domusdigitalis.dev
WSL ↔ Windows — Cross-Environment Commands
From zsh (WSL) — control Windows
pwsh -NoLogo -Command 'Get-Date'
pwsh -NoLogo -Command "$(cat <<'PS'
$procs = Get-Process | Where-Object { $_.WorkingSet64 -gt 100MB }
$procs | Sort-Object WorkingSet64 -Descending |
Select-Object ProcessName, Id, @{N="MB";E={[math]::Round($_.WorkingSet64/1MB)}} |
Format-Table -AutoSize
PS
)"
# Open in default Windows app
wslview /mnt/c/Users/erosado/Documents/report.pdf
# Open Explorer to current WSL directory
explorer.exe .
# Open specific Windows path
explorer.exe 'C:\Users\erosado\Downloads'
# Pipe anything to Windows clipboard
cat file.txt | clip.exe
# Copy a command's output
pwsh -NoLogo -Command 'Get-TransportRule | Format-List Name, State' | clip.exe
# Windows C: drive is at /mnt/c
ls /mnt/c/Users/erosado/Downloads/
# Copy from Windows to WSL
cp /mnt/c/Users/erosado/Downloads/report.pdf ~/atelier/
# Watch a Windows directory for new files
find /mnt/c/Users/erosado/Downloads -maxdepth 1 -mmin -5 -type f -printf '%T+ %p\n' | sort -r
From PowerShell — control WSL
wsl -e bash -c 'grep -rn "Ghost-Sender" ~/atelier/_bibliotheca/domus-captures/docs/'
$result = wsl -e bash -c 'git -C ~/atelier/_bibliotheca/domus-captures log --oneline -5'
$result
Process Management — Windows Side
pwsh -NoLogo -Command '
Get-Process | Sort-Object WorkingSet64 -Descending |
Select-Object -First 20 ProcessName, Id,
@{N="MB";E={[math]::Round($_.WorkingSet64/1MB)}},
@{N="CPU(s)";E={[math]::Round($_.CPU,1)}},
@{N="Handles";E={$_.HandleCount}} |
Format-Table -AutoSize'
pwsh -NoLogo -Command 'Get-Process | Where-Object { $_.ProcessName -like "*teams*" } |
Select-Object ProcessName, Id, @{N="MB";E={[math]::Round($_.WorkingSet64/1MB)}} |
Format-Table -AutoSize'
pwsh -NoLogo -Command 'Stop-Process -Name "Teams" -Force -ErrorAction SilentlyContinue'
pwsh -NoLogo -Command 'Stop-Process -Id 12345 -Force'
pwsh -NoLogo -Command 'Get-NetTCPConnection -State Listen |
Select-Object LocalAddress, LocalPort, OwningProcess,
@{N="Process";E={(Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName}} |
Sort-Object LocalPort | Format-Table -AutoSize'
pwsh -NoLogo -Command 'Get-NetTCPConnection -LocalPort 8080 -ErrorAction SilentlyContinue |
Select-Object LocalAddress, LocalPort, RemoteAddress, State,
@{N="Process";E={(Get-Process -Id $_.OwningProcess).ProcessName}}'
Services — Windows Side
pwsh -NoLogo -Command 'Get-Service | Where-Object { $_.Status -eq "Running" } |
Sort-Object DisplayName | Format-Table Name, DisplayName, Status -AutoSize'
pwsh -NoLogo -Command 'Get-Service -Name "WinRM" | Format-List Name, DisplayName, Status, StartType'
Restart-Service -Name "WinRM" -Force
System Info — Quick Health from zsh
pwsh -NoLogo -Command '
Write-Host "=== Windows System ===" -ForegroundColor Cyan
Write-Host "Hostname: $env:COMPUTERNAME"
Write-Host "User: $env:USERNAME"
Write-Host "OS: $((Get-CimInstance Win32_OperatingSystem).Caption)"
Write-Host "Uptime: $((Get-Date) - (Get-CimInstance Win32_OperatingSystem).LastBootUpTime)"
Write-Host "RAM: $([math]::Round((Get-CimInstance Win32_OperatingSystem).TotalVisibleMemorySize/1MB))GB total, $([math]::Round((Get-CimInstance Win32_OperatingSystem).FreePhysicalMemory/1MB))GB free"
Write-Host "CPU: $((Get-CimInstance Win32_Processor).Name)"
Write-Host "Disk C: $([math]::Round((Get-PSDrive C).Free/1GB))GB free of $([math]::Round(((Get-PSDrive C).Used + (Get-PSDrive C).Free)/1GB))GB"'
pwsh -NoLogo -Command 'Get-PSDrive -PSProvider FileSystem |
Select-Object Name, @{N="Used(GB)";E={[math]::Round($_.Used/1GB,1)}},
@{N="Free(GB)";E={[math]::Round($_.Free/1GB,1)}},
@{N="Total(GB)";E={[math]::Round(($_.Used+$_.Free)/1GB,1)}} |
Format-Table -AutoSize'
Exchange Online — Connect from zsh
pwsh -NoLogo -Command 'Connect-ExchangeOnline -UserPrincipalName erosado@chla.usc.edu'
MFA prompt opens in the Windows browser. After auth, the session persists in the pwsh process. For multi-command sessions, start pwsh interactively instead of one-shot commands.
|
pwsh -NoLogo
# Then inside pwsh:
# Connect-ExchangeOnline
# Get-TransportRule | Format-List Name, State
# exit
File Transfer Patterns
# WSL → Windows Downloads
cp ~/atelier/_bibliotheca/domus-captures/output/report.pdf /mnt/c/Users/erosado/Downloads/
# Windows → WSL (glob)
cp /mnt/c/Users/erosado/Downloads/*.{png,pdf,jpg} ~/atelier/_staging/
# Bulk move with null safety
find /mnt/c/Users/erosado/Downloads -maxdepth 1 -name '*.pdf' -mmin -60 -print0 |
xargs -0 -I{} cp {} ~/atelier/_staging/
inotifywait -m /mnt/c/Users/erosado/Downloads -e create -e moved_to |
awk '{printf "%s %s\n", strftime("%H:%M:%S"), $3}'
inotifywait requires inotify-tools. Install with sudo pacman -S inotify-tools if not present.
|
Security & Encryption
View encrypted files without writing to disk
age --decrypt -i ~/.secrets/.metadata/keys/master.age.key \
data/d001/projects/mandiant-remediation/findings-status-2026-04-16.adoc.age \
| bat --language asciidoc --file-name "findings-status-2026-04-16.adoc"
Batch re-encrypt — brace expansion + loop
for f in data/d001/projects/mandiant-remediation/{findings-status,guest-acl-update,siem-report}-2026-04-16.adoc; do
rm -f "${f}.age" && echo y | encrypt-file "$f"
done
Always rm -f the .age first. If you skip it, encrypt-file prompts about overwrite and may only delete the plaintext without re-encrypting.
|
Detect stale plaintext — files needing re-encryption
for f in data/d001/projects/*/*.adoc; do
age="${f}.age"
if [ -f "$f" ] && [ -f "$age" ]; then
pt_mod=$(/usr/bin/stat -c'%Y' "$f")
age_mod=$(/usr/bin/stat -c'%Y' "$age")
[ "$pt_mod" -gt "$age_mod" ] && echo "STALE: $f"
fi
done
Secure delete — shred for sensitive plaintext
shred -u data/d001/projects/mandiant-remediation/man-report.txt
On SSD/NVMe, shred is less effective (wear leveling), but better than rm which only removes the directory entry.
|
Pre-push audit — find all unencrypted project files
find data/d001/projects -type f ! -name '*.age' ! -name 'README.adoc' ! -name '.gitkeep' ! -name '*.py' | sort
System & Infrastructure
PipeWire audio validation
wpctl status # PipeWire status
pactl list sinks short # list audio sinks
pw-play /usr/share/sounds/freedesktop/stereo/bell.oga # test default sink
journalctl -b --grep='sof|cs35l56' --no-pager | tail -20 # kernel audio firmware
cat /proc/asound/cards # ALSA sound cards
gopass — personal document management
gopass-personal-docs # interactive entry creation
gopass-query bills # list recurring bills with totals
gopass-query storage # list storage units with gate codes
gopass-query export bills # export category to JSON
Makefile — daily workflow
make new-day # create today's worklog + update attributes
make serve # build + local server (port 8000)
make # build only
make sync-nav # sync worklog nav entries
make update-index # rebuild monthly index
KVM — VM & ISO management
ssh kvm-01 "sudo virsh list --all"
ssh kvm-02 "sudo virsh list --all"
ssh kvm-01 "ls -lh /mnt/nas/isos/*[Ii][Ss][Ee]* /var/lib/libvirt/images/*[Ii][Ss][Ee]* /mnt/onboard-ssd/isos/*[Ii][Ss][Ee]* 2>/dev/null"
ssh kvm-02 "ls -lh /mnt/nas/isos/*[Ii][Ss][Ee]* /mnt/ssd/libvirt/images/*[Ii][Ss][Ee]* 2>/dev/null"
sudo virsh console <vm-name> # Escape: Ctrl+]
ssh kvm-01 "mount | grep nas; ls /mnt/"
Per-project file dashboard
for d in data/d001/projects/*/; do
total=$(find "$d" -type f | wc -l)
plain=$(find "$d" -type f ! -name '*.age' ! -name 'README.adoc' ! -name '.gitkeep' ! -name '*.py' | wc -l)
echo "$(basename "$d") | ${total} files | ${plain} plaintext"
done
USB-C / Thunderbolt Charging Diagnostics
{
echo "=== Power Supply ==="
cat /sys/class/power_supply/*/status
echo ""
cat /sys/class/power_supply/*/type
echo ""
echo "=== UPower ==="
upower -d | grep -E 'state|percentage|energy-rate|voltage'
echo ""
echo "=== dmesg (typec/thunderbolt/PD) ==="
sudo dmesg | grep -iE 'typec|thunderbolt|ucsi|PD|power.delivery|charging' | tail -20
echo ""
echo "=== Pacman log (kernel/typec) ==="
grep -iE 'thunderbolt|typec|ucsi|^.*upgraded linux ' /var/log/pacman.log | tail -20
} | tee /tmp/INC-$(date +%F)-usbc-charging.txt
Pattern: { } groups commands into a single stdout stream. tee writes to file AND displays on screen. Reusable for any multi-command evidence capture — change the commands inside, keep the structure.
pacman — package inspection
Source: 2026-06-25 — curl error 77 investigation, checking for package upgrades
# Check package version and install date (awk field filter)
pacman -Qi curl | awk '/^Version|^Install Date/'