WRKLOG-2026-06-09
Summary
Monday. Heavy documentation and operations session. Decrypted and placed 4 encrypted session captures from 06-08 into d001 (intune-cert, rsyslog commands, syslog session). Created 5 runbooks (VNC removal, rsyslog new sources, Monad pipeline, NAS resync, TCP clocks batch 2). Full BMS Controller Segmentation project migrated from Principia LaTeX (9 sections, 5 Mermaid diagrams, 4 PDFs) into d001 encrypted project with d001 open/close workflow. Git tree audited — removed misplaced files (Alexandra Angulo resume, generated PDFs, runtime lock files), created .graveyard/ for local archive. TCP clock batch 2: 9 new MACs added to ISE via ERS batch API, discovered new switch deployed without RADIUS/AAA config — clocks in ISE but can’t authenticate.
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 |
57 |
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 |
37 |
✅ 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 |
64 |
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 |
94 |
P0 - ACTIVE — ISE integration in progress |
Guest Redirect ACL |
Guest redirect ACL work needed. Related to Mandiant remediation findings. |
2026-05-12 |
32 |
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 |
93 |
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 |
96 |
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 |
96 |
P0 - Blocked by k3s |
Strongline Gateway VLAN fix |
8 devices in wrong identity group (David Rukiza assigned) |
2026-03-16 |
89 |
P0 - TODO |
TCP Clocks deployment |
ISE identity group validation, query outputs, comms with team. Active d001 data Apr 22-23. |
2026-04-22 |
52 |
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 |
59 |
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 |
58 |
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 |
93 |
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 |
93 |
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
Triage Status
| Item | Status | Destination |
|---|---|---|
⚡ CHARGE TIME IN PEOPLESOFT — Monday critical (carryover day 2) |
❌ not started |
PeopleSoft → submit hours before EOD |
VNC removal — uninstall/disable across affected systems |
🟡 runbook created, ready to execute |
|
rsyslog → Monad pipeline — build ingest pipeline for ASA lab logs |
🟡 runbook created, architecture decided |
|
Point ISE lab, switch, FMC → rsyslog (CHLXSYSLOG01) |
🟡 runbook created, source + server configs documented |
|
Secondary NAS mount unsynced — resync failed (carryover day 2) |
🟡 diagnostic runbook created |
|
P1: Intune hybrid join cert auth failure (carryover day 2) |
🟡 investigation file created, commands ready |
|
P1: Spam bypass — SCL 9 delivered to inbox (carryover day 2) |
🟡 investigation file created, commands ready |
spam-bypass-investigation.adoc — run Phase 1 on work machine |
Install BYOD P12 cert on ZFold7 (carryover day 2) |
🟡 cert issued, needs P12 bundle transfer |
Phase 8: BYOD Certs |
TCP Clocks batch 2 — 9 MACs added to ISE, switch NOT configured |
⚠️ ISE done, switch blocking |
New switch needs RADIUS/AAA config + ISE NAD entry |
Fix dsec embedded quotes in vault unseal keys (carryover day 2) |
❌ not started |
dsec edit d000 dev/vault — remove wrapping double quotes |
Rejoin AD from ISE GUI (carryover day 2) |
❌ not started |
Phase 6 — DOMUS_AD join point |
Fix NAS SSH config (adminerosado → evanusmodestus) (carryover day 2) |
❌ not started |
~/.ssh/config line 245 |
Fix WLC SSH config (add PubkeyAuthentication no) (carryover day 2) |
❌ not started |
~/.ssh/config — add Host 9800-wlc-01 block |
Add NTP rule to vyos-02 (carryover day 2) |
❌ not started |
set firewall ipv4 name MGMT_LOCAL rule 45 |
Add kvm-01 NAS mounts to /etc/fstab (carryover day 2) |
❌ not started |
10.50.1.70:/volume1/isos → /mnt/nas/isos |
Generalize build-antora-page.sh for HTML output (carryover day 2) |
❌ not started |
scripts/build-antora-page.sh |
Explore AsciiDoc CSV tables (carryover day 2) |
❌ not started |
codex/documentation/asciidoc.adoc or discoveries/ |
Printer unreachable — iPSK auth + VLAN filtering (carryover day 2) |
❌ blocked |
Depends on ISE rotation completing |
Linux Remediation — reuse Shahab model (carryover day 2) |
🟡 inventory found, summary built |
d001/linux-research/ + domus-ise-linux |
iTrack → Jira (Atlassian) migration (carryover day 2) |
🟡 announced — team must become proficient |
New project — needs codes.adoc entry, d001 scaffolding |
P0/P1/P2 tracker refresh — 47 days stale (carryover day 2) |
❌ not started |
trackers/work/priorities/ |
BMS Controller Segmentation — Principia migration |
✅ done |
|
Git tree audit — remove misplaced files |
✅ done |
|
Encrypted file placement (06-08 captures) |
✅ done |
4 files placed in d001 investigations + siem project |
TCP Clocks — one-off 40:AC:8D:00:94:10 reassigned |
✅ done |
Reassigned to IoT_Onboard via ERS PUT |
ASA VPN Migration — Okta RADIUS → Entra SAML plan |
✅ plan complete, PDF built |
|
Abnormal — Hoxhunt SCL -1 investigation |
✅ done |
|
Abnormal — Policy review commands (20 sections + sclizer) |
✅ done |
|
ISE Lab → rsyslog → Monad pipeline guide |
✅ execution guide created |
|
RAD Projects — scaffolded |
✅ done |
|
Build toolchain — Rouge + theme overhaul |
✅ done |
thankful_eyes, dark terminal blocks, build-adoc.sh fixed |
Dashboard system — 11 SVG + 14 PNG + 12 domain diagrams |
✅ done |
|
YAML substrate — project-master.yaml + association engine |
✅ done |
37 projects in YAML, 107 triples in association engine, CLAUDE.md graph |
Ad-Hoc Requests
VNC Removal — Security Hardening
VNC (Virtual Network Computing) uses the RFB protocol, which transmits session data unencrypted by default — including keyboard input, screen content, and in some configurations, authentication credentials. In a hospital environment this creates multiple risk surfaces:
-
No centralized authentication — VNC passwords are stored locally per host, outside AD/LDAP. No MFA, no group policy enforcement, no audit trail.
-
HIPAA exposure — unencrypted screen sharing of patient data over the network violates the technical safeguard requirements of 45 CFR 164.312(e)(1).
-
Unauthorized remote access — VNC servers are frequently installed by end users or vendors without change control. Ports 5900-5910 listening on production hosts represent unmanaged ingress.
-
No session logging — unlike RDP with NLA or SSH with centralized logging, VNC connections leave no audit trail in SIEM.
Authorized remote access uses approved tools (CyberArk, BeyondTrust, RDP with NLA + MFA). VNC is not on the approved software list.
Windows — Discovery and Removal
Step 1: Discover VNC Installations
# RealVNC
Get-ItemProperty "HKLM:\SOFTWARE\RealVNC\*" -ErrorAction SilentlyContinue
Get-ItemProperty "HKLM:\SOFTWARE\WOW6432Node\RealVNC\*" -ErrorAction SilentlyContinue
# TightVNC
Get-ItemProperty "HKLM:\SOFTWARE\TightVNC" -ErrorAction SilentlyContinue
Get-ItemProperty "HKLM:\SOFTWARE\WOW6432Node\TightVNC" -ErrorAction SilentlyContinue
# UltraVNC
Get-ItemProperty "HKLM:\SOFTWARE\uvnc bvba\UltraVNC" -ErrorAction SilentlyContinue
# TigerVNC
Get-ItemProperty "HKLM:\SOFTWARE\TigerVNC" -ErrorAction SilentlyContinue
Get-Service | Where-Object { $_.DisplayName -match "vnc" -or $_.Name -match "vnc" } |
Format-Table Name, DisplayName, Status, StartType -AutoSize
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" |
Where-Object { $_.DisplayName -match "vnc" } |
Select-Object DisplayName, DisplayVersion, UninstallString, InstallDate |
Format-List
Get-ScheduledTask | Where-Object { $_.TaskName -match "vnc" -or $_.TaskPath -match "vnc" } |
Format-Table TaskName, TaskPath, State -AutoSize
Get-NetFirewallRule | Where-Object { $_.DisplayName -match "vnc" } |
Format-Table DisplayName, Direction, Action, Enabled -AutoSize
# Also check by port — VNC rules may not mention VNC in the name
Get-NetFirewallPortFilter | Where-Object { $_.LocalPort -match "^590[0-9]$" } |
Get-NetFirewallRule |
Format-Table DisplayName, Direction, Action, Enabled -AutoSize
Get-NetTCPConnection -State Listen |
Where-Object { $_.LocalPort -ge 5900 -and $_.LocalPort -le 5910 } |
Select-Object LocalAddress, LocalPort, OwningProcess,
@{N='ProcessName';E={(Get-Process -Id $_.OwningProcess).ProcessName}} |
Format-Table -AutoSize
Step 2: Stop and Disable VNC Services
Get-Service | Where-Object { $_.DisplayName -match "vnc" -or $_.Name -match "vnc" } | ForEach-Object {
Write-Host "Stopping: $($_.DisplayName) [$($_.Name)]" -ForegroundColor Yellow
Stop-Service -Name $_.Name -Force
Set-Service -Name $_.Name -StartupType Disabled
Write-Host " Stopped and disabled." -ForegroundColor Green
}
Step 3: Uninstall VNC Variants
# Gather uninstall entries
$vnc = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" |
Where-Object { $_.DisplayName -match "vnc" }
foreach ($app in $vnc) {
Write-Host "Uninstalling: $($app.DisplayName)" -ForegroundColor Yellow
Write-Host " UninstallString: $($app.UninstallString)" -ForegroundColor Cyan
# Attempt silent uninstall — adjust flags per vendor
if ($app.UninstallString -match "msiexec") {
# MSI-based: add /quiet /norestart
$msiArgs = ($app.UninstallString -replace "msiexec.exe\s*", "") + " /quiet /norestart"
Start-Process "msiexec.exe" -ArgumentList $msiArgs -Wait
} else {
# EXE-based: try /S (NSIS) or /SILENT (Inno Setup)
$exe = $app.UninstallString -replace '"', ''
Start-Process $exe -ArgumentList "/S" -Wait -ErrorAction SilentlyContinue
}
}
# RealVNC Server
& "C:\Program Files\RealVNC\VNC Server\vnclicense.exe" -deactivate 2>$null
& "C:\Program Files\RealVNC\VNC Server\vncserver.exe" -unregister 2>$null
# TightVNC — typical MSI GUID (verify with registry query above)
# msiexec /x {YOUR-GUID-HERE} /quiet /norestart
# UltraVNC
& "C:\Program Files\uvnc bvba\UltraVNC\unins000.exe" /SILENT 2>$null
Step 4: Remove Firewall Rules
# Remove rules named with VNC
Get-NetFirewallRule | Where-Object { $_.DisplayName -match "vnc" } | ForEach-Object {
Write-Host "Removing firewall rule: $($_.DisplayName)" -ForegroundColor Yellow
Remove-NetFirewallRule -Name $_.Name
}
# Remove rules allowing VNC ports regardless of name
Get-NetFirewallPortFilter | Where-Object { $_.LocalPort -match "^590[0-9]$" } |
Get-NetFirewallRule | ForEach-Object {
Write-Host "Removing port-based rule: $($_.DisplayName) [port filter match]" -ForegroundColor Yellow
Remove-NetFirewallRule -Name $_.Name
}
Step 5: Clean Up Residual Files
$vnc_paths = @(
"$env:ProgramFiles\RealVNC",
"${env:ProgramFiles(x86)}\RealVNC",
"$env:ProgramFiles\TightVNC",
"${env:ProgramFiles(x86)}\TightVNC",
"$env:ProgramFiles\uvnc bvba",
"${env:ProgramFiles(x86)}\uvnc bvba",
"$env:ProgramFiles\TigerVNC",
"${env:ProgramFiles(x86)}\TigerVNC"
)
foreach ($path in $vnc_paths) {
if (Test-Path $path) {
Write-Host "Removing: $path" -ForegroundColor Yellow
Remove-Item -Path $path -Recurse -Force
}
}
GPO Enforcement (Preventive)
Deploy a Group Policy to block reinstallation:
-
Software Restriction Policy or AppLocker rule blocking execution of
vncserver.exe,tvnserver.exe,winvnc.exe,vncviewer.exe -
Windows Firewall GPO denying inbound TCP 5900-5910 at the domain level
-
SCCM/Intune compliance rule flagging VNC as non-compliant software
These require AD/GPO admin access — coordinate with the Windows team and document as a CR per STD-005.
Linux — Discovery and Removal
Step 1: Discover VNC Installations
# Active VNC services
systemctl list-units --type=service --all | grep -i vnc
# Enabled VNC services (survive reboot)
systemctl list-unit-files --type=service | grep -i vnc
dpkg -l | awk '/vnc|tigervnc|tightvnc|x11vnc|vino/{print $1, $2, $3}'
rpm -qa | grep -iE 'vnc|tigervnc|tightvnc|x11vnc|vino'
ss -tlnp | awk '$4 ~ /:590[0-9]/ {print}'
# Example output:
# LISTEN 0 5 0.0.0.0:5901 0.0.0.0:* users:(("Xvnc",pid=1234,fd=7))
ps aux | awk '/[Vv]nc|[Xx]11vnc|[Vv]ino/{print $1, $2, $11}'
find /etc /home -name "*.vnc" -o -name "xstartup" -o -name ".vnc" -type d 2>/dev/null
Step 2: Stop and Remove
# Stop all VNC-related services
for svc in $(systemctl list-units --type=service --all --no-legend | awk '/vnc/{print $1}'); do
echo "Stopping: ${svc}"
sudo systemctl stop "${svc}"
sudo systemctl disable "${svc}"
echo " Stopped and disabled."
done
# Verify what will be removed first
apt list --installed 2>/dev/null | grep -iE 'vnc|tigervnc|tightvnc|x11vnc|vino'
# Remove (purge removes config files too)
sudo apt purge -y tigervnc-standalone-server tigervnc-common \
tightvncserver x11vnc vino 2>/dev/null
sudo apt autoremove -y
# Verify first
rpm -qa | grep -iE 'vnc|tigervnc|tightvnc|x11vnc|vino'
# Remove
sudo dnf remove -y tigervnc-server tigervnc x11vnc 2>/dev/null
Step 3: Firewall Cleanup
# Check for VNC rules
sudo ufw status numbered | grep -i "590[0-9]"
# Delete by rule number (adjust number from output above)
# sudo ufw delete <rule-number>
# Or deny the range explicitly
sudo ufw deny 5900:5910/tcp comment "Block VNC — unauthorized remote access"
sudo ufw reload
# Check current rules
sudo firewall-cmd --list-all | grep -E "590[0-9]|vnc"
# Remove VNC service if added
sudo firewall-cmd --permanent --remove-service=vnc-server 2>/dev/null
# Block VNC ports explicitly
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" port port="5900-5910" protocol="tcp" reject'
sudo firewall-cmd --reload
Step 4: Clean Up User VNC Directories
# Find and list .vnc directories across all home dirs
find /home -maxdepth 2 -name ".vnc" -type d 2>/dev/null
# Remove them (after confirming the list above)
# find /home -maxdepth 2 -name ".vnc" -type d -exec rm -rf {} +
Network Infrastructure — Management Access Audit
VNC is not a standard management protocol for network gear, but unauthorized access methods should be verified during the sweep.
Cisco ASA
show run | include vnc
show run | include http server
show run | include ssh
show run management-access
Cisco IOS/IOS-XE Switches
show run | section line vty
show run | include ip http
show ip ssh
show run | include access-class
Firepower Management Center (FMC)
# FMC management is web-based (HTTPS 443)
# Verify no additional listeners on unexpected ports
show network # FMC CLI
show access-list # Check for VNC port permits on managed firewalls
The primary concern on network gear is ensuring ACLs do not permit VNC ports (5900-5910) through the infrastructure. Audit firewall rules for these ports:
# ASA
show access-list | include 5900
show access-list | include 5901
# IOS-XE
show ip access-lists | include 5900
Post-Removal Verification
Port Scan Validation
# Single host
# Replace <target-ip> with the remediated host
# Example: nmap -p 5900-5910 10.50.4.100
nmap -sT -p 5900-5910 <target-ip>
# Subnet sweep — find any remaining VNC listeners
# Example: nmap -p 5900-5910 10.50.4.0/24
nmap -sT -p 5900-5910 --open <subnet-cidr>
Expected result: all ports show closed or filtered, never open.
Service Validation
# Services
Get-Service | Where-Object { $_.DisplayName -match "vnc" }
# Expected: no output
# Listening ports
Get-NetTCPConnection -State Listen |
Where-Object { $_.LocalPort -ge 5900 -and $_.LocalPort -le 5910 }
# Expected: no output
# Registry
Get-ItemProperty "HKLM:\SOFTWARE\*VNC*", "HKLM:\SOFTWARE\WOW6432Node\*VNC*" -ErrorAction SilentlyContinue
# Expected: no output
# Installed programs
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" |
Where-Object { $_.DisplayName -match "vnc" }
# Expected: no output
# Services
systemctl list-units --type=service --all | grep -i vnc
# Expected: no output
# Listening ports
ss -tlnp | awk '$4 ~ /:590[0-9]/'
# Expected: no output
# Packages
dpkg -l 2>/dev/null | grep -i vnc # Debian
rpm -qa 2>/dev/null | grep -i vnc # RHEL
# Expected: no output (or status 'rc' = removed, config remaining)
# Processes
pgrep -ai vnc
# Expected: no output
Validation Checklist
| Step | Check | Result |
|---|---|---|
1 |
No VNC services running ( |
[ ] Pass |
2 |
No VNC ports listening ( |
[ ] Pass |
3 |
No VNC packages installed ( |
[ ] Pass |
4 |
No VNC firewall allow rules remain |
[ ] Pass |
5 |
VNC ports blocked by host firewall (UFW/firewalld/Windows Firewall) |
[ ] Pass |
6 |
Network ACLs deny 5900-5910 inbound |
[ ] Pass |
7 |
Nmap scan confirms no open VNC ports on remediated hosts |
[ ] Pass |
8 |
GPO/Intune compliance rule deployed (Windows — preventive) |
[ ] Pass |
Evidence Collection
Document remediation per host for the security team:
# Capture evidence to a timestamped file per host
# Example: HOSTNAME=ws-radiology-04
echo "=== VNC Removal Evidence — $(hostname) ===" > "/tmp/vnc-removal-$(hostname)-$(date +%F).txt"
echo "Date: $(date)" >> "/tmp/vnc-removal-$(hostname)-$(date +%F).txt"
echo "" >> "/tmp/vnc-removal-$(hostname)-$(date +%F).txt"
echo "--- Services ---" >> "/tmp/vnc-removal-$(hostname)-$(date +%F).txt"
systemctl list-units --type=service --all 2>/dev/null | grep -i vnc >> "/tmp/vnc-removal-$(hostname)-$(date +%F).txt"
echo "(none found)" >> "/tmp/vnc-removal-$(hostname)-$(date +%F).txt"
echo "--- Listening Ports ---" >> "/tmp/vnc-removal-$(hostname)-$(date +%F).txt"
ss -tlnp | awk 'NR==1 || $4 ~ /:590[0-9]/' >> "/tmp/vnc-removal-$(hostname)-$(date +%F).txt"
echo "--- Packages ---" >> "/tmp/vnc-removal-$(hostname)-$(date +%F).txt"
dpkg -l 2>/dev/null | grep -i vnc >> "/tmp/vnc-removal-$(hostname)-$(date +%F).txt"
rpm -qa 2>/dev/null | grep -i vnc >> "/tmp/vnc-removal-$(hostname)-$(date +%F).txt"
echo "--- Nmap ---" >> "/tmp/vnc-removal-$(hostname)-$(date +%F).txt"
echo "Run from scanning host: nmap -sT -p 5900-5910 $(hostname -I | awk '{print $1}')" >> "/tmp/vnc-removal-$(hostname)-$(date +%F).txt"
rsyslog — Adding ISE, Switch, and FMC Sources
Three new log sources for CHLXSYSLOG01. ASA is already flowing. Same architecture: device sends syslog to 10.248.13.254:514, rsyslog writes per-source log files, Monad ingests downstream.
Part 1: Source-Side Configuration
1A — Cisco ISE (Home Lab)
ISE sends syslog natively. Two steps: define the remote target, then assign it to logging categories.
Administration → System → Logging → Remote Logging Targets → Add Name: CHLXSYSLOG01 IP Address: 10.248.13.254 Port: 514 Facility: LOCAL6 Max Length: 8192 Include Alarms: Yes (optional — adds ISE health alarms to syslog)
Administration → System → Logging → Logging Categories Category → Enable → Target: CHLXSYSLOG01 ───────────────────────────────────────────────────────────────── Passed Authentications → ✓ Failed Attempts → ✓ RADIUS Diagnostics → ✓ Authentication Flow Diagnostics → ✓ Administrator Audits → ✓ (optional — tracks admin logins) System Diagnostics → ✓ (optional — ISE health) Posture and Client Provisioning → ✓ (optional)
# SSH to ISE, enter application shell
# ISE does not expose syslog target config via CLI — GUI only.
# CLI can verify after configuration:
show logging application
show logging system
CISE_Passed_Authentications — Successful 802.1X/MAB/RADIUS auths CISE_Failed_Attempts — Auth failures (wrong cert, expired, policy deny) CISE_RADIUS_Diagnostics — RADIUS protocol-level events CISE_RADIUS_Accounting — Accounting start/stop/interim CISE_Administrative_and_Operational_Audit — Admin GUI/CLI actions CISE_Profiler — Endpoint profiling events CISE_System_Diagnostics — ISE node health, replication CISE_Threat_Centric_NAC — TC-NAC verdicts (if enabled)
1B — Cisco Switch (IOS / IOS-XE)
conf t
logging host 10.248.13.254
logging trap informational
logging facility local6
logging source-interface Loopback0 (1)
logging on
end
| 1 | Use the management or loopback interface so the source IP is deterministic. Replace Loopback0 with the actual management interface (Vlan10, GigabitEthernet0/0, etc.). |
show logging
show logging | include Logging to
Logging to 10.248.13.254 (udp port 514, audit disabled,
link up),
27 message lines logged,
0 message lines rate-limited,
0 message lines dropped-by-MD,
xml disabled, sequence number disabled
filtering disabled
Logging Source-Interface: Loopback0
%SEC-6-IPACCESSLOGP — ACL permit/deny hits %LINK-3-UPDOWN — Interface state changes %LINEPROTO-5-UPDOWN — Line protocol state changes %SYS-5-CONFIG_I — Config change from terminal %AUTHMGR-5-* — 802.1X auth manager events %DOT1X-5-* — 802.1X protocol events %MAB-5-* — MAB authentication events %EPM-6-POLICY_APP* — Endpoint policy application (dACL, VLAN) %RADIUS-4-RADIUS_DEAD — RADIUS server unreachable
1C — Cisco FMC (Firepower Management Center)
FMC sends its own management/audit logs. FTD sensors send connection/intrusion events separately.
System → Configuration → Logging → Syslog Server
→ Add Syslog Server
IP Address: 10.248.13.254
Port: 514
Protocol: UDP
Facility: LOCAL5
Enable logging for:
✓ Health Events
✓ Audit Logs
✓ Intrusion Events (if applicable)
✓ Connection Events (high volume — enable selectively)
! On FMC: Devices → Platform Settings → <policy> → Syslog
! Or via FlexConfig:
platform logging host inside 10.248.13.254 udp/514
platform logging trap informational
platform logging facility local5
FTD platform logging requires a FlexConfig policy deployed from FMC. Direct SSH to FTD does not persist syslog config.
|
FMC audit logs: "User '<user>' ... from <ip>" — Admin login/logout "Policy '<name>' ... deployed" — Policy deployment "Task '<name>' ... completed/failed" — Scheduled task results FTD connection events (if enabled): %FTD-6-430003 — Connection permitted %FTD-4-430002 — Connection denied %FTD-1-430001 — Intrusion event FTD intrusion events: GID:SID format (e.g., 1:40000) — Snort rule triggers Classification, Priority, Source/Dest — Full 5-tuple Health events: "Health Alert: <module> ... <status>" — Disk, CPU, interface health
Part 2: rsyslog Server-Side Configuration (CHLXSYSLOG01)
Three config files under /etc/rsyslog.d/. Numbering convention: 10-asa.conf already exists from 2026-06-08, so these start at 20-.
Pre-check — current state before changes
# What configs exist now?
ls -la /etc/rsyslog.d/
# Is the ASA config from yesterday still active?
sudo awk '/^[^#]/ && NF' /etc/rsyslog.d/10-asa.conf
# Current disk usage — baseline before adding sources
df -h /var | awk 'NR==1 || /var/'
du -sh /var/log/asa/ 2>/dev/null || echo "No ASA log dir yet"
2A — ISE Config
sudo tee /etc/rsyslog.d/20-ise.conf << 'RSYSLOG'
# ISE syslog — home lab ISE instance
# Source IP: <ISE_IP> (replace with actual ISE management IP)
# Facility: local6 (set on ISE remote logging target)
# Created: 2026-06-09
template(name="IseFile" type="string"
string="/var/log/ise/%HOSTNAME%/%$YEAR%-%$MONTH%-%$DAY%.log")
if $fromhost-ip == '<ISE_IP>' then {
action(type="omfile" dynaFile="IseFile"
FileCreateMode="0640"
DirCreateMode="0750"
CreateDirs="on")
stop
}
RSYSLOG
Replace <ISE_IP> with the actual management IP of the ISE node. This is the IP that rsyslog will see as the source. Confirm with ip -br addr on ISE or check the ISE GUI under Administration → System → Deployment → Node.
|
sudo awk '/^[^#]/ && NF' /etc/rsyslog.d/20-ise.conf
2B — Switch Config
sudo tee /etc/rsyslog.d/20-switch.conf << 'RSYSLOG'
# Network switch syslog — IOS/IOS-XE
# Source IP: <SWITCH_IP> (replace with switch management/source-interface IP)
# Facility: local6 (set on switch: logging facility local6)
# Created: 2026-06-09
template(name="SwitchFile" type="string"
string="/var/log/switch/%HOSTNAME%/%$YEAR%-%$MONTH%-%$DAY%.log")
if $fromhost-ip == '<SWITCH_IP>' then {
action(type="omfile" dynaFile="SwitchFile"
FileCreateMode="0640"
DirCreateMode="0750"
CreateDirs="on")
stop
}
RSYSLOG
Replace <SWITCH_IP> with the IP of the switch’s logging source-interface. If the switch has no source-interface configured, rsyslog sees whatever egress IP the switch routes from.
|
sudo awk '/^[^#]/ && NF' /etc/rsyslog.d/20-switch.conf
2C — FMC Config
sudo tee /etc/rsyslog.d/20-fmc.conf << 'RSYSLOG'
# FMC syslog — Cisco Firepower Management Center
# Source IP: <FMC_IP> (replace with FMC management IP)
# Facility: local5 (set on FMC syslog server config)
# Created: 2026-06-09
template(name="FmcFile" type="string"
string="/var/log/fmc/%HOSTNAME%/%$YEAR%-%$MONTH%-%$DAY%.log")
if $fromhost-ip == '<FMC_IP>' then {
action(type="omfile" dynaFile="FmcFile"
FileCreateMode="0640"
DirCreateMode="0750"
CreateDirs="on")
stop
}
RSYSLOG
sudo awk '/^[^#]/ && NF' /etc/rsyslog.d/20-fmc.conf
Config load order
The full /etc/rsyslog.d/ should now look like:
/etc/rsyslog.d/ ├── 10-asa.conf ← existing (2026-06-08) ├── 20-fmc.conf ← new ├── 20-ise.conf ← new ├── 20-switch.conf ← new └── (any other existing configs)
Order matters: rsyslog processes if/stop rules top-down. The stop directive ensures a message matching one source does not also land in the default syslog. Numbered prefixes control evaluation order.
Part 3: Validation
Step 1 — Syntax check before restart
sudo /usr/sbin/rsyslogd -N1
rsyslogd: version 8.2504.0, config validation run (level 1)... rsyslogd: End of config validation run. Bye.
If errors appear, fix the offending config file. Common mistakes: missing closing brace, unquoted IP, typo in template name.
Step 2 — Restart rsyslog
# Before — confirm current PID and uptime
systemctl show rsyslog --property=MainPID,ActiveEnterTimestamp
# Change
sudo systemctl restart rsyslog
# After — confirm new PID and clean status
systemctl show rsyslog --property=MainPID,ActiveEnterTimestamp
systemctl is-active rsyslog && echo "rsyslog running" || echo "FAILED"
sudo journalctl -u rsyslog --since "30 seconds ago" --no-pager
Step 3 — Verify packets arriving per source
# ISE — filter by ISE source IP
sudo tcpdump -i any src host <ISE_IP> and port 514 -nn -c 5
# Switch — filter by switch source IP
sudo tcpdump -i any src host <SWITCH_IP> and port 514 -nn -c 5
# FMC — filter by FMC source IP
sudo tcpdump -i any src host <FMC_IP> and port 514 -nn -c 5
Step 4 — Verify logs landing on disk
# ISE
ls -la /var/log/ise/ 2>/dev/null && tail -5 /var/log/ise/*/$(date +%Y-%m-%d).log 2>/dev/null || echo "No ISE logs yet"
# Switch
ls -la /var/log/switch/ 2>/dev/null && tail -5 /var/log/switch/*/$(date +%Y-%m-%d).log 2>/dev/null || echo "No switch logs yet"
# FMC
ls -la /var/log/fmc/ 2>/dev/null && tail -5 /var/log/fmc/*/$(date +%Y-%m-%d).log 2>/dev/null || echo "No FMC logs yet"
# Pane 1: ISE
sudo tail -f /var/log/ise/*/$(date +%Y-%m-%d).log
# Pane 2: Switch
sudo tail -f /var/log/switch/*/$(date +%Y-%m-%d).log
# Pane 3: FMC
sudo tail -f /var/log/fmc/*/$(date +%Y-%m-%d).log
Step 5 — Trigger test events
# From a wired endpoint, disconnect/reconnect the cable.
# Or from ISE CLI:
show logging application | tail -20
# Any RADIUS auth attempt generates CISE_Passed_Authentications or CISE_Failed_Attempts
! On the switch — any config change generates %SYS-5-CONFIG_I
conf t
hostname <current-hostname>
end
! Or shut/no shut a test interface
FMC GUI: System → Health → Health Monitor → Run All Modules This generates health event syslogs immediately. Or: make any policy change → deploy → generates audit log.
Validation Checklist
| Source | Verification Command | Expected Result |
|---|---|---|
ISE — target configured |
ISE GUI: Administration → System → Logging → Remote Logging Targets |
CHLXSYSLOG01 listed, IP 10.248.13.254, port 514 |
ISE — categories assigned |
ISE GUI: Administration → System → Logging → Logging Categories |
Passed Auths + Failed Attempts → CHLXSYSLOG01 |
Switch — logging host set |
|
|
Switch — facility correct |
|
|
FMC — syslog server added |
FMC GUI: System → Configuration → Logging |
10.248.13.254 UDP 514 listed |
rsyslog — config valid |
|
|
rsyslog — service running |
|
|
rsyslog — listening |
|
|
ISE — packets arriving |
|
Packets captured from ISE IP |
Switch — packets arriving |
|
Packets captured from switch IP |
FMC — packets arriving |
|
Packets captured from FMC IP |
ISE — logs on disk |
|
Directory with hostname subdirectory, today’s date file |
Switch — logs on disk |
|
Directory with hostname subdirectory, today’s date file |
FMC — logs on disk |
|
Directory with hostname subdirectory, today’s date file |
No default syslog bleed |
|
|
Post-Validation: Logrotate
The dynamic file paths (/var/log/ise/, /var/log/switch/, /var/log/fmc/) are not covered by the default /etc/logrotate.d/rsyslog. Add rotation or these directories will grow unbounded.
cat /etc/logrotate.d/rsyslog
sudo tee /etc/logrotate.d/rsyslog-sources << 'LOGROTATE'
/var/log/asa/*/*.log
/var/log/ise/*/*.log
/var/log/switch/*/*.log
/var/log/fmc/*/*.log
{
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 0640 syslog adm
sharedscripts
postrotate
/usr/lib/rsyslog/rsyslog-rotate
endscript
}
LOGROTATE
sudo logrotate -d /etc/logrotate.d/rsyslog-sources
Monad Pipeline — rsyslog Forwarding Configuration
Architecture
The rsyslog server acts as a centralized buffer between log sources and Monad. This is deliberately not a pass-through — the disk-assisted queue on rsyslog absorbs bursts, survives Monad outages, and decouples source availability from pipeline health.
ASA Lab ──────┐
ISE Lab ──────┤
Switch ───────┤──> rsyslog (CHLXSYSLOG01) ──> Monad Pipeline ──> Sentinel
FMC ──────────┤ 10.248.13.254 (syslog input) (output)
Win Hosts ────┘ │
disk queue
(500MB buffer)
Part 1: rsyslog Forwarding to Monad (on CHLXSYSLOG01)
Option A: omfwd — TCP/TLS Forwarding (Recommended)
The omfwd module ships with rsyslog — no extra packages. It forwards syslog
over TCP (optionally TLS) to Monad’s syslog input endpoint. The critical advantage
is the disk-assisted queue: if Monad is unreachable, rsyslog buffers to disk and
retries automatically. Direct source-to-Monad has no such buffer — when Monad drops,
logs are lost.
sudo tee /etc/rsyslog.d/90-monad-forward.conf << 'RSYSLOG'
# Forward all collected logs to Monad syslog input
# Disk-assisted queue provides backpressure and survives outages
#
# queue.type="LinkedList" — in-memory linked list (fast, low overhead)
# queue.filename="monad-fwd" — enables disk assistance; files land in WorkDirectory
# queue.maxDiskSpace="500m" — cap disk usage at 500MB before dropping
# queue.saveOnShutdown="on" — flush in-memory queue to disk on rsyslog stop/restart
# action.resumeRetryCount="-1" — retry forever (never give up on Monad)
# action.resumeInterval="30" — wait 30s between retry attempts
#
# template: RSYSLOG_SyslogProtocol23Format = RFC 5424 structured syslog
# Monad's syslog input expects RFC 5424. If it chokes, try RSYSLOG_TraditionalForwardFormat (BSD).
action(type="omfwd"
target="<MONAD_SYSLOG_ENDPOINT>"
port="<PORT>"
protocol="tcp"
template="RSYSLOG_SyslogProtocol23Format"
queue.type="LinkedList"
queue.filename="monad-fwd"
queue.maxDiskSpace="500m"
queue.saveOnShutdown="on"
action.resumeRetryCount="-1"
action.resumeInterval="30")
RSYSLOG
|
Replace |
Queue Parameters Explained
| Parameter | Purpose |
|---|---|
|
In-memory linked list. Lower overhead than FixedArray for bursty traffic. When the in-memory portion fills, it spills to disk via the filename. |
|
Enables disk assistance. Without this, the queue is memory-only and data is lost on restart. Files are created in rsyslog’s |
|
Hard cap on disk usage. At ~200 bytes per syslog message, 500MB holds roughly 2.5 million messages. Tune based on expected outage duration and message rate. |
|
On |
|
Retry forever. The value |
|
Seconds between retry attempts when the action is suspended (Monad unreachable). 30 seconds is conservative — not aggressive enough to hammer, frequent enough to recover quickly. |
This is the backpressure advantage. Direct source-to-Monad forwarding (the rolled-back ASA config) has none of this — when the destination is down, messages hit the floor.
Option B: omhttp — HTTP POST Forwarding (Alternative)
If Monad exposes a REST/HTTP input endpoint (not just syslog), omhttp can POST
log batches as JSON. This requires the rsyslog-omhttp package.
dpkg -l | awk '/rsyslog.*http/'
# If not installed:
# sudo apt install rsyslog-omhttp
sudo tee /etc/rsyslog.d/90-monad-forward-http.conf << 'RSYSLOG'
module(load="omhttp")
template(name="MonadJSON" type="list") {
constant(value="{\"message\":\"")
property(name="msg" format="jsonf")
constant(value="\",\"host\":\"")
property(name="hostname")
constant(value="\",\"facility\":\"")
property(name="syslogfacility-text")
constant(value="\",\"severity\":\"")
property(name="syslogseverity-text")
constant(value="\",\"timestamp\":\"")
property(name="timereported" dateformat="rfc3339")
constant(value="\"}")
}
action(type="omhttp"
server="<MONAD_HTTP_ENDPOINT>"
serverport="443"
restpath="<MONAD_HTTP_PATH>"
usehttps="on"
httpheaderkey="Authorization"
httpheadervalue="ApiKey <MONAD_API_KEY>"
template="MonadJSON"
batch="on"
batch.maxsize="100"
queue.type="LinkedList"
queue.filename="monad-http-fwd"
queue.maxDiskSpace="500m"
queue.saveOnShutdown="on"
action.resumeRetryCount="-1"
action.resumeInterval="30")
RSYSLOG
|
Option A (omfwd/syslog) is strongly preferred. The syslog input on Monad handles parsing, TLS cert routing, and native syslog semantics. Option B requires additional package installation, JSON template maintenance, and embeds the API key in rsyslog config. Use Option B only if Monad’s syslog endpoint is unavailable or if you need structured JSON ingestion. |
Apply and Verify rsyslog Config
sudo rsyslogd -N1 2>&1 | tail -5
sudo systemctl restart rsyslog
sudo systemctl status rsyslog --no-pager
ls -la /var/spool/rsyslog/
# Should see monad-fwd.* files appear once messages start queuing
Part 2: Monad Pipeline Creation (API Calls from Workstation)
All commands run from your workstation. Requires MONAD_API_KEY and MONAD_API_URL
environment variables, plus MONAD_ORG_ID.
printf "API URL: %s\nOrg ID: %s\nKey set: %s\n" \
"${MONAD_API_URL:-NOT SET}" \
"${MONAD_ORG_ID:-NOT SET}" \
"$([ -n "${MONAD_API_KEY:-}" ] && echo yes || echo NO)"
Step 1: List Existing Pipelines
curl -s -X GET \
-H "X-API-Key: ${MONAD_API_KEY}" \
"${MONAD_API_URL}/v2/${MONAD_ORG_ID}/pipelines" | \
jq '.pipelines[] | {name, id, enabled}'
Step 2: Create Syslog Input
Create a syslog input that will receive forwarded logs from rsyslog (CHLXSYSLOG01).
curl -s -X GET \
-H "X-API-Key: ${MONAD_API_KEY}" \
"${MONAD_API_URL}/v1/inputs" | \
jq '.[] | select(.name | test("syslog"; "i")) | {type_id, name, description}'
curl -s -X POST \
-H "X-API-Key: ${MONAD_API_KEY}" \
-H "Content-Type: application/json" \
"${MONAD_API_URL}/v1/${MONAD_ORG_ID}/inputs" \
-d '{
"name": "CHLXSYSLOG01-Forward",
"description": "Syslog forwarded from centralized rsyslog collector (10.248.13.254)",
"type": "monad-syslog",
"config": {
"settings": {}
}
}' | jq '.'
|
Capture the |
SYSLOG_INPUT_ID="<id-from-response>"
Step 3: Create the Pipeline
curl -s -X POST \
-H "X-API-Key: ${MONAD_API_KEY}" \
-H "Content-Type: application/json" \
"${MONAD_API_URL}/v2/${MONAD_ORG_ID}/pipelines" \
-d '{
"name": "CHLA-rsyslog-to-Sentinel",
"description": "Centralized rsyslog (CHLXSYSLOG01) forwarding to Microsoft Sentinel",
"enabled": false
}' | jq '.'
PIPELINE_ID="<id-from-response>"
Step 4: Add Syslog Input Node to Pipeline
INPUT_NODE_ID=$(cat /proc/sys/kernel/random/uuid)
curl -s -X PATCH \
-H "X-API-Key: ${MONAD_API_KEY}" \
-H "Content-Type: application/json" \
"${MONAD_API_URL}/v2/${MONAD_ORG_ID}/pipelines/${PIPELINE_ID}" \
-d "$(jq -n \
--arg node_id "$INPUT_NODE_ID" \
--arg input_id "$SYSLOG_INPUT_ID" \
'{
nodes: [{
id: $node_id,
slug: ("input-" + ($node_id | split("-")[0])),
component_id: $input_id,
component_type: "input",
enabled: true
}]
}')" | jq '.'
Step 5: Create or Reference Sentinel Output
curl -s -X GET \
-H "X-API-Key: ${MONAD_API_KEY}" \
"${MONAD_API_URL}/v1/${MONAD_ORG_ID}/outputs" | \
jq '.outputs[] | select(.name | test("sentinel"; "i")) | {name, id, type}'
If a Sentinel output already exists, reuse its ID. Otherwise, create one:
curl -s -X GET \
-H "X-API-Key: ${MONAD_API_KEY}" \
"${MONAD_API_URL}/v1/outputs" | \
jq '.[] | select(.name | test("sentinel"; "i")) | {type_id, name}'
curl -s -X POST \
-H "X-API-Key: ${MONAD_API_KEY}" \
-H "Content-Type: application/json" \
"${MONAD_API_URL}/v1/${MONAD_ORG_ID}/outputs" \
-d '{
"name": "CHLA-Sentinel",
"description": "Microsoft Sentinel workspace for CHLA SIEM",
"type": "microsoft-sentinel",
"config": {
"settings": {
"tenant_id": "<AZURE_TENANT_ID>",
"client_id": "<AZURE_CLIENT_ID>",
"client_secret": "<AZURE_CLIENT_SECRET>",
"workspace_id": "<LOG_ANALYTICS_WORKSPACE_ID>",
"table_name": "Syslog"
}
}
}' | jq '.'
SENTINEL_OUTPUT_ID="<id-from-response-or-existing>"
Step 6: Add Output Node and Create Edge
OUTPUT_NODE_ID=$(cat /proc/sys/kernel/random/uuid)
curl -s -X PATCH \
-H "X-API-Key: ${MONAD_API_KEY}" \
-H "Content-Type: application/json" \
"${MONAD_API_URL}/v2/${MONAD_ORG_ID}/pipelines/${PIPELINE_ID}" \
-d "$(jq -n \
--arg input_node "$INPUT_NODE_ID" \
--arg output_node "$OUTPUT_NODE_ID" \
--arg output_id "$SENTINEL_OUTPUT_ID" \
'{
nodes: [{
id: $output_node,
slug: ("output-" + ($output_node | split("-")[0])),
component_id: $output_id,
component_type: "output",
enabled: true
}],
edges: [{
name: "always",
from_node_instance_id: $input_node,
to_node_instance_id: $output_node,
conditions: { operator: "always" }
}]
}')" | jq '.'
The edge condition "operator": "always" forwards all messages unconditionally.
To filter, replace with a rule-based condition:
{
"conditions": {
"operator": "and",
"rules": [{
"field": "message",
"operator": "contains",
"value": "%ASA-"
}]
}
}
Available edge operators: always, never, and, or, nor, xor.
Available rule operators: contains, ends_with, equals, exists, matches, starts_with.
Step 7: Activate the Pipeline
curl -s -X PATCH \
-H "X-API-Key: ${MONAD_API_KEY}" \
-H "Content-Type: application/json" \
"${MONAD_API_URL}/v1/${MONAD_ORG_ID}/pipelines/${PIPELINE_ID}" \
-d '{"enabled": true}' | jq '{id, name, enabled}'
Step 8: Verify Pipeline Status
curl -s -X GET \
-H "X-API-Key: ${MONAD_API_KEY}" \
"${MONAD_API_URL}/v2/${MONAD_ORG_ID}/pipelines/${PIPELINE_ID}" | \
jq '{
name, enabled,
nodes: [.nodes[] | {slug, component_type, enabled}],
edges: [.edges[] | {name, from: .from_node_instance_id[:8], to: .to_node_instance_id[:8]}]
}'
curl -s -X GET \
-H "X-API-Key: ${MONAD_API_KEY}" \
"${MONAD_API_URL}/v1/${MONAD_ORG_ID}/logs/pipelines/${PIPELINE_ID}" | \
jq '.logs[:10] | .[] | {timestamp, level, message}'
Part 3: Validation
Monad Pipeline Metrics
curl -s -X GET \
-H "X-API-Key: ${MONAD_API_KEY}" \
"${MONAD_API_URL}/v1/${MONAD_ORG_ID}/logs/pipelines/${PIPELINE_ID}" | \
jq '{
total_logs: (.logs | length),
errors: [.logs[] | select(.level == "error")] | length,
latest: .logs[0].timestamp
}'
rsyslog Queue Status (on CHLXSYSLOG01)
# Queue spool files — if these exist, messages are buffered (Monad was/is unreachable)
ls -lh /var/spool/rsyslog/monad-fwd.* 2>/dev/null || echo "No queue files (good — messages flowing)"
# rsyslog internal stats (if impstats is enabled)
grep 'monad-fwd' /var/log/rsyslog-stats.log 2>/dev/null | tail -5
# Quick health: is rsyslog forwarding or queuing?
sudo journalctl -u rsyslog --since "10 min ago" --no-pager | \
awk '/monad|queue|suspend|resume/'
# Enable impstats if not already (add to /etc/rsyslog.conf)
# module(load="impstats" interval="60" format="cee" log.syslog="on")
# Then:
sudo grep -c 'monad-fwd' /var/log/syslog && echo "Queue stats present"
End-to-End Test
The goal: generate a log event on a source device, watch it traverse rsyslog, appear in Monad metrics, and land in Sentinel.
! On the ASA CLI — generate a syslog event
logging message 199999 level 6
! Or simply make a minor config change and revert
# On CHLXSYSLOG01
sudo tail -f /var/log/syslog | awk '/%ASA/{print; count++; if(count>=3) exit}'
# On your workstation — check Monad pipeline logs (allow 60s for propagation)
sleep 60 && curl -s -X GET \
-H "X-API-Key: ${MONAD_API_KEY}" \
"${MONAD_API_URL}/v1/${MONAD_ORG_ID}/logs/pipelines/${PIPELINE_ID}" | \
jq '.logs[:5]'
// In Log Analytics workspace — Sentinel
Syslog
| where TimeGenerated > ago(10m)
| where SyslogMessage contains "%ASA-"
| take 10
Validation Checklist
| # | Check | Command / Method | Expected |
|---|---|---|---|
1 |
rsyslog config valid |
|
No errors |
2 |
rsyslog service running |
|
|
3 |
Queue spool directory exists |
|
Directory present |
4 |
No persistent queue files |
|
No files (messages flowing) |
5 |
Monad pipeline enabled |
GET |
|
6 |
Pipeline has input + output nodes |
GET |
2 nodes minimum |
7 |
Edge connects input to output |
GET |
1+ edges |
8 |
No pipeline errors |
GET |
No |
9 |
ASA logs visible in rsyslog |
|
Recent ASA messages |
10 |
Logs arriving in Sentinel |
KQL: |
Events present |
Rollback
If the pipeline causes issues — disable forwarding without losing the configuration.
# Comment out the forward, don't delete — you'll want it back
sudo sed -i 's/^action(type="omfwd"/# action(type="omfwd"/' /etc/rsyslog.d/90-monad-forward.conf
sudo systemctl restart rsyslog
curl -s -X PATCH \
-H "X-API-Key: ${MONAD_API_KEY}" \
-H "Content-Type: application/json" \
"${MONAD_API_URL}/v1/${MONAD_ORG_ID}/pipelines/${PIPELINE_ID}" \
-d '{"enabled": false}' | jq '{id, name, enabled}'
NAS Secondary Mount — Resync Diagnostics
Phase 0: Current State Assessment
Establish ground truth before touching anything.
mount | awk '/nfs|cifs|smb/{print}'
findmnt -t nfs,nfs4,cifs
awk '/nfs|cifs/' /etc/fstab
ping -c 2 10.50.1.70
showmount -e 10.50.1.70
# Replace <secondary-mount> with the actual mount point from findmnt output above
timeout 5 stat /mnt/nas/<secondary-mount> 2>&1
ls -la /mnt/nas/<secondary-mount>/ 2>&1
If stat hangs past the 5-second timeout, the NFS handle is stale.
Proceed directly to Phase 2, Option A or B.
|
Phase 1: Diagnose the Failure
dmesg | awk '/nfs|NFS|stale/i'
nfsstat -c
rpcinfo -p 10.50.1.70
# Derive the unit name: mount path /mnt/nas/foo becomes mnt-nas-foo.mount
systemctl list-units --type=mount | awk '/nas/'
# Then check the specific unit
systemctl status <unit-name>.mount
journalctl -u <unit-name>.mount --no-pager -n 50
# ESTALE (errno 116) in dmesg or strace confirms stale handle
dmesg | awk '/ESTALE|116|Stale file handle/'
Phase 2: Recovery Options
Work through these in order — least disruptive first.
# Verify before
findmnt /mnt/nas/<path>
# Change — lazy unmount detaches without blocking on busy files
sudo umount -l /mnt/nas/<path> && sudo mount /mnt/nas/<path>
# Verify after
findmnt /mnt/nas/<path>
ls -la /mnt/nas/<path>/
# Verify before — check what processes hold the mount
sudo lsof +f -- /mnt/nas/<path> 2>/dev/null | awk 'NR<=10'
# Change
sudo umount -f /mnt/nas/<path>
sudo mount /mnt/nas/<path>
# Verify after
findmnt /mnt/nas/<path>
stat /mnt/nas/<path>
Force unmount (-f) can cause data loss in processes with open writes.
Check lsof output before proceeding.
|
# Check if rsync left a partial transfer
find /mnt/nas/<path> -name '.~tmp~' -o -name '*.partial' 2>/dev/null
# Dry-run first — see what would transfer
rsync -avz --dry-run --progress <source>/ /mnt/nas/<path>/
# Execute when dry-run looks correct
rsync -avz --progress <source>/ /mnt/nas/<path>/
# SSH to NAS if enabled
ssh admin@10.50.1.70
# On the NAS: check volume health
cat /proc/mdstat
df -h
# Check NFS export config
cat /etc/exports
# Check shared folder permissions
ls -la /volume1/
Phase 3: Resync Verification
After recovery, confirm data integrity.
echo "--- Primary ---"
find /mnt/nas/<primary> -type f | wc -l
echo "--- Secondary ---"
find /mnt/nas/<secondary> -type f | wc -l
diff <(find /mnt/nas/<primary> -type f -printf '%P\n' | sort) \
<(find /mnt/nas/<secondary> -type f -printf '%P\n' | sort)
rsync -avzc --progress /mnt/nas/<primary>/ /mnt/nas/<secondary>/
The -c flag forces checksum comparison instead of mod-time/size.
Slower but catches silent corruption.
|
Phase 4: Prevention
# /etc/systemd/system/mnt-nas-secondary.automount
cat <<'UNIT'
[Unit]
Description=Automount NAS secondary share
[Automount]
Where=/mnt/nas/<secondary>
TimeoutIdleSec=300
[Install]
WantedBy=multi-user.target
UNIT
#!/usr/bin/env bash
# /usr/local/bin/nas-mount-check.sh
MOUNT="/mnt/nas/<secondary>"
if ! timeout 5 stat "$MOUNT" &>/dev/null; then
logger -t nas-check "ALERT: $MOUNT stale or unreachable — attempting remount"
umount -l "$MOUNT" 2>/dev/null
mount "$MOUNT"
if timeout 5 stat "$MOUNT" &>/dev/null; then
logger -t nas-check "OK: $MOUNT remounted successfully"
else
logger -t nas-check "FAIL: $MOUNT remount failed — manual intervention required"
fi
fi
# Verify before
crontab -l 2>/dev/null | awk '/nas/'
# Add if absent
(crontab -l 2>/dev/null; echo '*/15 * * * * /usr/local/bin/nas-mount-check.sh') | crontab -
# Verify after
crontab -l | awk '/nas/'
# /etc/autofs/auto.master.d/nas.autofs
echo "/mnt/nas /etc/autofs/auto.nas --timeout=300" | sudo tee /etc/autofs/auto.master.d/nas.autofs
# /etc/autofs/auto.nas — indirect map
# <key> <options> <server>:<export>
# secondary -fstype=nfs4,rw 10.50.1.70:/volume1/secondary
sudo systemctl enable --now autofs
sudo systemctl status autofs
TCP Clocks — Batch 2 Onboarding (2026-06-09)
Request
9 new TCP time clocks (Kaba replacements). New OUI: 40:AC:BD (batch 1 was 40:AC:8D).
| MAC Address |
|---|
40:AC:BD:00:95:AB |
40:AC:BD:00:95:AD |
40:AC:BD:00:95:AE |
40:AC:BD:00:95:AF |
40:AC:BD:00:95:B0 |
40:AC:BD:00:95:B1 |
40:AC:BD:00:96:04 |
40:AC:BD:00:96:05 |
40:AC:BD:00:96:06 |
ISE — Endpoints Added
All 9 MACs added to IoT_Onboard via ERS batch add (self-contained block in d001/projects/tcp-clocks/partials/queries-ers.adoc, line 177+). Static group assignment, description stamped with date and PM.
Issue Found — New Switch Not Configured for ISE
| Clocks were added to ISE but the switch they connect to is not configured as a RADIUS client in ISE. New switch was deployed without 802.1X/MAB configuration. |
-
Clocks will not authenticate — switch is not sending RADIUS requests to ISE
-
Endpoints sit in ISE but never get a session
-
No dACL enforcement, no VLAN assignment
-
New switch introduced without InfoSec coordination
-
No RADIUS/AAA config, no ISE network device entry
-
Switch was not part of the existing NAD (Network Access Device) inventory
| Step | Action | Status |
|---|---|---|
1 |
Identify the new switch — hostname, IP, model, location |
❌ |
2 |
Add switch as Network Device in ISE (Administration → Network Resources → Network Devices) |
❌ |
3 |
Configure AAA on the switch — |
❌ |
4 |
Configure switch ports for MAB — |
❌ |
5 |
Verify RADIUS connectivity — |
❌ |
6 |
Verify clock authentication — |
❌ |
7 |
Validate clocks hitting |
❌ |
! AAA configuration
aaa new-model
aaa authentication dot1x default group radius
aaa authorization network default group radius
aaa accounting dot1x default start-stop group radius
! RADIUS server
radius server ISE-PAN
address ipv4 <ISE_PAN_IP> auth-port 1812 acct-port 1813
key <SHARED_SECRET>
! Global 802.1X
dot1x system-auth-control
! Per-port MAB (for BMS/IoT devices)
interface range GigabitEthernet1/0/1-48
switchport mode access
authentication host-mode multi-auth
authentication order dot1x mab
authentication priority dot1x mab
authentication port-control auto
mab
dot1x pae authenticator
spanning-tree portfast
! Via ISE GUI:
! Administration → Network Resources → Network Devices → Add
! Name: <switch-hostname>
! IP: <switch-ip>
! RADIUS Shared Secret: <same as switch config>
! Network Device Group: Location > <building>, Type > Switch
! Or via ERS API:
ers_write POST "/networkdevice" '{
"NetworkDevice": {
"name": "<switch-hostname>",
"authenticationSettings": {
"radiusSharedSecret": "<SHARED_SECRET>",
"enableKeyWrap": false
},
"NetworkDeviceIPList": [{
"ipaddress": "<switch-ip>",
"mask": 32
}],
"NetworkDeviceGroupList": [
"Location#All Locations",
"Device Type#All Device Types#Switch"
]
}
}'
Lesson Learned
New network equipment deployments MUST include InfoSec notification for ISE NAD registration. Without RADIUS client config on the switch and a corresponding Network Device entry in ISE, 802.1X/MAB enforcement is bypassed entirely — devices get flat network access with no segmentation.
Architectus Digitalis — Your Universal Identity
What This Is
A passphrase-protected age key for everything outside your personal vault. Teaching, collaboration, file sharing, future projects — one key, one identity.
~/.age/
├── identities/
│ ├── personal.key ← YOURS. UNTOUCHED. d001/encrypt-file/decrypt-file
│ └── personal.pub ← YOURS. UNTOUCHED.
├── recipients/
│ └── self.txt ← YOURS. UNTOUCHED.
└── architectus-digitalis/ ← NEW — your universal key
├── architectus-digitalis.key ← passphrase-protected private key
├── architectus-digitalis.pub ← public key (share freely)
├── recipients-team.txt ← who can decrypt team files
└── notes.adoc.age ← your encrypted notes
Step 1 — Create the Directory
umask 077
mkdir -p ~/.age/architectus-digitalis
ls -la ~/.age/
drwx------ architectus-digitalis drwx------ identities drwx------ recipients
Step 2 — Generate Key + Passphrase-Protect in One Pipeline
The private key NEVER touches disk as plaintext. age-keygen outputs the secret to stdout, which pipes directly into age -p (passphrase encryption). The public key prints to stderr (your terminal) — copy it from there.
umask 077
age-keygen | age -p -o ~/.age/architectus-digitalis/architectus-digitalis.key
You will see on your terminal (stderr):
# created: 2026-06-09T... # public key: age1...YOUR_PUBLIC_KEY...
Copy that age1… line — you need it for Step 3.
Then age prompts you for a passphrase — TWICE. Choose something strong. This passphrase protects the key at rest.
The AGE-SECRET-KEY-1… line was piped directly into the encrypted file. It never appeared on screen, never hit disk, never entered terminal scrollback. This is by design.
|
Step 3 — Derive and Verify the Public Key
Extract the public key FROM the encrypted private key. This simultaneously verifies your passphrase works and the key file isn’t corrupt.
age -d ~/.age/architectus-digitalis/architectus-digitalis.key \
| age-keygen -y \
> ~/.age/architectus-digitalis/architectus-digitalis.pub
cat ~/.age/architectus-digitalis/architectus-digitalis.pub
This should output age1… — the same public key you saw in Step 2. The secret key was decrypted in memory, fed to age-keygen -y which extracted the public key, and the secret was discarded. No plaintext key on disk, no plaintext key in terminal scrollback.
Step 4 — Create Your Notes File
An encrypted notes file for passphrase hints, collaborator log, and usage history.
cat > /tmp/architectus-digitalis-notes.adoc << 'EOF'
= Architectus Digitalis — Identity Notes
:revdate: 2026-06-09
== Identity
[cols="1,3"]
|===
| Field | Value
| Key Name | Architectus Digitalis
| Created | 2026-06-09
| Owner | Evan Rosado
| Purpose | Universal — collaboration, teaching, file exchange, future projects
| Public Key | (see architectus-digitalis.pub)
| Passphrase Hint | (write YOUR hint here — NOT the actual passphrase)
|===
== Collaborators
[cols="1,2,2"]
|===
| Name | Public Key (age1...) | Added
| _first collaborator_ | _their key_ | _date_
|===
== Usage Log
[cols="1,3"]
|===
| Date | Action
| 2026-06-09 | Key generated. Passphrase set. Zero plaintext exposure.
|===
== Rules
1. NEVER share architectus-digitalis.key (the encrypted private key file)
2. NEVER type the passphrase into chat, email, or any logged system
3. Share architectus-digitalis.pub freely — it can only encrypt, not decrypt
4. Add collaborators by appending their age1... to recipients-team.txt
5. This key is SEPARATE from personal.key — different purpose, different trust boundary
6. Removing a collaborator from recipients-team.txt only affects FUTURE encryptions — they can still decrypt anything previously shared with them. If trust is broken, that's a key rotation event.
== Backup
The passphrase-protected .key file is safe to back up:
- Copy to gocryptfs vault or dsec-managed store
- Consider paper backup of the secret key (one line) in a physically secure location
- If the NVMe dies without a backup, every file encrypted to this key is unrecoverable
EOF
age -e -R ~/.age/architectus-digitalis/architectus-digitalis.pub \
-o ~/.age/architectus-digitalis/notes.adoc.age \
/tmp/architectus-digitalis-notes.adoc
# /tmp is tmpfs on Arch — rm suffices (shred is pointless on tmpfs)
rm /tmp/architectus-digitalis-notes.adoc
/tmp is tmpfs on Arch — plaintext lives in RAM only, never written to disk. Verify: findmnt /tmp | grep tmpfs. The one risk: if you don’t have encrypted swap (or zram), tmpfs pages can be swapped out. Check: swapon --show or zramctl.
|
Step 5 — Initialize Recipients File
{
echo "# Architectus Digitalis — Evan Rosado"
cat ~/.age/architectus-digitalis/architectus-digitalis.pub
echo ""
echo "# Add collaborators below — one age1... public key per line"
} > ~/.age/architectus-digitalis/recipients-team.txt
cat ~/.age/architectus-digitalis/recipients-team.txt
Step 6 — Verify Everything
stat -c '%a %U %G' ~/.age/identities/personal.key
# Expected: 600 evanusmodestus evanusmodestus
d001 open tcp-clocks && d001 close tcp-clocks
# Should decrypt and re-encrypt normally
echo "architectus test $(date)" > /tmp/arch-test.txt
# Encrypt with recipients
age -e -R ~/.age/architectus-digitalis/recipients-team.txt \
-o /tmp/arch-test.txt.age /tmp/arch-test.txt
# Decrypt with architectus (passphrase prompted)
age -d -i <(age -d ~/.age/architectus-digitalis/architectus-digitalis.key) \
-o /tmp/arch-test-decrypted.txt /tmp/arch-test.txt.age
# Compare
diff /tmp/arch-test.txt /tmp/arch-test-decrypted.txt && echo "✓ architectus works"
# Clean up (tmpfs — rm suffices)
rm /tmp/arch-test.txt /tmp/arch-test.txt.age /tmp/arch-test-decrypted.txt
echo "test" | age -e -R ~/.age/recipients/self.txt \
| age -d -i <(age -d ~/.age/architectus-digitalis/architectus-digitalis.key) \
>/dev/null 2>&1 \
&& echo "✗ PROBLEM: architectus decrypted a personal file" \
|| echo "✓ architectus correctly excluded from personal vault"
Step 7 — How to Use Daily
Convention: stage all plaintext in /tmp (tmpfs), never in $HOME. This eliminates the rm/shred question entirely.
# Stage in /tmp, encrypt, send the .age file
cp ~/some-file.txt /tmp/
age -e -R ~/.age/architectus-digitalis/recipients-team.txt -o /tmp/some-file.txt.age /tmp/some-file.txt
rm /tmp/some-file.txt
# Send some-file.txt.age via email, Teams, USB — it's encrypted
age -d -i <(age -d ~/.age/architectus-digitalis/architectus-digitalis.key) \
-o /tmp/decrypted.txt received-file.age
# Passphrase prompted once — plaintext lands in tmpfs
echo "# Friend Name" >> ~/.age/architectus-digitalis/recipients-team.txt
echo "age1theirpublickey..." >> ~/.age/architectus-digitalis/recipients-team.txt
age -d -i <(age -d ~/.age/architectus-digitalis/architectus-digitalis.key) \
-o /tmp/notes.adoc ~/.age/architectus-digitalis/notes.adoc.age
nvim -n -i NONE /tmp/notes.adoc
# After editing, re-encrypt and clean
age -e -R ~/.age/architectus-digitalis/architectus-digitalis.pub \
-o ~/.age/architectus-digitalis/notes.adoc.age /tmp/notes.adoc
rm /tmp/notes.adoc
nvim -n -i NONE disables swap files and viminfo — prevents Neovim from writing plaintext fragments to ~/.local/state/nvim/. Without this flag, your decrypted notes could persist in Neovim’s undo history on disk.
|
Backup
The passphrase-protected .key file is safe to copy — without the passphrase it’s useless.
# Copy to your gocryptfs vault or dsec-managed store
cp ~/.age/architectus-digitalis/architectus-digitalis.key /path/to/secure/backup/
# Consider: paper backup of the raw secret key (one line)
# Decrypt to terminal, write by hand, store physically
age -d ~/.age/architectus-digitalis/architectus-digitalis.key
# Copy the AGE-SECRET-KEY-1... line to paper. Store in safe/lockbox.
# This is your recovery path if the NVMe dies.
| If the key file is lost AND you have no backup, every file encrypted to Architectus Digitalis is permanently unrecoverable. The passphrase cannot help without the key file. |
Revocation
Removing a collaborator from recipients-team.txt only prevents them from decrypting future files. Everything previously encrypted while their key was in the recipients list remains decryptable by them — forever. The math doesn’t change retroactively.
If trust is actually broken (key compromised, collaborator hostile):
# 1. Generate a NEW key (repeat Step 2)
# 2. Update recipients-team.txt with your new public key
# 3. Remove the compromised collaborator
# 4. Notify remaining collaborators to update their copy
# 5. Re-encrypt ALL shared files with the new recipients
# 6. The OLD key and all files encrypted to it are considered burned
This is a key rotation event, not a recipients-file edit.
Final State
~/.age/architectus-digitalis/ ├── architectus-digitalis.key ← passphrase-protected (600) ├── architectus-digitalis.pub ← public key — share freely (644) ├── recipients-team.txt ← team access list (644) └── notes.adoc.age ← your encrypted notes (644)
No plaintext private key ever touched disk. Passphrase required for every use. Completely isolated from personal.key.
Commands of the day
Terminal Commands
Glob with NULLglob — suppress errors on no matches
setopt NULLglob (zsh) prevents "no matches found" errors when a glob pattern
expands to nothing. Without it, ls *.xyz in an empty match throws an error.
With it, the pattern silently expands to nothing.
setopt NULLglob
ls /mnt/c/Users/erosado/Downloads/*.{png,pdf,jpg,jpeg}
/mnt/c/Users/erosado/Downloads/abnormal-project-v2.pdf /mnt/c/Users/erosado/Downloads/CHLA_Abnormal_ReadWrite_Permissions.pdf /mnt/c/Users/erosado/Downloads/Configuring SCEP with Microsoft NDES for Chrome Enterprise (2).pdf /mnt/c/Users/erosado/Downloads/guest-acl-update-2026-04-16.pdf /mnt/c/Users/erosado/Downloads/image.png /mnt/c/Users/erosado/Downloads/intune-cert-commands-2026-06-08-for-tony.pdf
bash equivalent is shopt -s nullglob. The zsh setopt variant is session-scoped — resets on new shell.
|
PowerShell — BMS File Discovery
Full search session in d001/projects/bms-controller-segmentation/partials/pwsh-search-commands-2026-06-09.adoc.
Get-ChildItem "C:\Users\erosado" -Recurse -File -ErrorAction SilentlyContinue |
Where-Object {
$_.Extension -match '\.vsdx|\.pdf|\.xlsx|\.docx' -and
$_.Name -match 'bms|johnson|metasys|niagara|jci|BAS'
} |
Sort-Object LastWriteTime -Descending |
Select-Object -First 30 FullName, LastWriteTime
Build Script Gotcha — partials vs pages
Always build from pages/, never from partials/ directly. Building a partial standalone causes Asciidoctor PDF table cell truncation errors — the partial’s content renders as one oversized cell.
# WRONG — 25 truncation errors, 1.1MB ./scripts/build-antora-page.sh docs/modules/ROOT/partials/worklog/daily-notes/2026-06-09.adoc pdf # CORRECT — clean build, 2.4MB ./scripts/build-antora-page.sh docs/modules/ROOT/pages/2026/06/WRKLOG-2026-06-09.adoc pdf
ISE ERS — Batch Endpoint Add Pattern
Self-contained block in d001/projects/tcp-clocks/partials/queries-ers.adoc line 177+.
gid=$(ers "/endpointgroup?filter=name.EQ.${TARGET_GROUP}" | \
jq -r '.SearchResult.resources[0].id // empty')
while read -r MAC; do
exists=$(ers "/endpoint?filter=mac.EQ.${MAC}" | \
jq -r '.SearchResult.resources[0].id // empty')
[[ -n "$exists" ]] && echo "SKIP: ${MAC}" && continue
# POST to /ers/config/endpoint with MAC, description, groupId
done << 'EOF'
<MAC list heredoc>
EOF
./scripts/build-antora-page.sh docs/modules/ROOT/partials/worklog/daily-notes/2026-06-09.adoc pdf --theme light-cyan 2>&1
→ Repo: domus-captures
→ Source: /home/evanusmodestus/atelier/_bibliotheca/domus-captures/docs/modules/ROOT/partials/worklog/daily-notes/2026-06-09.adoc
→ Partials: /home/evanusmodestus/atelier/_bibliotheca/domus-captures/docs/modules/ROOT/partials
→ Examples: /home/evanusmodestus/atelier/_bibliotheca/domus-captures/docs/modules/ROOT/examples
→ Building 2026-06-09
========================================
Building: /tmp/antora-build-189671/2026-06-09.adoc
========================================
→ Building PDF...
asciidoctor: ERROR: the table cell on page 2 has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page
asciidoctor: ERROR: the table cell on page 3 has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page
asciidoctor: ERROR: the table cell on page 4 has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page
asciidoctor: ERROR: the table cell on page 5 has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page
asciidoctor: ERROR: the table cell on page 6 has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page
asciidoctor: ERROR: the table cell on page 7 has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page
asciidoctor: ERROR: the table cell on page 8 has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page
asciidoctor: ERROR: the table cell on page 9 has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page
asciidoctor: ERROR: the table cell on page 10 has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page
asciidoctor: ERROR: the table cell on page 11 has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page
asciidoctor: ERROR: the table cell on page 12 has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page
asciidoctor: ERROR: the table cell on page 13 has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page
asciidoctor: ERROR: the table cell on page 14 has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page
asciidoctor: ERROR: the table cell on page 15 has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page
asciidoctor: ERROR: the table cell on page 16 has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page
asciidoctor: ERROR: the table cell on page 17 has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page
asciidoctor: ERROR: the table cell on page 18 has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page
asciidoctor: ERROR: the table cell on page 19 has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page
asciidoctor: ERROR: the table cell on page 20 has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page
asciidoctor: ERROR: the table cell on page 21 has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page
asciidoctor: ERROR: the table cell on page 22 has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page
asciidoctor: ERROR: the table cell on page 23 has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page
asciidoctor: ERROR: the table cell on page 24 has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page
asciidoctor: ERROR: the table cell on page 25 has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page
✓ PDF: 1.1M
========================================
Build complete!
========================================
Output directory: /tmp/antora-build-189671/output
-rw-r--r-- 1 evanusmodestus evanusmodestus 1.1M Jun 9 13:25 2026-06-09.pdf
✓ Output: /home/evanusmodestus/atelier/_bibliotheca/domus-captures/docs/modules/ROOT/partials/worklog/daily-notes/output/
-rw-r--r-- 1 evanusmodestus evanusmodestus 1.1M Jun 9 13:25 /home/evanusmodestus/atelier/_bibliotheca/domus-captures/docs/modules/ROOT/partials/worklog/daily-notes/output/2026-06-09.pdf
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.
-
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)
Encrypted File Placement (from 06-08)
-
Decrypted 4
.agefiles, placed into d001 structure -
intune-cert-commands→d001/investigations/2026-06-08-intune-cert-auth/ -
rsyslog-commands,rsyslog-commands-test→d001/projects/siem-qradar-to-sentinel/scripts/ -
syslog-2026-06-08→d001/projects/siem-qradar-to-sentinel/
Runbooks Created (5)
-
VNC Removal — Windows PowerShell discovery + uninstall, Linux apt/dnf, network device audit, nmap verification
-
rsyslog New Sources — ISE, switch, FMC → CHLXSYSLOG01 (source-side config + 3 rsyslog server-side configs)
-
Monad Pipeline — rsyslog
omfwdforwarding with disk-assisted queue, 8-step Monad API pipeline creation -
NAS Resync — stale handle detection, 4 recovery options, systemd automount prevention
-
TCP Clocks Batch 2 — 9 MACs added, switch not configured, action plan with AAA template
BMS Controller Segmentation — Full Migration from Principia
-
Source:
Principia/02_Assets/PRJ-ISE-CHLA/profiling/BMS-Controller-Segmentation/(LaTeX, doc ID 2025-ISE-088) -
12 AsciiDoc partials: executive summary, network architecture, ISE policy (77 device models), dACL (28 ACEs), Claroty integration (risk 63.8→42.4), troubleshooting, device inventory, quick reference, acronyms, issues, todos, search commands
-
5 Mermaid diagrams: campus topology, MAB auth flow, protocol map, dACL enforcement, device onboarding
-
4 legacy PDFs + 5 rendered SVGs in
output/ -
All encrypted in
data/d001/projects/bms-controller-segmentation/—d001 open/close bms-controller -
README with pull list for 15 remaining files on work machine (3 Visio sources, 3 spreadsheets, 3 BAS diagrams, policy docs)
Git Tree Audit & Cleanup
-
Created
.graveyard/— local archive directory (gitignored) -
Removed from tracking: Alexandra Angulo resume (PDF+TXT), ISE analytics PDF, 2 generated worklog PDFs
-
Removed runtime state:
.claude/scheduled_tasks.json,.claude/scheduled_tasks.lock -
Added
.mmdtodata/gitignore pattern
TCP Clocks Batch 2 — ISE ERS Operations
-
9 new MACs (OUI
40:AC:BD) added toIoT_Onboardvia ERS batch add with exists-check -
Updated
queries-ers.adoc: self-contained batch block (dsource + ers function + heredoc), 3 validation queries -
Updated
queries-dataconnect.adoc: widened all SQL filters for new OUI prefixes -
Updated
context.adoc: batch 2 OUI and status -
Issue discovered: new switch deployed without RADIUS/AAA config — endpoints in ISE but can’t authenticate. Switch onboarding template documented.
rsyslog Architecture Decision
-
Confirmed rsyslog as collection tier in front of Monad (not sources → Monad direct)
-
Advantages: protocol normalization, disk-assisted queues (backpressure), edge filtering, single config point for new sources
-
Pattern matches enterprise standard: sources → collector (rsyslog) → pipeline (Monad) → SIEM (Sentinel)
ASA VPN Migration: Okta RADIUS → Entra ID SAML
-
Full migration plan built (PRJ-2026-06-asa-vpn-okta-to-entra)
-
ASA baseline captured: 2 tunnel groups (CHLA_CORPORATE_USERS, CHLA_BYOD_USERS), group policies, crypto/certs, DAP, active sessions
-
6 ISE policy screenshots captured (BYOD + Corporate — policy set, authc rules, authz rules)
-
Entra app config guide: SAML URLs, claims, Conditional Access, cert import, ASA SAML IdP config
-
ISE role analysis: full before/after comparison — ISE keeps authorization, accounting, posture unchanged. Only authentication moves to SAML.
-
5-phase timeline: Prep → Entra (Justin Halbmann) → ASA (Tony Sun) + ISE (Evan) → Cutover → Cleanup
-
Risk matrix: 6 risks including VPN SSL cert expiring 2026-07-28 (49 days)
-
12-step migration checklist with owners
-
PDF built with light-cyan theme + thankful_eyes syntax highlighting
TCP Clocks — Batch 2 + Switch Discovery
-
9 new MACs (OUI 40:AC:BD) added to ISE via ERS batch API
-
1 one-off clock (40:AC:8D:00:94:10) reassigned to IoT_Onboard
-
Discovered new switch deployed without RADIUS/AAA config — clocks in ISE but can’t authenticate
-
ERS queries file reorganized: CRUD ops grouped, batch add self-contained with ers function, 3 validation queries added
-
DataConnect queries widened for new OUI prefixes
Abnormal Security / Email Policy Review
-
Hoxhunt phishing simulation investigation: SCL -1 bypass, intentional delivery confirmed
-
Connect-ExchangeOnline troubleshooting: msalruntime DLL fix documented
-
Policy review validation commands rebuilt: 20 sections (anti-spam, anti-phish, outbound, Safe Links, Safe Attachments, transport rules, malware, connection filter, tenant allow/block, connectors, message trace, historical search, quarantine, ATP report, audit log, pilot scoping, mailbox-level, Outlook reactions, ORCA)
-
sclizer junk folder investigation added (~800 emails in junk)
ISE Lab → rsyslog → Monad Pipeline
-
6-step execution guide created in d001/projects/siem-qradar-to-sentinel
-
ISE syslog config (GUI steps + logging categories), rsyslog per-source config, Monad API pipeline creation (10 API calls), rsyslog omfwd forwarding, end-to-end validation
RAD Projects
-
New d001 project scaffolded:
d001 open rad-projects
Dashboard & Reporting System
-
scripts/generate-dashboards.sh— 11 auto-generated SVG diagrams (repo map, productivity, Gantt, competency, SIEM, toolchain, infra, d001 landscape/timeline/API/education) -
scripts/domus-report.py— Python COO reporting tool, 14 matplotlib charts (overview, projects, education, growth, files, competencies, analytics) -
Analytics report: scatter + regression (Pearson r=0.077), commit heatmap (peak Thu 15:00), cumulative growth curve, Pareto 80/20 distribution
-
12 domain-specific Graphviz diagrams: 4 network (ASA VPN, ISE deployment, SIEM detailed, home lab rack), 4 project (BMS policy, Abnormal mail flow, Monad internals, TCP clocks), 4 education (CISSP domains, literature tree, C curriculum, CLI mastery)
-
Makefile targets:
make diagrams-dashboard,make report -
Total: 37 visual assets (11 SVG + 14 PNG + 12 domain SVG)
YAML Substrate & Knowledge Graph
-
data/project-master.yaml— 37 CHLA projects, machine-readable twin to project-master.adoc -
Fields: slug, priority, status, owners, depends_on, blocks, tools, d001/docs paths, milestones
-
5 dependency chains for critical path analysis
-
Association engine populated: 107 new triples across 4 YAML files (chla projects, CLI tools, codex knowledge, homelab infrastructure)
-
CLAUDE.md updated with Project Graph section — 9 dependency chains for instant session context
-
Next: YAML→AsciiDoc generator, FastAPI layer, dynamic Gantt from YAML
Build Toolchain Improvements
-
Rouge style changed from monokai.sublime (brown) to thankful_eyes (navy-blue) in build-adoc.sh
-
light-cyan theme: dark terminal code blocks (#0F1E2D), generous padding, rounded corners, caption styling
-
Fixed:
literal/listingblocks sharecode:theme key,[source,text]tagging for output blocks -
Fixed:
cisco-ios→cisco_iosRouge lexer name -
Documented: build-antora-page.sh (Antora) vs build-adoc.sh (d001/standalone)
Commands & Patterns Learned
zsh: setopt NULLglob
-
Suppresses "no matches found" when glob expands to nothing
-
bashequivalent:shopt -s nullglob -
Used for:
ls /mnt/c/Users/erosado/Downloads/*.{png,pdf,jpg,jpeg}
PowerShell: Recursive file discovery with multi-filter
-
Get-ChildItem -Recurse -File | Where-Object { $.Extension -match '…' -and $.Name -match '…' } -
Pipe to
Sort-Object LastWriteTime -Descendingfor most recent first -
Set-Clipboardto grab results for paste
ISE ERS: Self-contained batch operation pattern
-
dsource→ers()function → resolve group UUID → loop with exists-check → POST -
Heredoc for MAC list — no temp files needed
-
Exists-check prevents duplicates:
ers "/endpoint?filter=mac.EQ.${MAC}" | jq '.SearchResult.resources[0].id // empty'
age encryption: d001 workflow discipline
-
d001 closeonly scanspartials/— files inscripts/,output/,certs/,config-snapshots/need one-offencrypt-file -
Must
rmstale.agebeforeencrypt-file— old.agesilently overrides edits -
.mmdfiles need explicit gitignore underdata/
Git hygiene: .graveyard/ pattern
-
Local archive directory, gitignored — for files removed from tracking but preserved on disk
-
git mv→git rm --cached→ file stays in.graveyard/, removed from index
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.
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.