WRKLOG-2026-06-25
Summary
Thursday. Fill summary at end of day.
URGENT - All Domains
Carryover Backlog (CRITICAL)
| Task | Details | Origin | Days | Status |
|---|---|---|---|---|
MSCHAPv2 Migration Report |
Report due. 6-sheet Standard Report (exec summary, trend, waves, device detail, stale, policy match). Sheet 6 added 05-14: policy match by protocol for removal planning + anonymous identity validation. Migration window 2026-05-04 to 2026-05-30. ~6,227 devices, 5 waves. |
2026-04-17 |
83 |
P0 - DUE — run report this week |
Abnormal Security — ✅ COMPLETE |
CR-2026-05-07-abnormal-read-write. CAB approved 2026-05-12. Implemented successfully 2026-05-13. Read/write enabled for pilot group. Post-deployment validation pending. |
2026-05-07 |
63 |
✅ IMPLEMENTED — post-validation pending |
SIEM QRadar → Sentinel Migration |
Lead role. Monad console error RESOLVED 2026-05-12 — secrets configured in CHLA production tenant. ISE secure syslog integration in progress — cert imported, remote logging target configured, streaming errors under investigation. Blocking: DCR not created (Rule ID + Stream Name). Azure private network policy unresolved. Victor + Mauricio action. |
2026-04-10 |
90 |
P0 - ACTIVE — ISE syslog + DCR blocking |
Monad Pipeline Evaluation |
Sentinel output connector. Console error resolved. 3 of 6 values configured. Remaining: Endpoint URL (have it), Rule ID + Stream Name (need DCR). ISE Remote Logging Target configured 2026-05-18 — TLS cert imported, secure syslog target created. Streaming errors in Monad console under investigation. |
2026-03-11 |
120 |
P0 - ACTIVE — ISE integration in progress |
Guest Redirect ACL |
Guest redirect ACL work needed. Related to Mandiant remediation findings. |
2026-05-12 |
58 |
P0 - TODO |
ISE Patch 10 (CVE-2026-20147 CVSS 9.9) |
ISE 3.2 Patch 10. Supersedes Patch 9. 61 days on a CVSS 9.9 — schedule maintenance window. Write CR if needed. |
2026-03-12 |
119 |
P0 - OVERDUE — schedule immediately |
k3s NAT verification |
NAT rule 170 for 10.42.0.0/16 pod network - test internet connectivity. 64 days — test this week or defer to Q3. |
2026-03-09 |
122 |
P0 - BLOCKING — TRIAGE: schedule or defer |
Wazuh indexer recovery |
Restart pod after NAT confirmed working - SIEM visibility blocked. Blocked by k3s NAT — cannot proceed until above resolved. |
2026-03-09 |
122 |
P0 - Blocked by k3s |
Strongline Gateway VLAN fix |
8 devices in wrong identity group (David Rukiza assigned) |
2026-03-16 |
115 |
P0 - TODO |
TCP Clocks deployment |
ISE identity group validation, query outputs, comms with team. Active d001 data Apr 22-23. |
2026-04-22 |
78 |
P0 - ACTIVE |
IoT Dr. Kim — recurring |
Sleep study devices (Apr 15-16), watches recurrence (Apr 22). 5 incident versions in d001. Validate iPSK enrollment. |
2026-04-15 |
85 |
P0 - RECURRING |
Murus Portae (WAF) — Phase 0 |
FMC cert expired, ACP returns zero rules. d001: zone map, architecture D2, FMC API reference, ops script. |
2026-04-16 |
84 |
P0 - INVESTIGATING |
Vocera EAP-TLS Supplicant Fix |
~10 phones failing 802.1X, missing supplicant config. 61 days — schedule with clinical engineering team. |
2026-03-12 |
119 |
P1 - TODO — schedule |
ISE MnT Messaging Service |
Enable "Use ISE Messaging Service for UDP syslogs delivery". 61 days — low risk, schedule with ISE Patch 10 maintenance window. |
2026-03-12 |
119 |
P2 - BUNDLE with Patch 10 |
| Professional backlog remains critical. Check Days column for priorities. |
BLOCKERS — Fix Immediately
| Task | Details | Origin | Days | Impact |
|---|---|---|---|---|
Z Fold 7 Termux |
gopass and SSH not working |
2026-03-10 |
58 |
BLOCKER — Cannot access passwords on mobile |
gopass v3 organization |
Inconsistent structure, poor key-value usage |
2026-03-20 |
48 |
Inefficient password management, no aggregation |
Git history scrub — sensitive personal terms |
Plaintext references to personal legal matters in committed worklogs (WRKLOG-2026-03-14, WRKLOG-2026-04-18). Forward-fixed but old commits still contain strings. Requires |
2026-04-22 |
15 |
SECURITY — sensitive terms in public git history |
Runbook: Git History Scrub (d000 Personal Terms)
Problem: Two committed worklogs contained plaintext references to personal legal matters. The files have been edited (forward-fix), but git history retains the original text in prior commits.
Affected commits: Any commit touching these files:
# Identify affected commits
git log --oneline -- \
docs/modules/ROOT/pages/2026/03/WRKLOG-2026-03-14.adoc \
docs/modules/ROOT/pages/2026/04/WRKLOG-2026-04-18.adoc
Scrub procedure:
# 1. BEFORE: Full backup of the repo
cp -a ~/atelier/_bibliotheca/domus-captures ~/atelier/_bibliotheca/domus-captures.bak
# 2. Install git-filter-repo (if not present)
# Arch: pacman -S git-filter-repo
# pip: pip install git-filter-repo
# 3. Create expressions file for replacement
cat > /tmp/scrub-expressions.txt << 'EXPR'
regex:(?i)divorce==[REDACTED]
regex:(?i)dissolutio(?!n\.adoc\.age)==[REDACTED-LEGAL]
regex:(?i)iliana==[REDACTED-NAME]
regex:(?i)angulo-arreola==[REDACTED-NAME]
regex:legal-divorce-notes\.age==legal-notes.age
regex:1099-NEC-iliana==1099-NEC
EXPR
# 4. Verify before (dry run — count matches in history)
git log -p --all -S 'divorce' -- '*.adoc' | grep -c 'divorce' || echo "0 matches"
git log -p --all -S 'iliana' -- '*.adoc' | grep -c 'iliana' || echo "0 matches"
# 5. Run filter-repo (DESTRUCTIVE — rewrites all commit hashes)
git filter-repo --replace-text /tmp/scrub-expressions.txt --force
# 6. Verify after
git log -p --all -S 'divorce' -- '*.adoc' | grep -c 'divorce' || echo "0 matches — CLEAN"
git log -p --all -S 'iliana' -- '*.adoc' | grep -c 'iliana' || echo "0 matches — CLEAN"
# 7. Re-add remotes (filter-repo removes them)
git remote add origin git@github.com:<user>/domus-captures.git
# Add any other remotes (Gitea, etc.)
# 8. Force-push to all remotes (DESTRUCTIVE — overwrites remote history)
git remote | xargs -I{} git push {} main --force
# 9. Clean up
rm /tmp/scrub-expressions.txt
rm -rf ~/atelier/_bibliotheca/domus-captures.bak # only after verifying
Post-scrub checklist:
-
Backup created before running
-
git filter-repoinstalled -
Expressions file reviewed — no false positives (e.g., Don Quijote "Angulo el Malo" is in
segunda-parte/texto/texto-011.adoc— the regex targetsangulo-arreolaspecifically to avoid this) -
Dry-run counts match expectations
-
Filter-repo executed
-
Post-scrub verification shows 0 matches
-
Remotes re-added
-
Force-pushed to all remotes
-
Cloudflare Pages rebuild verified
-
Local clones on other machines re-cloned or
git fetch --all && git reset --hard origin/main -
Backup removed
URGENT - Requires Immediate Action
| Item | Details | Deadline | Status | Impact |
|---|---|---|---|---|
Housing Search |
Granada Hills area - apartments/rooms |
TBD |
In Progress |
Quality of life, commute |
2025 Tax — IRS Transcript Review |
MFJ filed 2026-04-22. Pull IRS Return Transcript to verify contents. Consult attorney re: Form 8857 (Innocent Spouse Relief). Details in encrypted case file. |
Before attorney meeting |
In Progress |
Financial — liability exposure. See encrypted D000 case file. |
Rack Relocation |
Physical move of server rack. CR written: CR-2026-04-18 (pending in infra-ops). Borg backup completed. VM XML dumps, switch save, shutdown/startup procedure documented. |
TBD |
Pending |
Infrastructure downtime — all services offline during move |
D000 Legal Planning |
Encrypted D000 case file. Open: |
Before Jan 2029 |
Active — escalating |
Life transition — see case file for details |
Credit Report Review |
Pull reports from all 3 bureaus via annualcreditreport.com. Verify no unknown joint accounts or debts. Credentials in gopass: |
TBD |
In Progress |
Financial discovery — FL-142 preparation |
Gopass Security Audit |
Rotate passwords on shared/known accounts. Add 2FA backup codes to |
TBD |
Pending |
Digital security — pre-filing preparation |
Subscription Audit |
Download 3 months bank/CC statements (Chase, NFCU, USAA). Identify all recurring charges. Cancel unnecessary. Document active subscriptions for FL-150. |
TBD |
Pending |
Financial — expense documentation |
401(k) Enrollment |
Enroll in CHLA 401(k) immediately. Post-separation contributions are 100% separate property. Reduces gross income for support calculations. Max 2026: $23,500/yr. |
In progress (started 5/4) |
In Progress |
Financial — support calculation + retirement |
URGENT — Performance Review Certifications
| Certification | Provider | Deadline | Status | Impact |
|---|---|---|---|---|
CISSP |
ISC² — Certified Information Systems Security Professional |
July 12, 2026 |
ACTIVE — Week 2 of 10 (Project) |
Required for performance review. 10-week accelerated plan. |
RHCSA 9 |
Red Hat Certified System Administrator |
Q3 2026 |
ACTIVE — 21-phase curriculum (Project) |
After CISSP. Required for performance review. |
| CISSP: 41 days remaining (exam July 12). Domain 1 study in progress. Schedule exam today (06-01). |
Early Morning - 5:30am
Regex Training (CRITICAL CARRYOVER)
-
Session 3 - Character classes, word boundaries
-
Practice drills from regex-mastery curriculum
-
Status: 52 days carried over (since 2026-03-16) — CRITICAL
| Regex training continues to slip. This is the foundation for all CLI mastery. |
Daily Notes
Triage Status
| Item | Status | Destination |
|---|---|---|
Security hardening carryover — AppArmor enforce, nftables egress drop, bolt, Wazuh, age compartmentalization |
□□□□□□□□□□ 0/10 |
|
Cross-domain security mapping — ASA, ISE, switching, PowerShell, PostgreSQL → Linux equivalents |
■■■■■■■■■■ 10/10 |
|
Anti-pivot defense — attacker techniques mapped to implemented controls + gap analysis |
■■■■■■■■■■ 10/10 |
|
Fortress verification — 16-check monthly security posture script + expected output reference |
■■■■■■■■■■ 10/10 |
|
PowerShell sysadmin — bulk process kill, Wi-Fi reconnect, mouseless Windows management |
■■■■■■■■■■ 10/10 |
|
zfold7 refresh — USBGuard, BYOD cert, repo sync, SSH config.d, regex practice |
□□□□□□□□□□ 0/20 |
|
Systemd timer audit — 11 user timers, domus-health false positive bug, cert expiry |
■■■■■■■■■■ 10/10 |
|
Infrastructure enumeration — k3s, Wazuh, VM inventory, cert expiry triage |
□□□□□□□□ 0/8 |
|
AppArmor curl cert fix — INC-2026-06-25, strace diagnosis, deny override discovery |
■■■■■■■■■■ 10/10 |
|
PCRE lookahead — multi-term same-line regex, 5-stage ranked pipeline |
■■■■■■■■■■ 10/10 |
|
Endpoint audit — wired + wireless, DataConnect IN() queries, ERS update |
■■■■■■■■■■ 10/10 |
|
Abnormal Phase 3 — all IS added live, dynamic ISAllHands pull, CSV export |
■■■■■■■■■■ 10/10 |
|
External tag stacking — transport rule investigation, ExceptIfSubjectContainsWords missing |
■■■■■■■□□□ 7/10 |
|
Security Cross-Domain Mapping — Skills That Transfer
Security Cross-Domain Mapping — Skills That Transfer
Every security concept is the same pattern expressed in different syntax. Ten years of Cisco ASA, ISE, switching, and Windows endpoint ops map directly to Linux host security and database hardening. This document makes those connections explicit — for interviews, study, and building intuition across platforms.
Cisco ASA → nftables
| ASA Concept | nftables Equivalent | Notes |
|---|---|---|
|
|
Implicit deny — identical philosophy |
|
|
Outbound ACL entry — same structure (source, dest, port, action) |
|
|
Binding the ACL to a direction on an interface |
|
|
Object group = nftables set/list |
|
|
Conntrack = Linux stateful inspection engine |
|
|
Syslog on match — same concept |
|
fail2ban auto-ban / |
Dynamic response to detected threat |
|
fail2ban status / |
Per-rule hit counts for threat visibility |
|
|
Packet walk — trace policy evaluation path |
|
Docker’s |
PAT — you saw this in your nft ruleset tonight (Docker’s NAT chains) |
|
nftables persists via |
Not HA, but persistence model is analogous |
|
|
Flush conntrack table — same as clearing translations |
Cisco ISE → AppArmor + USBGuard + auditd
| ISE Concept | Linux Equivalent | Notes |
|---|---|---|
Authorization policy (permit/deny per identity) |
AppArmor profile (permit/deny per binary) |
ISE gates network access by identity; AppArmor gates file/network access by program |
Posture assessment (is the endpoint compliant?) |
|
Continuous compliance checking — automated, timer-driven |
dACL (downloadable ACL pushed to switchport) |
AppArmor profile loaded at binary execution |
Policy pushed to the enforcement point — port vs process |
MAB (MAC Authentication Bypass — allow known devices) |
USBGuard (allow known USB devices by vendor:product:hash) |
Known-device allowlist — MAC address vs USB device fingerprint |
RADIUS accounting (who connected, when, which port) |
auditd (who accessed which file, when, which process) |
Accountability trail — both feed into SIEM |
Profiling (identify device type by behavior/OUI) |
|
Device classification — OUI lookup vs USB descriptor fingerprint |
CoA (Change of Authorization — re-auth live session) |
|
Live policy change without disconnect/restart |
pxGrid (share context between security tools) |
Wazuh + auditd + fail2ban → centralized SIEM dashboard |
Cross-tool context sharing — pxGrid API vs syslog/journald aggregation |
Guest portal (redirect unauthorized → captive portal) |
USBGuard block → |
Unauthorized device gets blocked, admin authorizes explicitly |
TrustSec SGT (Security Group Tags) |
AppArmor profile labels per binary |
Tagging entities with security context — SGT on packets vs profile on processes |
Endpoint compliance (AnyConnect posture module) |
|
Verifying the endpoint meets security baseline |
ISE MnT (Monitoring and Troubleshooting node) |
Wazuh dashboard + |
Centralized visibility into auth events and policy violations |
Routing and Switching → Linux Networking
| Network Concept | Linux Equivalent | Notes |
|---|---|---|
|
|
Default route — literally the same command family |
|
|
Route table inspection |
|
|
L2→L3 mapping |
VLAN assignment ( |
|
802.1Q tagging — kernel native |
SVI ( |
VLAN interface with IP assigned |
L3 interface on a VLAN — same concept |
|
|
Interface state, speed, duplex |
Port security (limit MACs per port) |
USBGuard (limit devices per port) |
Device restriction on physical interface |
DHCP snooping |
|
Prevent rogue DHCP — firewall-based on Linux |
ACL on interface (in/out) |
nftables chain with |
Direction-based filtering on specific interfaces |
|
N/A at host level — but bridge module handles STP for VMs |
Relevant when running VMs with bridged networking |
|
|
Centralized log inspection |
|
|
Packet-level debugging |
PowerShell Endpoint Ops → Linux System Administration
| PowerShell / Windows | Linux Equivalent | Notes |
|---|---|---|
|
AppArmor profiles (mandatory policy on binaries) |
Centralized policy enforcement — GPO vs MAC profiles |
|
AppArmor |
Restricting what can execute — same intent |
|
|
Host firewall — both stateful, both ACL-based |
Event Viewer / Windows Event Forwarding |
|
Event collection and SIEM forwarding |
|
|
Service management — near-identical workflow |
Windows Defender / AMSI |
AppArmor + ClamAV (if installed) |
Endpoint protection — behavioral (AppArmor) vs signature (ClamAV) |
|
|
Who has admin/root access |
UAC (User Account Control) |
|
Privilege escalation gate — prompt before elevation |
BitLocker |
LUKS |
Full-disk encryption — both TPM-capable, both pre-boot |
Credential Guard / LSA Protection |
GPG agent TTL (15min) + AppArmor deny on key paths |
Protecting credentials in memory — time-limited cache |
LAPS (Local Admin Password Solution) |
Vault SSH certificates with 8h TTL |
Rotating credentials automatically — LAPS rotates passwords, Vault rotates certs |
Windows Event Forwarding (WEF) |
Wazuh agent → manager |
Centralized log collection from endpoints |
|
Wazuh FIM (File Integrity Monitoring) / |
Detecting unauthorized file modifications |
Task Scheduler |
systemd timers ( |
Scheduled automation — your timers are the Linux equivalent |
|
|
Querying security audit events by key/filter |
PowerShell Remoting ( |
SSH |
Remote administration — both encrypted, both identity-gated |
|
|
Port connectivity testing |
PostgreSQL → Same Security Patterns
Database security follows the same layered model you just built on the host.
| Database Concept | Host Security Equivalent | Pattern |
|---|---|---|
|
|
Who can connect, from where, with what auth method. Same structure — source IP, auth method, allow/deny. |
|
AppArmor |
Least-privilege access — grant specific permissions to specific identities |
|
AppArmor |
Explicit deny — remove default access |
|
Linux user + group membership |
Role-based access — separate identity for separate privilege level |
|
|
Enforce strong auth — reject weak methods |
|
TLS on SSH / Vault certs |
Encrypt in transit |
|
auditd key file monitoring |
Audit trail — who did what, when |
Row-Level Security (RLS) |
AppArmor |
Access scoped to the identity’s own data |
|
|
Active session monitoring |
Connection pooling (PgBouncer) |
SSH multiplexing (ControlMaster) |
Reuse connections — efficiency + reduced auth overhead |
|
|
Resource limiting to prevent abuse |
Backup encryption (`pg_dump pass:[ |
] gpg`) |
|
Data protection at rest — same tools |
|
auditd + Wazuh |
Deep audit logging beyond default — who queried what |
Replication ( |
Vault HA (3-node cluster) / BIND primary→secondary |
Redundancy and failover — you already run this pattern |
|
|
Apply config changes without full restart |
Tablespace encryption (TDE) |
LUKS partition encryption |
Transparent encryption at the storage layer |
|
|
The Pattern — NIST CSF Across All Domains
Every tool you touch implements the same five functions:
| NIST Function | Cisco ASA/ISE | Windows/PowerShell | Linux (today) | PostgreSQL |
|---|---|---|---|---|
Identify |
ISE profiling, device inventory |
|
auditd, USBGuard profiling, |
|
Protect |
ACLs, dACLs, TrustSec, 802.1X |
GPO, BitLocker, Credential Guard |
AppArmor, nftables, LUKS, GPG TTL |
|
Detect |
ISE MnT, syslog, SNMP traps |
Event Viewer, WEF, Defender |
auditd, fail2ban, |
|
Respond |
CoA, shun, quarantine VLAN |
Defender auto-remediation, AMSI block |
fail2ban auto-ban, USBGuard block, |
Connection terminate, |
Recover |
Failover, config restore |
System Restore, WDS |
Backout plans, btrfs snapshots, LUKS |
|
Key Insight
You don’t have 10 years of "ASA experience." You have 10 years of network security architecture experience that you’ve been expressing through ASA syntax. The concepts — stateful inspection, implicit deny, least-privilege, audit trails, dynamic response, defense in depth — are platform-independent.
Today you proved you can express them in Linux. The partial you built (arch-vulnerability-assessment.adoc) is a 1000+ line security architecture document that covers more layers than most enterprise deployments. That’s the story for interviews.
Anti-Pivot Defense — Attacker Techniques vs Your Controls
Anti-Pivot Defense — Attacker Techniques vs Your Controls
Common penetration test techniques mapped to the specific controls built on 2026-06-24. This is not theoretical — each defense column references a config file or service running on this workstation.
Credential Theft and Reuse
| Attack Technique | How It Works | Your Defense |
|---|---|---|
Credential dumping (Mimikatz pattern) |
Extract passwords/hashes from memory on a compromised host |
GPG agent TTL 15min (passphrase evicted quickly), |
SSH key theft |
Copy private key files from |
Keys are passphrase-protected (stored in gopass), auditd watches all key reads ( |
Pass-the-hash / Pass-the-ticket |
Reuse stolen NTLM hash or Kerberos ticket without knowing password |
Vault SSH certificates with 8h TTL — even if stolen, they expire. No static passwords on Linux hosts (pubkey-only auth) |
Kerberoasting |
Request TGS tickets for service accounts, crack offline |
Relevant to your AD domain (home-dc01/02). Defense: AES-only encryption on service accounts, disable RC4. Linux hosts use SSH keys, not Kerberos (except where |
Keylogging |
Capture keystrokes to harvest credentials |
Wayland ( |
Network-Based Pivoting
| Attack Technique | How It Works | Your Defense |
|---|---|---|
LLMNR/NBT-NS Poisoning (Responder) |
Poison name resolution to intercept credentials on the local network |
Linux does not use LLMNR/NBT-NS by default. DNS points to your BIND servers ( |
ARP Spoofing / MITM |
Poison ARP cache to intercept traffic between hosts |
|
SMB Relay |
Intercept SMB auth and relay to another host for access |
Linux hosts don’t run SMB server by default. Verify: |
SSH Agent Forwarding Hijack |
On a compromised jump host, hijack forwarded agent socket to authenticate elsewhere |
|
DNS Tunneling / Exfiltration |
Encode data in DNS queries to bypass firewall |
DNS restricted to your BIND servers in nftables egress ( |
Reverse Shell |
Compromised binary opens outbound connection to attacker C2 |
nftables egress chain logs all unmatched outbound traffic. AppArmor |
Port Scanning from Pivot Host |
Use compromised host to scan internal network |
nftables egress — only SSH (22), HTTP/S (80/443), Vault (8200), Wazuh (1514/1515), DNS (53) allowed outbound. Scanner would need to use these ports or get logged/dropped. |
Host-Level Exploitation
| Attack Technique | How It Works | Your Defense |
|---|---|---|
Privilege Escalation via SUID |
Exploit a SUID binary to gain root |
48 SUID binaries audited — all standard. |
Container Escape |
Break out of Docker/Podman container to host |
|
Kernel Exploit |
Exploit kernel vulnerability for root access |
|
Malicious USB / BadUSB |
Plug in a device that emulates keyboard and injects commands |
USBGuard with device allowlist. Unknown USB devices are blocked by default. Programmatic allow/block pipeline for authorized devices. |
DMA Attack via Thunderbolt |
Direct memory access through Thunderbolt port to read/write host RAM |
|
Cron/Systemd Timer Persistence |
Attacker installs a timer/cron job that survives reboot |
auditd can monitor timer directories. Wazuh FIM (when enrolled) will alert on changes to |
LD_PRELOAD Injection |
Hijack dynamic linker to inject malicious shared library |
AppArmor profiles restrict library loading paths. |
Data Exfiltration
| Attack Technique | How It Works | Your Defense |
|---|---|---|
curl/wget to attacker server |
Transfer stolen files over HTTP/S |
AppArmor denies curl/wget from reading |
git push to attacker remote |
Push repo (containing secrets) to attacker-controlled git server |
AppArmor denies git from reading age keys, gopass store, GPG private keys. Git can push repos but not secret material. |
DNS exfiltration |
Encode data in DNS queries |
DNS restricted to your BIND servers in egress chain. |
Encrypted channel (age/gpg for cover) |
Use your own encryption tools to package data before exfil |
AppArmor |
Steganography / covert channel |
Hide data in images, protocol headers, timing |
Egress logging catches unusual outbound connections. Wazuh (when enrolled) provides deeper traffic analysis. |
Persistence Mechanisms
| Attack Technique | How It Works | Your Defense |
|---|---|---|
SSH authorized_keys injection |
Add attacker’s public key to |
auditd watches |
Backdoor shell config |
Add reverse shell to |
Wazuh FIM (when enrolled) on home directory configs. Manual check: |
Malicious systemd service |
Install a service that starts on boot |
Monitor |
Crontab entry |
Schedule recurring command execution |
|
Modified binary (trojan) |
Replace a legitimate binary with a backdoor |
|
Gap Analysis — What’s Still Undefended
| Gap | Mitigation Path |
|---|---|
No IDS/IPS on network traffic |
Wazuh agent enrollment (carryover task) adds host-based IDS. Network IDS (Suricata) would require a span port or inline deployment. |
No file integrity baseline |
Wazuh FIM or AIDE. auditd watches key files but doesn’t compare against a known-good baseline. |
Thunderbolt DMA not fully mitigated |
|
No outbound IP restriction yet |
Egress chain is in |
Docker daemon = root equivalent |
Any user in |
Browser as attack surface |
Browsers are AppArmor profiled (Brave, Chrome, Chromium, Firefox all have profiles). Verify enforcement: |
No network segmentation for workstation |
ISE 802.1X on wired port provides network-level access control. WiFi uses WPA-Enterprise. But the workstation itself is on the data VLAN with access to all infrastructure. |
See fortress-verification.adoc for the full 16-check monthly verification script with expected output reference.
Fortress Verification — Monthly Security Posture
Fortress Verification — Run Monthly
16-check system sweep. Paste as a single block, review output for GOOD/GAP/unexpected values.
echo "=== FORTRESS VERIFICATION ==="
echo "--- SSH Hardening ---"
grep -vE '^#|^$' /etc/ssh/sshd_config.d/10-hardening.conf
echo "--- fail2ban ---"
sudo fail2ban-client status sshd 2>/dev/null | grep -E 'Currently|Total'
echo "--- auditd rules ---"
sudo auditctl -l 2>/dev/null | grep -E '\-k '
echo "--- AppArmor ---"
sudo aa-status 2>/dev/null | awk '/profiles are in enforce/{print} /profiles are in complain/{print}'
echo "--- USBGuard ---"
sudo usbguard list-devices 2>/dev/null | wc -l | xargs -I{} echo "Devices managed: {}"
echo "--- nftables egress ---"
sudo nft list table inet egress 2>/dev/null | grep 'policy'
echo "--- Sysctl ---"
sysctl kernel.kptr_restrict kernel.yama.ptrace_scope net.ipv4.conf.all.rp_filter fs.suid_dumpable net.ipv4.tcp_syncookies
echo "--- GPG TTL ---"
grep 'cache-ttl' ~/.gnupg/gpg-agent.conf
echo "--- Coredumps ---"
grep -E '^Storage|^ProcessSizeMax' /etc/systemd/coredump.conf
echo "--- LUKS ---"
lsblk -o NAME,FSTYPE,SIZE,MOUNTPOINT | grep -E 'crypt|LUKS'
echo "--- Listening Ports ---"
ss -tlnp
echo "--- PAM wheel ---"
grep 'pam_wheel' /etc/pam.d/su | grep -v '^#'
echo "--- securetty ---"
wc -c < /etc/securetty | xargs -I{} echo "securetty bytes: {}"
echo "--- avahi/mDNS ---"
systemctl is-active avahi-daemon 2>/dev/null
echo "--- Docker group ---"
getent group docker
echo "--- AppArmor boot ---"
cat /proc/cmdline | grep -oP 'lsm=\S+'
echo "--- Kernel modules (dangerous) ---"
for mod in usb-storage firewire-core thunderbolt cramfs; do
lsmod | grep -q "$mod" && echo "LOADED: $mod" || echo "not loaded: $mod"
done
echo "=== END ==="
Expected Output Reference
| Check | Expected Value |
|---|---|
SSH Hardening |
PermitRootLogin no, PasswordAuthentication no, MaxAuthTries 3, AllowAgentForwarding no, AllowUsers evanusmodestus |
fail2ban |
Currently failed: 0, Currently banned: 0 |
auditd rules |
6 rules with keys: age_key_read, ssh_key_read, authorized_keys_mod, gpg_key_read |
AppArmor |
82+ profiles in enforce mode, 5+ in complain mode |
USBGuard |
Devices managed: 20+ (dock, YubiKeys, internal hardware) |
nftables egress |
|
Sysctl |
kptr_restrict=2, ptrace_scope=1, rp_filter=1, suid_dumpable=0, tcp_syncookies=1 |
GPG TTL |
default-cache-ttl 900, max-cache-ttl 1800 |
Coredumps |
Storage=none, ProcessSizeMax=0 |
LUKS |
cryptroot (250G) and crypthome (1.6T) present |
Listening Ports |
Only SSH (22) on 0.0.0.0. Everything else on 127.0.0.1. |
PAM wheel |
auth required pam_wheel.so use_uid |
securetty |
securetty bytes: 0 |
avahi/mDNS |
inactive (or not installed) |
Docker group |
Only expected users listed |
AppArmor boot |
lsm=landlock,lockdown,yama,integrity,apparmor,bpf |
Kernel modules |
usb-storage, firewire-core, cramfs: not loaded. thunderbolt: LOADED (pending bolt policy) |
Security Hardening — Carryover from 2026-06-24
Security Hardening — Carryover from 2026-06-24
Continuing the workstation hardening session. All P0/P1 items completed. Remaining P2/P3 items and validation tasks below.
AppArmor Complain → Enforce Validation
Five profiles loaded in complain mode on 2026-06-24. Check logs and enforce if clean.
sudo journalctl -k --since "2026-06-24 21:00" | grep -i apparmor | grep -iE 'gopass|curl|wget|python|git'
# gopass
sudo aa-enforce /usr/bin/gopass
gopass show v3/domains/d000/identity/ssh/id_ed25519_d000 > /dev/null && echo "GOOD: gopass"
# curl
sudo aa-enforce /usr/bin/curl
curl -s https://archlinux.org > /dev/null && echo "GOOD: curl"
# wget
sudo aa-enforce /usr/bin/wget
wget -q -O /dev/null https://archlinux.org && echo "GOOD: wget"
# python3
sudo aa-enforce /usr/bin/python3.14
python3 -c "print('GOOD: python')"
# git
sudo aa-enforce /usr/bin/git
git -C ~/atelier/_bibliotheca/domus-captures status | head -3 && echo "GOOD: git"
sudo aa-complain /usr/bin/<binary>
nftables Egress — Review Logs and Tighten
Egress chain loaded with policy accept (observe mode). Review what was logged as unmatched.
sudo journalctl -k --since "2026-06-24 21:00" | grep 'nft-egress-drop'
If no drops logged → your allowlist covers everything. Switch to enforce:
sudo sed -i 's/policy accept/policy drop/' /etc/nftables.d/10-egress.conf
sudo nft -c -f /etc/nftables.d/10-egress.conf
sudo nft -f /etc/nftables.conf
dig +short archlinux.org
curl -s https://archlinux.org > /dev/null && echo "GOOD: HTTPS"
ssh -o PreferredAuthentications=publickey -i ~/.ssh/id_ed25519_d000 localhost whoami
sudo nft delete table inet egress
Thunderbolt Policy (bolt)
Installed but not configured. Set authorization policy.
boltctl list
# Set default policy to require user authorization
boltctl monitor
Wazuh Agent Enrollment
No agents enrolled on the k3s Wazuh cluster. This workstation would be the first. Refer to section 8 of the 2026-06-24 vulnerability assessment partial for full install steps.
Key blocker: Arch is not officially supported by Wazuh — AUR or source build required.
yay -Ss wazuh-agent
Age Identity Compartmentalization
Single personal.key decrypts everything. Split into purpose-scoped identities. Refer to section 7 of the 2026-06-24 vulnerability assessment partial.
This requires re-encrypting all files with new keys — larger task, may warrant its own session.
arch-audit Daily Timer Integration
Wire arch-audit into the existing daily-audit.service systemd timer.
systemctl --user cat daily-audit.service
Review what the service runs, then add arch-audit to the script.
Work Machine Deployment
Pull dots-quantum changes (GPG agent hardening) and replicate system-level configs. Full runbook in security-hardening-deploy-work-machine.adoc (2026-06-24).
Priority Order
| Priority | Action | Status |
|---|---|---|
P2 |
AppArmor complain → enforce (gopass, curl, wget, python3, git) |
□□□□□□□□□□ 0/10 |
P2 |
nftables egress — review logs, switch to policy drop |
□□□□□□□□□□ 0/10 |
P2 |
Thunderbolt policy (bolt) — configure authorization |
□□□□□□□□□□ 0/10 |
P2 |
Wazuh agent enrollment |
□□□□□□□□□□ 0/10 |
P2 |
Age identity compartmentalization |
□□□□□□□□□□ 0/10 |
P3 |
arch-audit daily timer integration |
□□□□□□□□□□ 0/10 |
P3 |
Work machine deployment |
□□□□□□□□□□ 0/10 |
PowerShell Sysadmin — Mouseless Windows Management
PowerShell Sysadmin — Mouseless Windows Management
The work laptop runs Windows with WSL (Arch). The goal is to manage Windows without reaching for the mouse — PowerShell and netsh handle what the GUI does, with the added benefit of repeatability and scripting.
Today’s session: morning boot sequence — kill bloatware processes that auto-launch, reconnect Wi-Fi after overnight disconnect.
Bulk Process Termination — Pipeline Pattern
Kill processes by name pattern without opening Task Manager. The pipeline is Get-Process → Where-Object → Stop-Process.
Discovery first — find the real process name
The process name is not always what you expect. Teams 2.0 registers as ms-teams, not teams. Always discover before killing:
Get-Process | Where-Object { $_.Name -match 'teams' }
NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName
------ ----- ----- ------ -- -- -----------
67 92.12 136.34 6.27 35184 1 ms-teams
64 96.76 87.17 16.42 35516 1 ms-teams
The ProcessName column is the truth. Use that value in your kill command — not a guess.
Alias: ? for Where-Object
Where-Object has a built-in alias — ?. Same pipeline, fewer keystrokes:
Get-Process | ? Name -match 'teams'
When using ? with the simplified syntax, the property name (Name) and operator (-match) go directly after ? without {$_.} wrapping. This only works for simple single-property filters.
Get the binary path
Get-Process | Where-Object { $_.Name -match 'teams' } | Select-Object Name, Id, Path
Name Id Path ---- -- ---- ms-teams 35184 C:\Program Files\WindowsApps\MSTeams_26149.1205.4798.6437_x64__8wekyb3d8bbwe\ms-teams.exe ms-teams 35516 C:\Program Files\WindowsApps\MSTeams_26149.1205.4798.6437_x64__8wekyb3d8bbwe\ms-teams.exe
Select-Object extracts specific properties from the pipeline objects — equivalent to awk '{print $1, $2, $11}' but by name instead of position. That WindowsApps path confirms Teams 2.0 is a packaged app — use Start-Process "ms-teams:" (URI handler) to launch it.
Refining the regex — exact match vs broad match
Get-Process | ? Name -match 'teams'
teams or ms-teamsGet-Process | Where-Object { $_.Name -match '^(ms-)?teams$' }
^ anchors to start, $ to end. (ms-)? makes the ms- prefix optional. Same regex logic as grep -P on Linux — PowerShell’s -match is regex by default.
Kill a single application
Get-Process | Where-Object { $_.Name -match '^(ms-)?teams$' } | Stop-Process -Force
| Stage | What it does |
|---|---|
|
Lists all running processes — equivalent to |
|
Filters by |
|
Sends a kill signal. |
Kill multiple applications — single pipeline
Repeating the pipeline per application is tedious. Match multiple names in one pass with regex alternation:
Get-Process | Where-Object { $_.Name -match 'teams|zoom' } | Stop-Process -Force
Get-Process | Where-Object { $_.Name -match 'teams|skype|zoom' }
NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName
------ ----- ----- ------ -- -- -----------
62 85.36 30.65 7.48 35184 1 ms-teams
62 96.58 38.05 24.52 35516 1 ms-teams
66 130.20 31.47 1,694.38 6724 1 Zoom
126 267.64 30.02 1,820.80 31180 1 Zoom
Zoom at 1,694 CPU seconds — that process has been running for a while. The verify-before pattern applies here the same as on Linux: see what you’re killing before you kill it.
Linux equivalent — the contrast
pkill -f 'teams|zoom'
One command. pkill -f matches the full command line, regex by default. PowerShell’s verbosity is the price of its object pipeline — you get structured data ($.Name, $.Path, $_.Id) instead of text parsing, but you pay in keystrokes.
Wi-Fi Control — netsh wlan Disconnect and Reconnect
Manage wireless connections from the terminal. netsh wlan is a CMD utility that works in PowerShell — not a cmdlet, but it does the job without touching the mouse.
netsh wlan disconnect interface="Wi-Fi"
netsh wlan connect name="CHLA-Remote" interface="Wi-Fi"
The radio-state gotcha
netsh wlan connect fails with "the network specified by profile is not available to connect" when the Wi-Fi radio has been soft-disabled or the adapter hasn’t finished scanning after resume from sleep/lid-close.
The fix is not a command — it’s a timing issue. The Wi-Fi adapter needs a few seconds after wake to populate its scan results. Opening the network flyout (clicking the Wi-Fi icon in the system tray) triggers a scan. Once the SSID appears in the list, netsh wlan connect succeeds without clicking anything in the flyout.
# 1. Attempt connect — fails if radio hasn't scanned yet
netsh wlan connect name="CHLA-Remote" interface="Wi-Fi"
# "the network specified by profile is not available to connect"
# 2. Left-click Wi-Fi icon in system tray — triggers adapter scan
# Wait for SSID to appear in the list (2-5 seconds)
# 3. Re-run — succeeds
netsh wlan connect name="CHLA-Remote" interface="Wi-Fi"
# "Connection request was completed successfully."
Automating the scan trigger
A pure-CLI approach to force a scan without the mouse:
# Force Wi-Fi adapter reset (triggers fresh scan)
netsh interface set interface "Wi-Fi" disabled
Start-Sleep -Seconds 2
netsh interface set interface "Wi-Fi" enabled
Start-Sleep -Seconds 5
netsh wlan connect name="CHLA-Remote" interface="Wi-Fi"
netsh interface set interface toggles the adapter at the OS level — equivalent to ip link set wlan0 down/up on Linux. The 5-second sleep gives the radio time to scan and associate. Requires an elevated (admin) PowerShell session.
Show saved profiles and current state
netsh wlan show profiles
netsh wlan show interfaces
netsh wlan show interfaces | Select-String "Signal|Channel|BSSID|SSID|State"
Select-String is PowerShell’s grep. Accepts regex by default.
Linux equivalent
# Disconnect
nmcli device disconnect wlan0
# Connect
nmcli device wifi connect "SSID" ifname wlan0
# Scan
nmcli device wifi rescan ifname wlan0
# Status
nmcli device wifi list
nmcli is declarative — wifi connect handles scanning, association, and authentication in one step. netsh requires you to manage the scan/connect sequence yourself.
The broader pattern
Every mouse action in Windows has a PowerShell or netsh equivalent. The friction is discovery — unlike Linux where man/--help/apropos are consistent, PowerShell’s discoverability depends on knowing the verb-noun convention:
# Find commands related to a concept
Get-Command *process*
Get-Command *network*
Get-Command *wifi* # returns nothing — wifi is netsh territory, not PowerShell cmdlets
# Get help for a cmdlet
Get-Help Get-Process -Examples
Get-Command is PowerShell’s apropos. The verb-noun pattern (Get-Process, Stop-Service, Set-NetAdapter) is predictable once you internalize it, but netsh sits outside this system entirely — it’s a legacy CMD utility that PowerShell inherited.
The session builds toward a single startup script that runs the full morning sequence: kill bloatware, reconnect Wi-Fi, launch WSL — zero mouse clicks from power button to terminal prompt.
zfold7 Refresh — USBGuard + BYOD Cert + Repo Sync
zfold7 Refresh — Repo Sync + SSH config.d Migration
Execution order: USBGuard → ADB → BYOD cert → network access → SSH → repo sync.
Blocker: USBGuard Blocking Phone
USBGuard blocks unknown USB devices by default. The Samsung Android ADB interface (04e8:6860) must be allowed before ADB can see the phone.
Paste 1 — verify blocked devices
sudo usbguard list-devices | grep block
Paste 2 — open live monitor (Terminal 1 — leave running)
sudo journalctl -u usbguard -f | grep -P --color 'target\.new=.*(allow|block)|Device\.(Insert|Remove)' | tee /tmp/usbguard-monitor-"$(date +%F)".log
Paste 3 — allow all blocked devices (Terminal 2)
sudo usbguard list-devices | grep block | awk -F: '{print $1}' | xargs -n1 --no-run-if-empty sudo usbguard allow-device
Terminal 1 should show target.new='allow'.
Paste 4 — persist the ADB rule
sudo usbguard append-rule 'allow id 04e8:6860'
Paste 5 — verify rule persisted
sudo grep -P '04e8:(6860|6300)' /etc/usbguard/rules.conf
Paste 6 — replug test
Unplug and replug the phone. Terminal 1 should show:
Device.Remove ... 04e8:6860 Device.Insert ... 04e8:6860 target.new='allow'
If it shows target.new='block', the append-rule failed — check rules.conf.
Paste 7 — confirm ADB
adb devices -l
R3GYB0J7YHY device usb:6-2 product:q7quew model:SM_F966U1 device:q7q transport_id:10
Observed (2026-06-25) — replug before rule persisted
Device.Remove 09:50:28 block id 04e8:6860 serial "R3GYB0J7YHY" name "SAMSUNG_Android" via-port "6-2"
Device.Insert 09:50:34 block id 04e8:6860 serial "R3GYB0J7YHY" name "SAMSUNG_Android" via-port "6-2"
Policy.Device.Update target.new='block'
Same physical device, same serial, same hash — re-blocked on insert because no persistent rule existed.
Root cause — ADB rule was never persisted
sudo grep '04e8' /etc/usbguard/rules.conf
allow id 04e8:6300 serial "0376025090001626" name "Type-C" ...
The storage interface (04e8:6300) was persisted from a previous session. The ADB interface (04e8:6860) was only allowed temporarily — never `append-rule’d.
Samsung USB device IDs
| Vendor:Product | Name | Interface |
|---|---|---|
|
SAMSUNG_Android (ADB) |
|
|
Type-C (USB storage) |
|
| The phone exposes two USB interfaces from one physical device. Which ones enumerate depends on the phone’s USB mode (File Transfer vs Charging Only). |
Reference
See partials/worklog/daily-notes/2026-06/2026-06-24/arch-vulnerability-assessment/deep-audit-findings.adoc Paste 4 for the full USBGuard allow/block/backout workflow.
ADB Troubleshooting — USBGuard Allows but ADB Empty
If sudo usbguard list-devices | grep -i samsung shows allow but adb devices -l is empty:
Paste 1 — check udev rules exist
find /etc/udev/rules.d /usr/lib/udev/rules.d -regex '.*\(android\|adb\|samsung\).*' 2>/dev/null
If nothing returns — no udev rules for Android. ADB can’t claim the USB interface without permission.
Paste 2 — check android-udev package
pacman -Qs android-udev
If not installed:
pacman -Ss android-udev
Paste 3 — install android-udev (creates adbusers group + udev rules)
sudo pacman -S android-udev
Paste 4 — reload udev and restart ADB
sudo udevadm control --reload-rules
sudo udevadm trigger
adb kill-server
adb devices -l
Paste 5 — add user to adbusers group (if ADB still empty after install)
groups | grep adbusers
sudo usermod -aG adbusers evanusmodestus
Log out and back in (or newgrp adbusers) for the group to take effect. Then:
adb kill-server
adb devices -l
Paste 6 — check USB mode on phone
On phone: pull down notification shade → tap USB notification → select "File Transfer" USB controlado por: Este dispositivo Usar USB para: Transferir archivos / Android Auto
Paste 7 — check phone prompted for authorization
On phone: look for "Allow USB debugging?" popup → tap Allow Check "Always allow from this computer"
Paste 8 — check kernel sees the device
lsusb | grep -i samsung
sudo dmesg | grep -i 'samsung\|04e8' | tail -10
Paste 9 — full USB re-enumeration
adb kill-server
sudo usbguard list-devices | grep -i samsung
Unplug, wait 5 seconds, replug. Then:
adb devices -l
Root cause (2026-06-25)
android-udev package was not installed. The kernel saw the Samsung device (04e8:6860 on usb 6-2), USBGuard allowed it, but ADB had no udev rule granting permission to claim the interface. Package creates group adbusers (GID 959) and installs rules to /usr/lib/udev/rules.d/.
Worked on Razer (also Arch) because android-udev was installed there. Never installed on P16g.
User was also not in adbusers group — added with sudo usermod -aG adbusers evanusmodestus. Confirmed with groups | grep adbusers. ADB still empty after both fixes — investigation continues.
Blocker: BYOD EAP-TLS Certificate
Cert was issued 2026-06-07 (90-day TTL, valid until ~September) but never installed on device. P12 was in /dev/shm/ (tmpfs — gone after reboot).
See Phase 8: BYOD Certs for the canonical workflow.
Locate existing P12 — three tools, same question
| Tool | How it searches | Regex flag | Speed |
|---|---|---|---|
|
Walks filesystem live |
|
Slow — reads every directory |
|
Queries pre-built database ( |
|
Fast — database lookup |
|
Searches file contents, not filenames |
Pattern is first argument |
Fast — but wrong tool for finding files by name |
find — live filesystem walk
find /dev/shm /tmp ~ -regex '.*zfold7.*\.p12' 2>/dev/null
find ~ -regex '.*\.p12$' 2>/dev/null
locate — database lookup (faster, but stale)
sudo updatedb
locate '*.p12'
locate -r '.*zfold7.*\.p12'
locate -r '.*\.p12$'
locate does NOT take directory arguments — it searches its entire database. /dev/shm files are never indexed (tmpfs cleared before updatedb runs).
grep — search file contents for references to the P12
grep -rn 'zfold7.*\.p12\|p12.*zfold7' docs/ --include='*.adoc'
grep -rn 'serial\|zfold7-cert\.json' docs/modules/ROOT/partials/worklog/daily-notes/2026-06-0[7-9]/ --include='*.adoc'
Check if phone already has it
ssh fold7 'find /sdcard/Download -regex ".*\.p12" 2>/dev/null'
Result (2026-06-24)
find /dev/shm /tmp ~ -regex '.*zfold7.*\.p12' 2>/dev/null
(no output — P12 gone from tmpfs)
locate -r '.*\.p12$'
/home/evanusmodestus/go/pkg/mod/github.com/.../testdata/certificate.p12
(unrelated Go module test cert — not ours)
P12 confirmed gone. Re-issue from Vault required.
Re-issue from Vault
dsource d000 dev/vault
vault write pki_int/issue/domus-byod \
common_name="zfold7-evanusmodestus.byod.inside.domusdigitalis.dev" \
alt_names="zfold7.byod.inside.domusdigitalis.dev" \
uri_sans="spiffe://domusdigitalis.dev/byod/zfold7-evanusmodestus" \
ttl="2160h" \
-format=json > /dev/shm/zfold7-cert.json
jq -r '.data.certificate' /dev/shm/zfold7-cert.json > /dev/shm/zfold7.crt
jq -r '.data.private_key' /dev/shm/zfold7-cert.json > /dev/shm/zfold7.key
jq -r '.data.ca_chain[]' /dev/shm/zfold7-cert.json > /dev/shm/zfold7-chain.pem
openssl x509 -in /dev/shm/zfold7.crt -noout -subject -dates -ext subjectAltName
Bundle P12 and push to phone
openssl pkcs12 -export \
-out /dev/shm/zfold7-evanusmodestus.p12 \
-inkey /dev/shm/zfold7.key \
-in /dev/shm/zfold7.crt \
-certfile /dev/shm/zfold7-chain.pem \
-name "Domus BYOD EAP-TLS"
adb push /dev/shm/zfold7-evanusmodestus.p12 /sdcard/Download/
Install on Android
-
Settings → Security → More security settings → Install from device storage
-
Select
zfold7-evanusmodestus.p12 -
Enter the export password
-
Name:
Domus BYOD EAP-TLS -
Usage: WiFi
Configure WiFi profile
-
Settings → Wi-Fi → Domus-Secure
-
EAP method: TLS
-
CA certificate: DOMUS-ROOT-CA
-
User certificate: Domus BYOD EAP-TLS
-
Identity: zfold7-evanusmodestus.byod.inside.domusdigitalis.dev
Verify in ISE — use proven scripts
dsource d000 dev/network/ise
bash $(find ~/atelier -name 'auth-history.sh' -path '*/mnt/*') 9C:83:06:CE:89:46 full 168 \
| tee /tmp/ise-auth-zfold7-"$(date +%F)".json \
| jq -r '.[] | "\(.TIMESTAMP_TIMEZONE) \(.PASSED) \(.FAILURE_REASON)"'
bash $(find ~/atelier -name 'active-sessions.sh' -path '*/ise*') \
| tee /tmp/ise-sessions-"$(date +%F)".json \
| jq '.activeList.activeSession[]? | select(.calling_station_id | ascii_upcase | contains("9C:83:06"))'
Cleanup
shred -vfz -n 3 /dev/shm/zfold7*
rm -f /dev/shm/zfold7*
Pre-flight — connect and verify
ssh fold7
whoami
uname -m
pwd
Step 1: Pull stale repos
git -C ~/atelier/_bibliotheca/domus-captures pull
git -C ~/.config/nvim pull
git -C ~/atelier/_projects/personal/dotfiles-optimus pull
Step 2: Clone missing repos
git clone git@github.com:EvanusModestus/dots-quantum.git ~/atelier/_projects/personal/dots-quantum
Step 3: SSH config.d migration
The phone has a flat ~/.ssh/config from Phase 1. Workstation now uses modular ~/.ssh/config.d/ with 7 files.
From workstation — push config.d to phone
scp -r ~/.ssh/config.d fold7:~/.ssh/
On phone — replace flat config with Include directive
cp ~/.ssh/config ~/.ssh/config.bak
cat > ~/.ssh/config << 'EOF'
Include config.d/*.conf
EOF
chmod 600 ~/.ssh/config
Verify — test key hosts
ssh -T git@github.com
ssh -T git@gitlab.com
ssh -T git@gitea-01
Step 4: ~/.secrets repo
mkdir -p ~/.secrets
git clone git@github.com:EvanusModestus/secrets-metadata.git ~/.secrets
ls ~/.secrets/.metadata/keys/
Step 5: Re-stow dotfiles
cd ~/atelier/_projects/personal/dotfiles-optimus/base
stow -t ~ zsh
stow -t ~ git
stow -t ~ fzf
stow -t ~ fd
stow -t ~ ripgrep
stow -t ~ vim
Step 6: gopass — verify v3 store has new schema
gopass sync
gopass show v3/domains/d000/identity/ise/ise-01 | tail -n +2 | yq 'keys'
Step 7: Verify yq variant
yq --version
gopass show v3/domains/d000/identity/ssh/github | tail -n +2 | yq 'keys'
Regex Practice — Finding Project Partials
# All zfold7 .adoc files
find docs/modules/ROOT/partials/projects -regex '.*zfold.*\.adoc$'
# Only phase files (phase + any digit)
find docs/modules/ROOT/partials/projects -regex '.*zfold.*/phase-[0-9].*\.adoc$'
# Specific phase range — phases 0 through 4
find docs/modules/ROOT/partials/projects -regex '.*zfold.*/phase-0[0-4].*\.adoc$'
# Phase 8 across two projects — alternation with \( \| \)
find docs/modules/ROOT/partials/projects -regex '.*\(zfold\|ise-home\).*/phase-08.*\.adoc$'
# Jump into the result with nvim
nvim $(find docs/modules/ROOT/partials/projects -regex '.*ise-home.*/phase-08.*\.adoc$')
# find — live filesystem, takes directory arguments, slow but always current
find /dev/shm /tmp ~ -regex '.*zfold7.*\.p12' 2>/dev/null
# locate — database lookup, fast but stale, NO directory arguments
sudo updatedb
locate -r '.*zfold7.*\.p12'
# grep — searches file CONTENTS not filenames, use for references
grep -rn 'zfold7.*\.p12\|p12.*zfold7' docs/ --include='*.adoc'
Checklist
| Check | Status |
|---|---|
USBGuard — Samsung ADB allowed |
[ ] |
USBGuard — rule persisted ( |
[ ] |
ADB sees device ( |
[ ] |
P12 located or re-issued from Vault |
[ ] |
P12 pushed to phone via ADB |
[ ] |
BYOD cert installed on Android |
[ ] |
WiFi profile configured (EAP-TLS) |
[ ] |
ISE auth verified (PASSED, no failure) |
[ ] |
SSH to fold7 works |
[ ] |
domus-captures pulled |
[ ] |
domus-nvim pulled |
[ ] |
dotfiles-optimus pulled |
[ ] |
dots-quantum cloned |
[ ] |
SSH config.d/ deployed |
[ ] |
Include directive in ~/.ssh/config |
[ ] |
GitHub/GitLab/Gitea SSH verified |
[ ] |
~/.secrets cloned |
[ ] |
Dotfiles re-stowed |
[ ] |
gopass v3 synced + new schema visible |
[ ] |
yq variant confirmed |
[ ] |
Systemd Timer Audit — 11 User Timers
Systemd Timer Audit — 11 User Timers
Full audit of all user systemd timers (~/.config/systemd/user/*.timer). Read every timer, service unit, and backing script. Checked journals for each unit.
Scorecard
| Timer | Schedule | Status | Notes |
|---|---|---|---|
cert-expiry-monitor |
Daily 08:03 |
⚠️ Alerting |
2 expired certs (ISE EAP-TLS 9d, Vault SSH 10d) |
chla-code-alert |
M-F 08:07 + 14:17 |
⚠️ Alerting |
32 projects without PeopleSoft codes |
cissp-countdown |
Daily 05:30 + 20:00 |
✅ Healthy |
51 days to exam, tier 2 messaging |
daily-audit |
Daily 21:07 |
⚠️ Alerting |
17 stale |
domus-health |
Daily 09:00 |
🔧 Bug |
False "missed" on 3 weekly timers |
dotfiles-sync |
Daily 20:30 |
✅ Healthy |
— |
education-pulse |
Daily 06:00 |
✅ Healthy |
Thu = Español — Inmersión Total |
pacman-news |
Daily 07:15 |
✅ Recovered |
Failed 06-24 (transient), ok 06-25 |
project-stale |
Mon 07:30 |
✅ Healthy |
Ran Mon 06-22, next Mon 06-29 |
security-remediation |
Mon 07:00 |
✅ Healthy |
1/10 phases complete (10%) |
weekly-review |
Sun 19:00 |
✅ Healthy |
221 commits last week |
Bug — domus-health false positives
The morning health notification reported "3 missed" and project-stale: never ran. This is incorrect — all three weekly timers ran on schedule:
-
project-stale — ran Mon 06-22 07:41 (72h ago, next Mon 06-29)
-
security-remediation — ran Mon 06-22 07:41 (72h ago, next Mon 06-29)
-
weekly-review — ran Sun 06-21 19:00 (96h ago, next Sun 06-28)
Root cause: domus-health (~/.local/bin/domus-health) uses a flat 192-hour window for weekly timers but reports "never ran" for timers that last executed 72-96 hours ago. The staleness heuristic does not account for day-of-week scheduling — a Monday timer checked on Thursday is mid-cycle, not missed.
Fix needed: Compare against the timer’s OnCalendar day-of-week, or only flag weekly timers when they exceed their actual 168-hour cycle plus a 24-hour grace period.
Cert Expiry — Action Required
| Certificate | Expired/Expires | Urgency |
|---|---|---|
ISE EAP-TLS Server Cert |
Expired 2026-06-16 |
CRITICAL — 9 days overdue |
Vault SSH Signing Cert |
Expired 2026-06-15 |
CRITICAL — 10 days overdue |
ASA VPN SSL Cert |
Expires 2026-07-28 |
WARNING — 33 days remaining |
Renewal path: vault write pki_int/issue/domus-server common_name=…
Architecture Notes
All 11 scripts source ~/.local/bin/domus-notify which provides:
-
domus_notify_tier1/2/3()— notification dispatch (maps tonotify-sendurgency levels) -
domus_lock()— flock-based execution guard (prevents overlapping runs) -
days_until()/dow_num()— date helpers
Scripts live in ~/.local/bin/ (10 scripts) and scripts/chla-code-alert.sh (1 in domus-captures). All executable, all exiting clean. No missing dependencies — w3m (used by pacman-news) is installed.
Optimization Opportunities
-
domus-healthweekly logic — fix false positives (described above) -
pacman-newsresilience — add retry or timeout for transient network failures (failed 06-24, self-recovered) -
security-remediationprogress — 1/10 at 10% since creation; consider whether the phase list reflects current priorities or needs pruning
Infrastructure Enumeration — k3s, Wazuh, VM Inventory
Infrastructure Enumeration — Pending Items
Resuming infrastructure work stalled since March. Priorities: k3s NAT verification, Wazuh agent enrollment, VM inventory cleanup.
Current State — Verified 2026-06-25 (End of Session)
| Component | Status | Finding |
|---|---|---|
k3s cluster (k3s-master-01) |
✅ HEALTHY |
Single node, Rocky 9.7, v1.34.4+k3s1, 123 days uptime |
k3s NAT rule 170 (10.42.0.0/16) |
⚠️ WRONG SUBNET |
Pod CIDR is 10.0.0.0/x (Cilium), not 10.42.0.0/16 — rule 170 targets wrong network |
Wazuh pods (all 4) |
✅ RUNNING |
dashboard, indexer, manager-master, manager-worker — all 1/1 |
Wazuh indexer |
✅ HEALTHY |
Cluster green, 424 shards, 10,000+ alerts from modestus-p16g indexed |
Wazuh agent (modestus-p16g) |
✅ ACTIVE |
Agent 003, connected to wazuh-workers:1514, journald + auditd + pacman collecting |
Wazuh DNS |
✅ FIXED |
3 A records corrected on bind-01: wazuh→.133, dashboard→.137, api→.133 |
Wazuh dashboard |
⚠️ CERT MISMATCH |
Serves cert with CN=wazuh, not CN=wazuh-dashboard — Firefox blocks, IP access works |
3 svclb pods |
⚠️ PENDING |
svclb-dashboard, svclb-prometheus-grafana, svclb-prometheus-alertmanager — MetalLB pool likely exhausted |
ise-02 (kvm-02) |
SHUT OFF |
Replaced by ise-01; pending |
Wazuh DNS Service Map
Verified via reverse DNS — use hostnames, not IPs.
| Service | Hostname | Port(s) |
|---|---|---|
Wazuh Manager (API, registration, syslog) |
wazuh.inside.domusdigitalis.dev |
55000, 1515, 514/UDP |
Wazuh Indexer (OpenSearch) |
wazuh-indexer.inside.domusdigitalis.dev |
9200 |
Wazuh Dashboard |
wazuh-dashboard.inside.domusdigitalis.dev |
443 |
Wazuh Workers (agent events) |
wazuh-workers.inside.domusdigitalis.dev |
1514 |
Traefik Ingress |
traefik.inside.domusdigitalis.dev |
80, 443 |
Step 1 — k3s Cluster Health ✅ DONE
Single control-plane node healthy. sudo /usr/local/bin/k3s kubectl required (not in sudo PATH).
ssh k3s-master-01 'sudo /usr/local/bin/k3s kubectl get nodes -o wide'
ssh k3s-master-01 'sudo /usr/local/bin/k3s kubectl get pods -A -o wide'
Step 2 — Wazuh Stack Health ✅ DONE
All 4 pods running. Dashboard serves (302). Indexer authenticated via gopass.
DOMAIN=inside.domusdigitalis.dev
# Indexer query (gopass-backed, no credential exposure)
curl -sk "https://wazuh-indexer.$DOMAIN:9200/_cat/indices?v" \
-u "admin:$(gopass show -o v3/domains/d000/k3s/wazuh/indexer)"
# Dashboard reachability
curl -sk "https://wazuh-dashboard.$DOMAIN" -o /dev/null -w "%{http_code}\n"
Wazuh Connectivity Test
Test whether this workstation can reach the manager registration and event ports.
DOMAIN=inside.domusdigitalis.dev
# Event port (1514) — agent data path
nc -zv "wazuh-workers.$DOMAIN" 1514
# Registration port (1515) — agent enrollment
nc -zv "wazuh.$DOMAIN" 1515
# API authentication (55000)
curl -sk "https://wazuh.$DOMAIN:55000/security/user/authenticate" \
-u "wazuh-wui:$(gopass show -o v3/domains/d000/k3s/wazuh/api)"
# Indexer health (9200)
curl -sk "https://wazuh-indexer.$DOMAIN:9200/_cluster/health?pretty" \
-u "admin:$(gopass show -o v3/domains/d000/k3s/wazuh/indexer)"
# Dashboard reachability (443)
curl -sk "https://wazuh-dashboard.$DOMAIN" -o /dev/null -w "%{http_code}\n"
Step 4 — VM Inventory Verification
for host in kvm-01 kvm-02; do
printf "\n=== %s ===\n" "$host"
ssh "$host" 'sudo virsh list --all'
done
Step 5 — Stale VM Cleanup
ssh kvm-02 'sudo virsh undefine ise-02 --remove-all-storage'
Priority Order (Updated — End of Session)
| Priority | Action | Status |
|---|---|---|
P0 |
k3s cluster health check |
✅ Done |
P0 |
Wazuh stack health — pods, dashboard, indexer |
✅ Done |
P0 |
Agent connectivity — test 1515/1514 from workstation |
✅ Done |
P0 |
Wazuh DNS fix — 3 A records corrected on bind-01 |
✅ Done |
P0 |
Wazuh agent enrollment — modestus-p16g (CR-2026-06-25) |
✅ Done — agent 003 active, 10,000+ events indexed |
P0 |
Wazuh API scripts — 8 gopass-backed verification tools |
✅ Done — data/d000/infra/wazuh-api/ |
P0 |
k3s NAT — pod CIDR is 10.0.0.0/x not 10.42.0.0/16, rule 170 is wrong |
⚠️ Needs review |
P1 |
VM inventory verification — kvm-01 + kvm-02 |
[ ] |
P1 |
ise-02 stale VM deletion |
[ ] |
P1 |
Rotate Wazuh registration password from default |
[ ] |
P1 |
Wazuh dashboard TLS cert — CN mismatch (wazuh vs wazuh-dashboard) |
[ ] |
P1 |
Wazuh alert tuning — reduce AppArmor/LSM noise (rule 80730) |
[ ] |
P2 |
k3s-master-02/03 status — confirm running or decommissioned |
[ ] |
P2 |
3 pending svclb pods — MetalLB IP pool audit |
[ ] |
P2 |
AppArmor wazuh-agentd — complain → enforce (48h observation) |
[ ] |
P2 |
Rootcheck definition files — download from Wazuh GitHub |
[ ] |
P2 |
Enroll remaining agents — kvm-01, kvm-02, bind-01, bind-02, ipa-01, vault-01 |
[ ] |
Wazuh DNS A Record Fix
DNS records for Wazuh services pointed to wrong MetalLB external IPs. Discovered by comparing dig results against kubectl get svc -n wazuh -o wide output.
| Record | Had | Should Be | Reason |
|---|---|---|---|
wazuh |
10.50.1.132 |
10.50.1.133 |
Manager service (55000, 1515, 514/UDP) |
wazuh-dashboard |
10.50.1.133 |
10.50.1.137 |
Dashboard service (443) |
wazuh-api |
10.50.1.134 |
10.50.1.133 |
API lives on manager, not workers |
Fix Applied
Zone file on bind-01: /var/named/inside.domusdigitalis.dev.zone
# Before
sudo awk 'NR>=24 && NR<=28' /var/named/inside.domusdigitalis.dev.zone
# Fix serial + 3 A records
sudo sed -i 's/2026061501/2026062501/' /var/named/inside.domusdigitalis.dev.zone
sudo sed -i '25s/10.50.1.132/10.50.1.133/' /var/named/inside.domusdigitalis.dev.zone
sudo sed -i '26s/10.50.1.133/10.50.1.137/' /var/named/inside.domusdigitalis.dev.zone
sudo sed -i '27s/10.50.1.134/10.50.1.133/' /var/named/inside.domusdigitalis.dev.zone
# After
sudo awk 'NR==3 || (NR>=24 && NR<=28)' /var/named/inside.domusdigitalis.dev.zone
# Reload
sudo rndc reload
Verification
From workstation — all five Wazuh DNS records resolving correctly:
for svc in wazuh wazuh-workers wazuh-dashboard wazuh-indexer wazuh-api; do
printf "%-20s %s\n" "$svc" "$(dig +short $svc.inside.domusdigitalis.dev)"
done
| Record | IP | Status |
|---|---|---|
wazuh |
10.50.1.133 |
Pass |
wazuh-workers |
10.50.1.134 |
Pass |
wazuh-dashboard |
10.50.1.137 |
Pass |
wazuh-indexer |
10.50.1.130 |
Pass |
wazuh-api |
10.50.1.133 |
Pass |
Post-Fix Connectivity
All four tests that previously failed now succeed:
DOMAIN=inside.domusdigitalis.dev
nc -zv -w3 "wazuh.$DOMAIN" 1515
nc -zv -w3 "wazuh.$DOMAIN" 55000
curl -sk "https://wazuh.$DOMAIN:55000/security/user/authenticate" \
-u "wazuh-wui:$(gopass show -o v3/domains/d000/k3s/wazuh/api)"
curl -sk "https://wazuh-dashboard.$DOMAIN" -o /dev/null -w "Dashboard HTTP: %{http_code}\n"
| Test | Result |
|---|---|
nc wazuh:1515 |
Connection succeeded |
nc wazuh:55000 |
Connection succeeded |
API authenticate |
JWT token returned (error: 0) |
Dashboard HTTPS |
HTTP 302 |
Wazuh Agent Install — Arch Linux (RPM Extraction)
Official install script does not support Arch (RPM/DEB only). Agent installed by extracting the 4.14.3 RPM with bsdtar. Full CR: CR-2026-06-25
Pre-flight — connectivity + backup
DOMAIN=inside.domusdigitalis.dev
# Verify ports
nc -zv "wazuh.$DOMAIN" 1515
nc -zv "wazuh-workers.$DOMAIN" 1514
curl -sk -o /dev/null -w '%{http_code}' "https://wazuh.$DOMAIN:55000/"
# Backup security configs
BACKUP_DIR="/tmp/pre-wazuh-backup-$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"
sudo cp -a /etc/apparmor.d/ "$BACKUP_DIR/apparmor.d/"
sudo cp -a /etc/nftables.conf "$BACKUP_DIR/nftables.conf"
sudo aa-status > "$BACKUP_DIR/apparmor-status.txt" 2>&1
sudo nft list ruleset > "$BACKUP_DIR/nftables-ruleset.txt" 2>&1
GPG key import
curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH -o /tmp/wazuh-gpg-key.asc
gpg --import /tmp/wazuh-gpg-key.asc
gpg --list-keys | grep -i wazuh
Version match — agent must match manager
ssh k3s-master-01 'sudo /usr/local/bin/k3s kubectl exec -n wazuh wazuh-manager-master-0 -- /var/ossec/bin/wazuh-control info 2>/dev/null | head -5'
Result: WAZUH_VERSION="v4.14.3" — agent must be 4.14.3.
Install script investigation (failed)
CDN 4.x path returns AccessDenied. 4.14 path serves the script but only supports apt/yum/dnf/zypper — no pacman.
curl -sI https://packages.wazuh.com/4.14/wazuh-install.sh | head -3
curl -sO https://packages.wazuh.com/4.14/wazuh-install.sh
grep -i 'arch\|pacman\|apt\|yum\|dnf\|zypper' wazuh-install.sh | head -20
RPM extraction — the actual install method
cd /tmp
curl -sO "https://packages.wazuh.com/4.x/yum/wazuh-agent-4.14.3-1.x86_64.rpm"
file wazuh-agent-4.14.3-1.x86_64.rpm
mkdir -p /tmp/wazuh-staging
cd /tmp/wazuh-staging
bsdtar -xf /tmp/wazuh-agent-4.14.3-1.x86_64.rpm
# Install
sudo cp -a ./var/ossec /var/ossec
sudo cp ./usr/lib/systemd/system/wazuh-agent.service /usr/lib/systemd/system/
sudo groupadd -r wazuh 2>/dev/null
sudo useradd -r -g wazuh -d /var/ossec -s /sbin/nologin wazuh 2>/dev/null
sudo chown -R root:wazuh /var/ossec
sudo chown -R wazuh:wazuh /var/ossec/queue /var/ossec/var /var/ossec/logs /var/ossec/tmp
sudo chmod -R 770 /var/ossec/queue /var/ossec/var /var/ossec/logs /var/ossec/tmp
sudo chmod +x /var/ossec/bin/*
sudo chmod +x /var/ossec/active-response/bin/*
sudo systemctl daemon-reload
# Verify
sudo /var/ossec/bin/wazuh-agentd -V
systemctl status wazuh-agent 2>&1 | head -5
Wazuh 4.14.x uses group wazuh not ossec. Queue/var/logs/tmp need wazuh:wazuh ownership with 770 permissions or the agent fails with Queue not accessible: Permission denied.
|
Configure ossec.conf
No default ossec.conf in the RPM — write fresh. Arch uses journald exclusively (no /var/log/auth.log, /var/log/syslog, /var/log/nftables.log).
sudo tee /var/ossec/etc/ossec.conf > /dev/null << 'OSSEC_EOF'
<ossec_config>
<client>
<server>
<address>wazuh-workers.inside.domusdigitalis.dev</address>
<port>1514</port>
<protocol>tcp</protocol>
</server>
<enrollment>
<enabled>yes</enabled>
<manager_address>wazuh.inside.domusdigitalis.dev</manager_address>
<port>1515</port>
<agent_name>modestus-p16g</agent_name>
</enrollment>
<notify_time>10</notify_time>
<time-reconnect>60</time-reconnect>
</client>
<localfile>
<log_format>journald</log_format>
</localfile>
<localfile>
<log_format>audit</log_format>
<location>/var/log/audit/audit.log</location>
</localfile>
<localfile>
<log_format>syslog</log_format>
<location>/var/log/pacman.log</location>
</localfile>
<syscheck>
<disabled>no</disabled>
<frequency>43200</frequency>
<directories check_all="yes" realtime="yes">/etc,/usr/bin,/usr/sbin</directories>
<directories check_all="yes">/boot</directories>
<ignore>/etc/mtab</ignore>
<ignore>/etc/hosts.deny</ignore>
<ignore>/etc/mail/statistics</ignore>
<ignore>/etc/random-seed</ignore>
<ignore>/etc/adjtime</ignore>
<ignore>/etc/resolv.conf</ignore>
</syscheck>
<rootcheck>
<disabled>no</disabled>
<check_files>yes</check_files>
<check_trojans>yes</check_trojans>
<check_dev>yes</check_dev>
<check_sys>yes</check_sys>
<check_pids>yes</check_pids>
<check_ports>yes</check_ports>
<check_if>yes</check_if>
<frequency>43200</frequency>
</rootcheck>
<active-response>
<disabled>no</disabled>
</active-response>
<logging>
<log_format>plain</log_format>
</logging>
</ossec_config>
OSSEC_EOF
<server><address> points to wazuh-workers (1514 event delivery), NOT wazuh (manager). Manager handles registration (1515) only. Getting this wrong causes Transport endpoint is not connected.
|
Manual registration with agent-auth
Auto-enrollment via systemctl start causes duplicate agent race condition. Use agent-auth for one-shot registration.
# Write registration password
echo -n "password" | sudo tee /var/ossec/etc/authd.pass > /dev/null
sudo chmod 640 /var/ossec/etc/authd.pass
sudo chown root:wazuh /var/ossec/etc/authd.pass
# Register (one-shot — -P takes string, not file path)
sudo /var/ossec/bin/agent-auth \
-m wazuh.inside.domusdigitalis.dev \
-p 1515 \
-P password \
-A modestus-p16g
# Verify key written
sudo awk '{print "id="$1, "name="$2, "key_fields="NF}' /var/ossec/etc/client.keys
Registration password is password (Wazuh default). Rotate immediately.
|
If duplicate agent error — cleanup
DOMAIN=inside.domusdigitalis.dev
sudo systemctl stop wazuh-agent
TOKEN=$(curl -sk -u "wazuh-wui:$(gopass show -o v3/domains/d000/k3s/wazuh/api)" \
-X POST "https://wazuh.$DOMAIN:55000/security/user/authenticate" | jq -r '.data.token')
# List agents
curl -sk -H "Authorization: Bearer $TOKEN" \
"https://wazuh.$DOMAIN:55000/agents" | jq '.data.affected_items[] | {id, name, status}'
# Delete stale (replace AGENT_ID)
curl -sk -H "Authorization: Bearer $TOKEN" \
-X DELETE "https://wazuh.$DOMAIN:55000/agents?agents_list=AGENT_ID&status=all&older_than=0s" | jq '.message'
# Clear local keys and re-register
sudo truncate -s 0 /var/ossec/etc/client.keys
sudo /var/ossec/bin/agent-auth -m wazuh.$DOMAIN -p 1515 -P password -A modestus-p16g
Start agent and verify
sudo systemctl start wazuh-agent
sudo tail -15 /var/ossec/logs/ossec.log
sudo grep -i 'connected' /var/ossec/logs/ossec.log | tail -3
Expected: Connected to the server ([wazuh-workers.inside.domusdigitalis.dev]:1514/tcp)
AppArmor profile (complain mode)
sudo aa-status 2>&1 | grep -i 'wazuh\|ossec'
Expected: /var/ossec/bin/wazuh-agentd in complain mode. Enforce after 48h observation.
Result
bash $(find data/d000/infra/wazuh-api -name 'wazuh-agents.sh')
Agent 003 modestus-p16g — status active, version Wazuh v4.14.3, connected to workers on 1514.
Wazuh API Verification
Scripts at data/d000/infra/wazuh-api/. All output to /tmp/wazuh-*.json. All credentials via gopass.
Health dashboard
bash $(find data/d000/infra/wazuh-api -name 'wazuh-cluster-health.sh')
Agent list with OS/version/status
bash $(find data/d000/infra/wazuh-api -name 'wazuh-agents.sh')
Agent detail — specific agent
bash $(find data/d000/infra/wazuh-api -name 'wazuh-agent-detail.sh') modestus-p16g
Alerts — all agents or filtered
# Last 10 alerts, all agents
bash $(find data/d000/infra/wazuh-api -name 'wazuh-alerts.sh')
# Last 10 from modestus-p16g
bash $(find data/d000/infra/wazuh-api -name 'wazuh-alerts.sh') 10 modestus-p16g
# Last 20 from any agent
bash $(find data/d000/infra/wazuh-api -name 'wazuh-alerts.sh') 20
File Integrity Monitoring events
bash $(find data/d000/infra/wazuh-api -name 'wazuh-fim-events.sh') modestus-p16g
Delete stale agent
bash $(find data/d000/infra/wazuh-api -name 'wazuh-delete-agent.sh') AGENT_ID
Test alert — trigger and verify pipeline end-to-end
# Trigger failed SSH login (Wazuh rule 5710)
ssh -o PreferredAuthentications=password -o NumberOfPasswordPrompts=1 fakeuser@localhost 2>/dev/null; true
# Wait for propagation
sleep 30
# Check if alert appeared from modestus-p16g
bash $(find data/d000/infra/wazuh-api -name 'wazuh-alerts.sh') 5 modestus-p16g
Wazuh Alert Tuning — Light Tune
10,000+ events/hour from two sources identified via indexer query.
Noise source 1 — gopass/gpg AppArmor ALLOWED (suppress)
Routine apparmor="ALLOWED" events from gopass calling gpgconf/gpg. Library loads, locale files, keyboxd socket access. Fires every time API scripts run or git hooks trigger gopass. Harmless — suppress on manager.
Noise source 2 — wazuh-agentd merged.mg EACCES (fix, don’t suppress)
wazuh-agentd tries to create /var/ossec/etc/shared/merged.mg (manager-pushed shared config) and fails with exit=-13 (EACCES). Directory permissions block it (root:wazuh, mode 040750). This is a real issue — fix permissions, keep the alert visible.
Step 1 — Fix agent shared directory permissions
# Before
sudo ls -la /var/ossec/etc/ | grep shared
# Fix
sudo chmod 770 /var/ossec/etc/shared
sudo chown wazuh:wazuh /var/ossec/etc/shared
# After
sudo ls -la /var/ossec/etc/ | grep shared
Step 2 — Write custom rule to suppress gopass noise
tee /tmp/wazuh-local-rules.xml << 'EOF'
<!-- Local rules -->
<!-- Copyright (C) 2015, Wazuh Inc. -->
<group name="local,syslog,sshd,">
<rule id="100001" level="5">
<if_sid>5716</if_sid>
<srcip>1.1.1.1</srcip>
<description>sshd: authentication failed from IP 1.1.1.1.</description>
<group>authentication_failed,pci_dss_10.2.4,pci_dss_10.2.5,</group>
</rule>
</group>
<group name="local,audit,apparmor,">
<!-- Suppress ALL AppArmor ALLOWED events — DENIED is the security signal -->
<rule id="100010" level="0">
<if_sid>80730</if_sid>
<regex>apparmor=.ALLOWED.</regex>
<description>Suppressed: routine AppArmor ALLOWED operation (all profiles)</description>
</rule>
</group>
EOF
Step 3 — Copy rule to manager pod and restart
scp /tmp/wazuh-local-rules.xml k3s-master-01:/tmp/wazuh-local-rules.xml
ssh k3s-master-01 'sudo /usr/local/bin/k3s kubectl cp /tmp/wazuh-local-rules.xml wazuh/wazuh-manager-master-0:/var/ossec/etc/rules/local_rules.xml -c wazuh-manager'
ssh k3s-master-01 'sudo /usr/local/bin/k3s kubectl exec -n wazuh -c wazuh-manager wazuh-manager-master-0 -- cat /var/ossec/etc/rules/local_rules.xml'
ssh k3s-master-01 'sudo /usr/local/bin/k3s kubectl exec -n wazuh -c wazuh-manager wazuh-manager-master-0 -- /var/ossec/bin/wazuh-control restart'
Step 4 — Verify
sleep 60
bash $(find data/d000/infra/wazuh-api -name 'wazuh-alerts.sh') 10 modestus-p16g
Expected: gopass/gpg ALLOWED events no longer appear. wazuh-agentd events should stop after the permission fix. Real alerts (DENIED, level 5+) still flow.
Identify noise sources — the query that found this
jq '[.hits.hits[]._source | select(.rule.id == "80730") |
.full_log // .data.audit.comm // .data.audit.exe] |
group_by(.) | map({process: .[0], count: length}) |
sort_by(-.count)' /tmp/wazuh-alerts.json
Full Environment Sweep
Covers hypervisor inventory, k3s cluster state, Wazuh service exposure, internal port check, agent list, workstation connectivity, indexer health, MetalLB pool, NAT rules, and pod CIDR verification.
tee /tmp/domus-infra-sweep.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
DOMAIN=inside.domusdigitalis.dev
K3S="ssh k3s-master-01 'sudo /usr/local/bin/k3s kubectl'"
echo "=== HYPERVISOR INVENTORY ==="
for host in kvm-01 kvm-02; do
printf "\n--- %s ---\n" "$host"
ssh "$host" 'sudo virsh list --all'
done
echo -e "\n=== K3S CLUSTER ==="
eval $K3S get nodes -o wide
echo ""
eval $K3S get pods -A -o wide
echo ""
eval $K3S get svc -A -o wide
echo -e "\n=== WAZUH SERVICES (WHY 1515/55000 FAIL) ==="
eval $K3S get svc -n wazuh -o wide
echo ""
eval $K3S describe svc -n wazuh wazuh | grep -E 'Port|NodePort|External|Endpoint'
echo -e "\n=== WAZUH INTERNAL PORT CHECK ==="
eval $K3S exec -n wazuh wazuh-manager-master-0 -- ss -tlnp 2>/dev/null | grep -E '1515|55000|1514|514'
echo -e "\n=== WAZUH API INTERNAL ==="
eval $K3S exec -n wazuh wazuh-manager-master-0 -- curl -sk https://localhost:55000 -o /dev/null -w "API HTTP: %{http_code}\n" 2>/dev/null
echo -e "\n=== WAZUH AGENT LIST ==="
eval $K3S exec -n wazuh wazuh-manager-master-0 -- /var/ossec/bin/agent_control -l 2>/dev/null
echo -e "\n=== CONNECTIVITY FROM WORKSTATION ==="
nc -zv -w3 "wazuh-workers.$DOMAIN" 1514 2>&1
nc -zv -w3 "wazuh.$DOMAIN" 1515 2>&1
nc -zv -w3 "wazuh.$DOMAIN" 55000 2>&1
curl -sk "https://wazuh-dashboard.$DOMAIN" -o /dev/null -w "Dashboard HTTP: %{http_code}\n"
curl -sk --max-time 5 "https://wazuh.$DOMAIN:55000/security/user/authenticate" \
-u "wazuh-wui:$(gopass show -o v3/domains/d000/k3s/wazuh/api)" 2>&1 | head -1
echo -e "\n=== WAZUH INDEXER ==="
curl -sk "https://wazuh-indexer.$DOMAIN:9200/_cluster/health?pretty" \
-u "admin:$(gopass show -o v3/domains/d000/k3s/wazuh/indexer)"
echo -e "\n=== METALLB CONFIG ==="
eval $K3S get ipaddresspool -A -o wide 2>/dev/null || eval $K3S get configmap -n metallb-system -o yaml 2>/dev/null | grep -A5 'address-pools'
echo ""
eval $K3S get svc -A -o wide | grep LoadBalancer
echo -e "\n=== NAT RULE 170 (VYOS) ==="
ssh vyos-01 'show nat source rules' 2>/dev/null
echo -e "\n=== POD CIDR VERIFICATION ==="
eval $K3S get pods -A -o wide | awk 'NR>1 {print $7}' | sort -t. -k1,1n -k2,2n -u | head -5
echo -e "\n=== K3S SERVICE STATUS ==="
ssh k3s-master-01 'systemctl is-active k3s; uptime'
echo "=== DONE ==="
EOF
bash /tmp/domus-infra-sweep.sh
Domus Infrastructure Project — Build & View
Three diagrams: Graphviz topology, D2 service dependencies, D2 radial v7 (from domus-infra-ops).
tee /tmp/build-domus-infra.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
PRJ=data/d000/projects/domus-infrastructure
# Graphviz topology
dot -Tsvg "$PRJ/diagrams/infrastructure-topology.dot" \
-o "$PRJ/output/infrastructure-topology.svg"
# D2 service dependencies (dark theme)
d2 --theme 200 "$PRJ/diagrams/service-dependencies.d2" \
"$PRJ/output/service-dependencies.svg"
# D2 radial v7 (dark theme)
d2 --theme 200 "$PRJ/diagrams/infrastructure-radial-v7.d2" \
"$PRJ/output/infrastructure-radial-v7.svg"
# Build standalone HTML
build-adoc $(find "$PRJ" -maxdepth 1 -name 'domus-infrastructure.adoc') \
html --variant light-cyan -a env-d000
# Open all in firefox
firefox $(find "$PRJ/output" -name 'domus-infrastructure.html') \
"$PRJ/output/infrastructure-topology.svg" \
"$PRJ/output/service-dependencies.svg" \
"$PRJ/output/infrastructure-radial-v7.svg" &
EOF
bash /tmp/build-domus-infra.sh
P16g Monitor Disconnect Freeze (INC-2026-06-25-002)
P16g Complete Freeze on External Monitor Disconnect (INC-2026-06-25-002)
ThinkPad P16g locked up hard after disconnecting external monitor via USB-C. No tty switch, no SSH, no network — required hard power cycle. This is the third display-related incident on this machine in one month, following the i915 PSR hang (INC-2026-05-23-002) and the WAYLAND-1 phantom monitor.
Investigation Commands
journalctl -b -1 --no-pager | grep -iE 'nvidia|i915|drm|gpu|hang|fault|error' | tail -50
journalctl -b -1 -p err --no-pager | tail -30
cat /proc/cmdline
hyprctl monitors -j | jq '.[].name'
journalctl -b -1 --no-pager | grep -iE 'hotplug|unplug|disconnect|connector' | tail -20
journalctl -b -1 --no-pager | grep -iE 'hung_task|rcu.*stall|soft.*lockup|hard.*lockup|watchdog' | tail -20
AppArmor curl Profile — CA Cert Read Denied (INC-2026-06-25)
AppArmor curl Profile — CA Cert Read Denied (INC-2026-06-25)
ISE ERS and MnT API calls fail with curl: (77) error adding trust anchors from file. Root cause: curl AppArmor profile (deployed 2026-06-24) has deny @{HOME}/.secrets/** r, blocking CA cert reads. DataConnect unaffected (no AppArmor profile on Python).
Prerequisites
dsource d000 dev/network/ise
source data/shared/scripts/ise-helpers.sh
Reproduce
ers "/endpoint?filter=mac.EQ.A8:2B:DD:8F:23:E6" | jq '.SearchResult.total'
mnt "/Session/ActiveCount" | xq '.'
MAC="A8:2B:DD:8F:23:E6" . $(find data -name 'full-endpoint-profile.sh')
curl: (77) error adding trust anchors from file: /home/evanusmodestus/.secrets/certs/d000/ise/ROOT-CA.crt
Diagnosis
strace -e openat curl --cacert "$ISE_CA_CERT" https://${ISE_PAN_FQDN} 2>&1 | grep -i 'secrets\|EACCES'
openat(AT_FDCWD, "/home/evanusmodestus/.secrets/certs/d000/ise/ROOT-CA.crt", O_RDONLY) = -1 EACCES (Permission denied)
openat(AT_FDCWD, "/home/evanusmodestus/.secrets/certs/d000/ise/ROOT-CA.crt", O_RDONLY) = -1 EACCES (Permission denied)
curl: (77) error adding trust anchors from file: /home/evanusmodestus/.secrets/certs/d000/ise/ROOT-CA.crt
EACCES on openat means the kernel refused the file read. Not a cert problem — a policy problem. strace bypasses the application’s error message and shows what actually happened at the syscall layer.
sudo aa-status | grep curl
/usr/bin/curl
sudo grep -n 'secrets' /etc/apparmor.d/usr.bin.curl
33: deny @\{HOME}/.secrets/** r,
Elimination evidence
| Test | Result |
|---|---|
|
|
|
Issuer=DOMUS-ROOT-CA, Subject=DOMUS-ISSUING-CA, CA:TRUE |
|
PEM certificate — valid format |
|
Modify: 2026-02-09 — unchanged since February |
|
HTTP 301 — system CA bundle works with |
|
8.20.0 / OpenSSL 3.6.3 |
Why this was hard to find
| Behavior | Explanation |
|---|---|
|
Explicit |
|
The |
Error said "trust anchors" |
curl translates |
|
No AppArmor profiles on those binaries — only |
Fix
Pre-change — backup
sudo cp /etc/apparmor.d/usr.bin.curl /etc/apparmor.d/usr.bin.curl.bak.$(date +%F)
ls -la /etc/apparmor.d/usr.bin.curl*
Fix attempt 1 — FAILED (documented for learning)
Added an owner allow rule above the deny line:
# DO NOT USE — deny overrides allow in AppArmor
# sudo sed -i '/deny @\{HOME}\/.secrets\/\*\* r,/i\ owner @\{HOME}/.secrets/certs/** r,' /etc/apparmor.d/usr.bin.curl
This does NOT work. AppArmor’s deny keyword is absolute — no allow or owner rule can override it. The deny always wins regardless of rule order. This is a fundamental AppArmor design decision that differs from firewall ACLs (where order matters) and SELinux (where allow can override deny via booleans).
Fix attempt 2 — SUCCEEDED
Replace the single blanket deny with specific denies that exclude the certs path. No deny covers ~/.secrets/certs/ anymore — instead, each sensitive subdirectory is denied individually.
sudo cp /etc/apparmor.d/usr.bin.curl.bak.$(date +%F) /etc/apparmor.d/usr.bin.curl
sudo grep -n 'secrets' /etc/apparmor.d/usr.bin.curl
sudo sed -i 's| deny @\{HOME}/\.secrets/\*\* r,| # Deny sensitive secrets — but allow CA certs for API tools\n deny @\{HOME}/.secrets/.metadata/** r,\n deny @\{HOME}/.secrets/gopass/** r,\n deny @\{HOME}/.secrets/vaults/** r,\n owner @\{HOME}/.secrets/certs/** r,|' /etc/apparmor.d/usr.bin.curl
sudo grep -n 'secrets' /etc/apparmor.d/usr.bin.curl
34: # Deny sensitive secrets — but allow CA certs for API tools
35: deny @\{HOME}/.secrets/.metadata/** r,
36: deny @\{HOME}/.secrets/gopass/** r,
37: deny @\{HOME}/.secrets/vaults/** r,
38: owner @\{HOME}/.secrets/certs/** r,
sudo apparmor_parser -R /etc/apparmor.d/usr.bin.curl && sudo apparmor_parser -a /etc/apparmor.d/usr.bin.curl
sudo aa-status | grep curl
Post-change verification — PASSED
mnt "/Session/ActiveCount" | xq '.'
{
"sessionCount": {
"count": "4"
}
}
MAC="A8:2B:DD:8F:23:E6" . $(find data -name 'full-endpoint-profile.sh')
curl file://$HOME/.secrets/.metadata/keys/master.age.key 2>&1 | head -1
curl: (37) Could not open file /home/evanusmodestus/.secrets/.metadata/keys/master.age.key
strace -e openat curl --cacert "$ISE_CA_CERT" https://${ISE_PAN_FQDN} 2>&1 | grep -i 'EACCES'
sudo dmesg | grep -i 'apparmor.*curl' | tail -5
Rollback
sudo cp /etc/apparmor.d/usr.bin.curl.bak.$(date +%F) /etc/apparmor.d/usr.bin.curl
sudo apparmor_parser -R /etc/apparmor.d/usr.bin.curl && sudo apparmor_parser -a /etc/apparmor.d/usr.bin.curl
sudo grep -n 'secrets' /etc/apparmor.d/usr.bin.curl
What was learned
| Concept | Lesson |
|---|---|
|
The definitive diagnostic for file access failures. Shows the syscall, the path, and the kernel’s response ( |
AppArmor |
Cannot be overridden by |
AppArmor |
Explicit |
|
|
Granular deny > blanket deny |
|
Application errors lie |
curl said "error adding trust anchors" — implying a cert format problem. The real cause was a kernel-level permission denial. Always verify with |
Security hardening breaks things |
The AppArmor profile was deployed yesterday as part of a vulnerability assessment. It worked for everything except ISE API calls because the cert path was inside the denied |
AppArmor vs SELinux — quick comparison for your colleague
| Feature | AppArmor | SELinux |
|---|---|---|
Model |
Path-based — rules reference file paths |
Label-based — rules reference security contexts (labels on files/processes) |
Default stance |
Permissive (allow unless denied) — profiles opt-in |
Restrictive (deny unless allowed) — system-wide mandatory |
|
Absolute, silent, overrides complain mode |
|
Debugging |
|
|
Distro default |
Ubuntu, SUSE, Arch (optional) |
RHEL, Fedora, CentOS |
Learning curve |
Lower — path-based rules are intuitive |
Higher — label assignment and policy modules are complex |
This incident |
Silent |
Would have logged the denial in audit log. |
Both enforce Mandatory Access Control (MAC) — the kernel decides access regardless of file permissions (chmod). Traditional Unix permissions (DAC) check user/group/other. MAC checks policy on top of DAC. A file can be chmod 644 and still be denied by AppArmor/SELinux.
Appendix — study resources
man apparmor.d
man apparmor_parser
man aa-status
man aa-complain
man aa-enforce
man aa-logprof
| Resource | Why |
|---|---|
|
Profile syntax — rules, file globs, |
|
How profiles are loaded ( |
|
Shows which profiles are loaded, which are enforced vs complain, which processes are confined. |
|
Toggle modes. Now you know complain mode does NOT affect |
|
Interactive tool that reads AppArmor logs and suggests profile changes. Only works for implicit denials (logged), not explicit |
Arch Wiki — AppArmor |
Practical setup guide, Arch-specific. Covers installation, boot params, profile management, and |
This partial |
Real incident with real syscall traces, two fix attempts (failed + succeeded), and the |
PCRE Lookahead — Multi-Term Same-Line Search
PCRE Lookahead — Multi-Term Same-Line Search
Finding lines where multiple terms ALL appear — regardless of order. Uses PCRE lookaheads (grep -P) to assert each term exists without consuming the line.
The core pattern — lookahead conjunction
grep -rnP '(?=.*EAP)(?=.*TEAP)(?=.*802\.1X)' --include='*.adoc' docs/
| Token | What it does |
|---|---|
|
Lookahead — asserts |
|
Second lookahead — asserts |
|
Third lookahead — asserts |
Combined |
All three must be true for the line to match. Order on the line is irrelevant — |
Why lookaheads and not .*EAP.*TEAP.*802\.1X? Because the non-lookahead version requires the terms in that exact order. Lookaheads are order-independent — each one tests the entire line independently.
Add color + line numbers
grep -rnP --color=always '(?=.*EAP)(?=.*TEAP)(?=.*802\.1X)' --include='*.adoc' docs/
Count matches per file — ranked by relevance
grep -rcP '(?=.*EAP)(?=.*TEAP)(?=.*802\.1X)' --include='*.adoc' docs/ | grep -v ':0$' | sort -t: -k2 -rn
| Stage | What it does |
|---|---|
|
|
|
Remove files with zero matches. |
|
|
Top N files with their matching lines — 5-stage pipeline
grep -rcP '(?=.*EAP)(?=.*TEAP)(?=.*802\.1X)' --include='*.adoc' docs/ | grep -v ':0$' | sort -t: -k2 -rn | head -3 | cut -d: -f1 | xargs grep -nP --color=always '(?=.*EAP)(?=.*TEAP)(?=.*802\.1X)'
| Stage | What it does |
|---|---|
|
Count matches per file |
|
Remove zero-match files |
|
Rank by count descending |
|
Take top 3 files |
|
Extract filename only (drop the count) |
|
Show the actual matching lines from those top 3 files |
Each stage is independently testable — run any prefix of the pipeline to see intermediate results. That’s composition.
Variations — different conjunctions
grep -rnP '(?=.*EAP)(?=.*(?:TEAP|802\.1X))' --include='*.adoc' docs/
(?:…) is a non-capturing group — groups without creating a backreference. Used here to OR two terms inside a single lookahead.
grep -rniP '(?=.*eap)(?=.*teap)(?=.*802\.1x)' --include='*.adoc' docs/
grep -rnP -C1 '(?=.*EAP)(?=.*TEAP)(?=.*802\.1X)' --include='*.adoc' docs/
grep -rnP '(?=.*EAP)(?=.*TEAP)(?!.*802\.1X)' --include='*.adoc' docs/
(?!…) is negative lookahead — asserts the pattern does NOT exist. Inverts the condition for that one term.
grep -rnP '(?=.*EAP)(?=.*TEAP)(?=.*802\.1X)(?=.*RADIUS)' --include='*.adoc' docs/
Each additional (?=…) narrows the results. Stack as many as needed.
Search both trees — docs/ AND data/
docs/ holds Antora reference material. data/ holds operational content — investigations, GPO findings, migration projects. Always search both:
grep -rcP '(?=.*EAP)(?=.*TEAP)(?=.*802\.1X)' --include='*.adoc' docs/ | grep -v ':0$' | sort -t: -k2 -rn
grep -rcP '(?=.*EAP)(?=.*TEAP)(?=.*802\.1X)' --include='*.adoc' data/ | grep -v ':0$' | sort -t: -k2 -rn
grep -rcP '(?=.*EAP)(?=.*TEAP)(?=.*802\.1X)' --include='*.adoc' docs/ data/ | grep -v ':0$' | sort -t: -k2 -rn
grep -rcP '(?=.*EAP)(?=.*TEAP)(?=.*802\.1X)' --include='*.adoc' data/ | grep -v ':0$' | sort -t: -k2 -rn | head -3 | cut -d: -f1 | xargs grep -nP --color=always '(?=.*EAP)(?=.*TEAP)(?=.*802\.1X)'
data/d001/ files must be decrypted (d001 open <project>) before grep can see their contents. Encrypted .age files won’t match.
|
Open top result in nvim
nvim $(grep -rcP '(?=.*EAP)(?=.*TEAP)(?=.*802\.1X)' --include='*.adoc' docs/ | grep -v ':0$' | sort -t: -k2 -rn | head -1 | cut -d: -f1)
Jump to first match inside nvim: /EAP.*TEAP\|TEAP.*EAP
Lookahead quick reference
| Syntax | Name | Matches when |
|---|---|---|
|
Positive lookahead |
|
|
Negative lookahead |
|
|
Positive lookbehind |
|
|
Negative lookbehind |
|
|
Non-capturing group |
Groups |
Lookbehinds in grep -P must be fixed-length — (?⇐EAP) works, (?⇐EAP.*) does not. Lookaheads have no length restriction.
|
Why this matters
Traditional grep finds one pattern per invocation. Lookaheads let you express "this line must contain A AND B AND C" in a single regex. Without lookaheads, you’d need:
grep 'EAP' file | grep 'TEAP' | grep '802\.1X'
Three processes, three pipes. The lookahead does it in one pass — faster, composable with sort/head/xargs, and the regex itself documents the search intent.
Endpoint Audit — Wired + Wireless (d000 Lab Test)
Endpoint Audit — Wired + Wireless (d000 Lab Test)
Full 3-API audit of both P16g interfaces before running the same workflow against d001 production (5C:FF:35:D8:BB:26) at 1pm.
Full endpoint profile — both MACs
MAC="A8:2B:DD:8F:23:E6" . $(find data -name 'full-endpoint-profile.sh')
MAC="E0:D5:5D:6C:E1:66" . $(find data -name 'full-endpoint-profile.sh')
Comparison — what the APIs returned
| Field | Wired (A8:2B:DD:8F:23:E6) | Wireless (E0:D5:5D:6C:E1:66) |
|---|---|---|
ERS Group |
Linux-Workstations (static ✅) |
Workstation (dynamic — needs fix) |
Custom Attributes |
OS=Arch Linux, Purpose=Engineering ✅ |
Empty — needs fix |
Description |
Evan - modestus-p16g wired - Arch Linux ✅ |
null — needs fix |
NAD |
Home-3560CX-01 Gi1/0/2 |
Home-9800-WLC capwap_90000002 |
Profiler Policy |
Unknown |
Linux-Workstation |
IP |
10.50.1.203 |
10.50.1.204 |
Auth Method |
dot1x / EAP-TLS / x509_PKI |
dot1x / EAP-TLS / x509_PKI |
Cert CN |
modestus-p16g.inside.domusdigitalis.dev |
modestus-p16g.inside.domusdigitalis.dev |
MnT Session |
null (no active session) |
Stop (idle timeout, June 19) |
Fix wireless endpoint — match wired config
jq -n '{OS:"Arch Linux", DevicePurpose:"Engineering Workstation"}' > /tmp/ep-attrs.json
bash $(find data/d000/infra/ise-api-reference/scripts/ers -name 'update-endpoint.sh') \
E0:D5:5D:6C:E1:66 4833f280-fd8f-11f0-9bb2-fafc6167f873 "Evan - modestus-p16g wireless - Arch Linux" /tmp/ep-attrs.json
jq -r '
def none: "∅";
"ISE ERS Endpoint Update Result",
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
(.UpdatedFieldsList.updatedField[] |
"✓ \(.field)\n old: \(.oldValue // none)\n new: \(.newValue // none)\n"
)
' /tmp/endpoint-E0D55D6CE166-result.json
{
printf 'FIELD\tOLD\tNEW\n'
jq -r '
.UpdatedFieldsList.updatedField[]
| [.field, (.oldValue // "∅"), (.newValue // "∅")]
| @tsv
' /tmp/endpoint-E0D55D6CE166-result.json
} | column -t -s $'\t'
bash $(find data/d000/infra/ise-api-reference/scripts/ers -name 'update-endpoint.sh') E0:D5:5D:6C:E1:66
DataConnect — individual queries (practice for d001)
dc_query "
SELECT CALLING_STATION_ID AS MAC, AUTHENTICATION_METHOD, AUTHENTICATION_PROTOCOL,
CREDENTIAL_CHECK, COUNT(*) AS CNT,
SUM(CASE WHEN FAILED > 0 THEN 1 ELSE 0 END) AS FAILURES
FROM radius_authentications
WHERE CALLING_STATION_ID IN ('A8:2B:DD:8F:23:E6','E0:D5:5D:6C:E1:66')
GROUP BY CALLING_STATION_ID, AUTHENTICATION_METHOD, AUTHENTICATION_PROTOCOL, CREDENTIAL_CHECK
ORDER BY CALLING_STATION_ID, CNT DESC
" | jq '.'
dc_query "
SELECT CALLING_STATION_ID AS MAC,
MIN(TIMESTAMP_TIMEZONE) AS FIRST_SEEN,
MAX(TIMESTAMP_TIMEZONE) AS LAST_SEEN,
COUNT(*) AS TOTAL_AUTHS,
SUM(CASE WHEN FAILED > 0 THEN 1 ELSE 0 END) AS TOTAL_FAILURES
FROM radius_authentications
WHERE CALLING_STATION_ID IN ('A8:2B:DD:8F:23:E6','E0:D5:5D:6C:E1:66')
GROUP BY CALLING_STATION_ID
" | jq '.'
dc_query "
SELECT CALLING_STATION_ID AS MAC, NAS_IP_ADDRESS, DEVICE_NAME, NAS_PORT_ID, NAS_PORT_TYPE
FROM radius_authentications
WHERE CALLING_STATION_ID IN ('A8:2B:DD:8F:23:E6','E0:D5:5D:6C:E1:66')
ORDER BY CALLING_STATION_ID, TIMESTAMP_TIMEZONE DESC
" | jq '.'
dc_query "
SELECT CALLING_STATION_ID AS MAC, TIMESTAMP_TIMEZONE, FAILURE_REASON,
AUTHENTICATION_METHOD, POLICY_SET_NAME, DEVICE_NAME
FROM radius_authentications
WHERE CALLING_STATION_ID IN ('A8:2B:DD:8F:23:E6','E0:D5:5D:6C:E1:66')
AND FAILED > 0
ORDER BY TIMESTAMP_TIMEZONE DESC
FETCH FIRST 20 ROWS ONLY
" | jq '.'
dc_query "SELECT * FROM radius_accounting FETCH FIRST 1 ROWS ONLY" | jq '.[0] | keys'
dc_query "
SELECT CALLING_STATION_ID AS MAC, USERNAME, ACCT_SESSION_TIME, ACCT_STATUS_TYPE,
ACCT_INPUT_OCTETS AS BYTES_IN, ACCT_OUTPUT_OCTETS AS BYTES_OUT,
ACCT_TERMINATE_CAUSE
FROM radius_accounting
WHERE CALLING_STATION_ID IN ('A8:2B:DD:8F:23:E6','E0:D5:5D:6C:E1:66')
ORDER BY TIMESTAMP_TIMEZONE DESC
FETCH FIRST 10 ROWS ONLY
" | jq '.'
dc_query "
SELECT CALLING_STATION_ID AS MAC, TIMESTAMP_TIMEZONE, USERNAME,
AUTHENTICATION_PROTOCOL, PASSED, DEVICE_NAME
FROM radius_authentications
WHERE CALLING_STATION_ID IN ('A8:2B:DD:8F:23:E6','E0:D5:5D:6C:E1:66')
ORDER BY TIMESTAMP_TIMEZONE DESC
FETCH FIRST 10 ROWS ONLY
" | jq -r '
"Auth Timeline — Both Interfaces",
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
(.[] |
"\(.TIMESTAMP_TIMEZONE) \(.MAC) \(.PASSED) \(.AUTHENTICATION_PROTOCOL) \(.DEVICE_NAME)"
)
'
Ready for d001
Same commands work at d001 — replace dsource d000 with dsource d001 and swap the MACs for 5C:FF:35:D8:BB:26. All queries use IN (…) syntax — add more MACs by appending to the list.
Abnormal Security — Phase 3 All IS (Live Execution 2026-06-25)
Phase 3 executed live by Evan at 14:30. Dynamic pull from ISAllHands distro group. Cleaned version in d001 project: partials/phase3-execution-2026-06-25.adoc. Raw execution log: notes/itrack-unsorted-chg-adlib-adoc-2026-06-25.adoc.
d001 open abnormal-security
nvim $(find data/d001/projects/abnormal-security -type f -regex '.*phase3-execution.*\.adoc')
External Email Tag Stacking — Transport Rule Investigation
Reported via Teams by Sarah Clizer. ExceptIfSubjectContainsWords missing on external sender modification rule. Investigation commands in d001 project.
d001 open abnormal-security
nvim $(find data/d001/projects/abnormal-security -type f -regex '.*external-tag.*\.adoc')
Work (CHLA)
| CHARGE TIME IN PEOPLESOFT - CRITICAL. Do this NOW before anything else. |
Critical (P0)
| Project | Description | Owner | Status | Due | Blocker |
|---|---|---|---|---|---|
Linux Research (Xianming Ding) |
EAP-TLS for Linux workstations, dACL, UFW |
Evan |
BEHIND (72 days overdue) |
02-24 |
Certificate "password required" - nmcli fix documented |
iPSK Manager |
Pre-shared key automation |
Ben Castillo |
BEHIND |
— |
DB replication issues |
MSCHAPv2 Migration |
Legacy auth deprecation — 6,227 devices, 5 waves. 6 batch SQL queries + 3-API endpoint profile script added (05-11). Report due. |
Evan |
25% — Report due, batch queries ready |
05-30 |
Report to turn in |
Research Segmentation |
All endpoints to Untrusted VLAN |
Evan |
BLOCKED |
— |
CISO decision pending |
Disaster Recovery |
ISE DR scoping — dot1x closed mode = total blackout |
Evan |
Scoping |
— |
— |
Mandiant Remediation |
Copy 4/16 findings, Guest ACL lab, Q2 assessment |
Evan |
Active |
— |
— |
SIEM QRadar → Sentinel |
Full SIEM platform transition. Monad console error resolved 05-12. Secrets configured. Blocked on DCR creation (Rule ID + Stream Name). Azure private network policy unresolved. |
Evan |
Active — blocked on DCR |
Q2 2026 |
Victor/Mauricio: create DCR, resolve Azure network policy |
Abnormal Security |
AI email platform — ESA cutover. CR assigned, CAB May 12 15:00. Implementation May 14 10:00. |
Evan |
Active — CAB today 15:00 |
05-14 |
Pre-CAB checklist: confirm Tyler, Jason, Sarah |
High Priority (P1)
| Project | Description | Owner | Status | Target |
|---|---|---|---|---|
ISE 3.4 Migration |
Upgrade from 3.2p9 |
Evan |
Blocked — maintenance window needed |
Q2 2026 |
Switch Upgrades |
IOS-XE fleet update (C9300, 3560CX) |
Evan |
Pending |
Q2 2026 |
Spikewell BYOD VPN |
dACL SQL, AD group integration |
Evan |
Active |
— |
Strongline Gateway |
MAC capture, Identity Group setup — 37 days aging |
Evan |
Active — David Rukiza assigned |
— |
Abnormal Security |
AI email security platform research, ESA cutover timeline |
Evan |
Newly assigned |
— |
DMZ Migration |
External services audit behind NetScaler |
Evan |
Audit phase |
— |
Firewall Audit (murus-portae) |
EtherChannel query, prefilter, policy assignments |
Evan |
Scoping — ASA API creds needed |
— |
iPSK Manager HA |
Server 2 config, TLS, SQL security audit |
Evan |
In progress |
— |
Sentinel KQL |
Build proficiency, distinguish from team |
Evan |
Onboarding |
— |
VNC Blocking |
Block and eliminate VNC protocol enterprise-wide |
Evan |
Active — Phase 0 (Discovery) |
Mid-June 2026 |
Strategic (P2)
| Project | Description | Owner | Status |
|---|---|---|---|
HHS Regulatory Compliance |
New HHS security policies implementation |
TBD |
NOT STARTED |
InfoSec Reporting Dashboard |
PowerBI metrics for executives |
TBD |
NOT STARTED |
EDR Migration (AMP → Defender) |
Endpoint protection consolidation |
TBD |
NOT STARTED |
Azure Legacy Migration |
Modern landing zone |
Team |
In Progress |
ChromeOS EAP-TLS |
SCEP + Victor, Paul testing |
Victor |
In Progress |
P0 — Critical / Blocking
Security & Compliance
-
ISE 3.2 Patch 10 upgrade — CVE-2026-20147 CVSS 9.9 / CVE-2026-20148. Propose maintenance window once patch confirmed on software.cisco.com.
-
ISE Advisory sa-ise-rce-traversal-8bYndVrZ — check Patch 10 availability
-
Mandiant Remediation — findings status tracked. Working session prep + defensive posture documented (comms-2026-04-24). Copy 4/16 updates into Excel at work. Guest ACL lockdown (WIR-M-01) pending lab validation. appendix-todos updated with MSCHAPv2 milestones.
-
Guest ACL update — guest redirect ACL work needed. Lab validate GUEST_CWA_REDIRECT_MAX_SECURITY in d000, then joint CR with NE. On today’s task list.
-
Disaster Recovery & Downtime Procedures — ISE top priority (dot1x closed mode = SPOF for network access)
-
ISE DR: Document failover sequence — PAN, MnT, PSN priority order
-
ISE DR: RADIUS dead-server detection on WLCs/switches — critical-auth VLAN fallback
-
ISE DR: Backup/restore procedures — scheduled config backups, tested restores
-
FTD/FMC DR: FMC loss = no policy management
-
Network DR: Core/distribution switch failure, STP reconvergence, HSRP failover
-
Document RTO/RPO per system
-
SIEM Migration (QRadar → Sentinel)
-
SIEM QRadar → Sentinel Migration — LEAD ROLE. 4 collection iterations (Apr 16, 17, 17-streamlined, 20-streamlined). Python chart pipeline built (
qradar-charts.py). Migration XLSX generated. Verification pending. Comms sent Apr 23.-
d001 artifacts: 8 JSON exports, 2 CSV inventories, migration XLSX, top5 source SVG/PNG, verification doc
-
Dependency: Monad pipeline for log source transition
-
Dependency: Sentinel KQL proficiency for query migration
-
-
Monad Pipeline Evaluation (origin: 2026-03-11) — lead role. Console error RESOLVED 05-12. 06-09: Architecture decision — rsyslog (CHLXSYSLOG01) as collection tier → Monad → Sentinel. ISE lab → rsyslog → Monad 6-step execution guide created with 10 API calls. ASA lab logs already flowing through rsyslog. DCR still needed — Victor + Mauricio.
-
Sentinel KQL — build proficiency, distinguish from team. Azure portal access acquired.
-
QRadar log source report — run AQL queries, fetch JSON, generate Python Excel
Active Deployments & Migrations
-
MSCHAPv2 Migration — 6-sheet Standard Report ready. Migration window 05-04 to 05-30 CLOSED. Confirm final report status and next steps with team. 6,227 MSCHAPv2 devices, 14,249 EAP-TLS/TEAP (70% migrated).
-
MSCHAPv2 weekly cadence — recurring Wednesday call established (first 04-22). Completed 2026-04-22.
-
MSCHAPv2 ownership matrix — sent in scoping email 4/24 with manager callouts (@Albert, @John). Completed 2026-04-24.
-
TCP Clocks deployment — Batch 1: 7 clocks validated (OUI 40:AC:8D). Batch 2 (06-09): 9 new MACs (OUI 40:AC:BD) added via ERS. 1 one-off reassigned. New switch deployed without RADIUS/AAA — clocks can’t authenticate. Switch onboarding template + 3 validation queries documented. ERS queries self-contained with
ersfunction. -
SRT Research VLAN — confirm roles with Tony Sun: Tony implementor, Evan tester. CAB approved 04-21.
-
Downtime Computers enforcement — draft ISE AuthZ rule: medigate_724 + Wireless = DenyAccess. Separate CR. d001: DC queries, audit CSVs (v1-v3), wireless violations report delivered 04-21.
-
Enterprise Linux 802.1X — standardize Shahab/Ding deployment (CISO priority). Overdue since 02-24. Blocked by nmcli cert fix. 06-15: Assembler build fixed (26 errors → 0). Segmentation document extension to 06-16.
d001 open linux-researchto work on it. -
Abnormal Security — CR-2026-05-07. Implemented 05-13. 06-09 update: Full policy review — 20-section EOP validation commands rebuilt, Hoxhunt SCL-1 investigation (intentional bypass confirmed), sclizer junk folder triage (~800 emails), Outlook reactions audit added, Connect-ExchangeOnline msalruntime fix documented. ESA migration expansion in progress — priority to move off ESA to full environment.
-
Team: Cox/William, Landeros/Jason, Rosado/Evan, Naranjo/Mauricio, Sandoval/Carlos
-
-
ASA VPN: Okta RADIUS → Entra SAML — (NEW 06-09) 5-phase migration plan built. ASA baseline captured (2 tunnel groups: CHLA_CORPORATE_USERS, CHLA_BYOD_USERS). 6 ISE policy screenshots. Tony Sun (ASA), Justin Halbmann (Entra/Okta), Evan (ISE). VPN cert expires 07-28. PDF deliverable ready. Share with team this week.
Tube System Upgrade (NEW — 06-01)
-
Tube System Upgrade — iTrack 3528165. 15x 10" TS stations need MAC addresses added to ISE identity group IoT_Onboard. MACs received from vendor (C8:1A:FE:20:xx:xx series). Station list spans ICU (CTICU, PICU, BMT, NICU, NICCU), ED, Surgery, Trauma, Pharmacy. Vendor contact: John Genest. Rationale: manufacturer no longer supports current system; failure risks delayed/missed patient care.
BMS Controller Segmentation (MIGRATED — 06-09)
-
BMS Controller Segmentation — Full migration from Principia LaTeX to d001. 12 partials, 5 Mermaid diagrams, 4 legacy PDFs, ISE screenshots.
d001 open bms-controller. Completed 2026-06-09.
BMS Device Inventory (NEW — 04-24)
-
BMS Device Inventory — 72 devices discovered across 37 switches (04-24). Profile-driven architecture (Claroty/Medigate). 16 queries built. Phase 0 complete. Next: cross-reference with Visio diagrams, classify by function, begin D2 diagrams. Cleanup: delete 4 orphaned test groups, migrate 4 retire-dACL devices, investigate 3 null-profile devices.
VNC Blocking (NEW — 05-11)
-
VNC Blocking — block and eliminate VNC enterprise-wide. Due mid-June 2026. Phase 0: discovery. January AQL query baseline to incorporate. Cross-reference BMS inventory for VNC-capable devices.
Investigations & Audits
-
Murus Portae (WAF) — Phase 0 discovery in progress. FMC cert expired. d001: DMZ NetScaler WAF investigation, zone map, architecture D2 diagrams (v1+v2 SVGs), FMC REST API reference guide, ops script. FMC API returning zero ACP rules — under investigation.
-
Firewall audit — FMC discovery inventory done (d001: fmc-discovery-2026-04-16). EtherChannel query, prefilter, policy assignments pending.
-
IoT Dr. Kim devices — RECURRING. All 4 MACs validated in IoT_iPSK_VLAN1620_Misc (04-24). v2 validation queries built with 7 deep analysis queries (group flapping, credential leakage, profile drift, NAS tracking, remediation timeline, deny audit, OUI scan). Revalidate — confirm no flapping since 04-24.
-
IoT device validation queries — v2 created with partials architecture, 16 queries across ERS/MnT/DataConnect/FMC. Completed 2026-04-24.
Stale Blockers (carried via carryover tracker)
-
k3s NAT verification — rule 170, 10.42.0.0/16 pod network (origin: 2026-03-09). 92 days. Blocks Wazuh indexer recovery → blocks SIEM visibility. Decide: test or defer to Q3.
-
Strongline Gateway VLAN fix — 8 devices wrong identity group (origin: 2026-03-16). 85 days. David Rukiza assigned — follow up on status.
Administrative
-
PeopleSoft — track time for current week
-
iTrack tickets — close open tickets
-
KQL library — build initial queries in codex + d001
-
Linux Research project — finalize and review
-
Tax filing 2025 (MFJ) — see encrypted case file in
data/d000/personal/for details and action items
P1 — Important
-
MSCHAPv2 action-item tracker — owner/status/next-steps per workstream
-
ISE admin MFA enforcement — recommendation tied to advisory (interim control pending Patch 10)
-
DMZ Migration — external services audit behind NetScaler. Linked to Murus Portae investigation.
-
Vocera/Wyse iTrack RCA — complete root cause report
-
GCC ISE Support — 3/4 nodes restored, PSN-04 deferred
-
Wazuh indexer recovery — blocked by k3s NAT (origin: 2026-03-09)
-
Vocera EAP-TLS Supplicant Fix (origin: 2026-03-12)
-
iPSK Manager HA — blocked by DB replication (Ben Castillo)
-
ISE 3.4 Migration — depends on Patch 10 completion first
-
Git history scrub — murus-portae-output.md + ise-analytics CSVs
-
Encrypt
prep-cmds-2026-04-15.adoc— plaintext committed to git -
ISE MnT Messaging Service — enable UDP syslog delivery (maintenance window needed)
Infrastructure (Personal)
-
Borg backups — test and validate on ALL systems (Razer, P16g, vault-01, bind-01, kvm-01, kvm-02)
-
Borg — verify backup script paths updated from dotfiles-optimus to dots-quantum
-
Borg — create initial archive for ThinkPad P16g if none exists
-
Libvirt VLAN hook debug on both KVMs
-
Te1/0/2 cable replacement and re-test
-
Vault Raft cluster — verify vault-01 rejoined
-
Fix EAP-TLS keyring/secrets issue on Razer workstation
Completed (confirmed — do not delete, archive only)
-
CR-2026-04-15 SRT Research VLAN — submitted to iTrack. Completed 2026-04-15.
-
CAB presentation 4/21 — SRT Research VLAN 233 → CHLA-Research. APPROVED. Completed 2026-04-21.
-
Downtime Computers wireless audit — 45 computers, 16 violating, v3 report delivered. Completed 2026-04-21.
-
Git identity fix — dots-quantum/git/.gitconfig email corrected. Completed 2026-04-21.
-
MSCHAPv2 10:30 meeting — next steps + ACL coordination. Completed 2026-04-17.
Service Requests (SR)
| SR# | Request | Requestor | Opened | Status |
|---|---|---|---|---|
3508542 |
Zoll cards connection issue |
— |
— |
STALE — verify in iTrack |
3508524 |
Disable dot1x on (2) network ports - 5th floor 3250 Wilshire (PXE-boot imaging issues) |
— |
— |
STALE — verify in iTrack (issues persisted after disable) |
3528165 |
Tube System Upgrade — 15 stations, MAC addresses for ISE IoT_Onboard identity group |
Genest, John (vendor contact) |
2026-06-01 |
NEW — MACs received, need ISE onboarding |
Incidents (INC)
| INC# | Priority | Description | Opened | SLA | Status |
|---|---|---|---|---|---|
1911859 |
— |
Strongline Gateways in Miscellaneous Subnet |
— |
— |
STALE — verify in iTrack (related to carryover P0) |
Change Requests - Emergency (ECAB)
| CR# | Description | Opened | Scheduled | Status |
|---|---|---|---|---|
No emergency changes |
Change Requests - Normal
| CR# | Description | Opened | Scheduled | Status |
|---|---|---|---|---|
No normal changes |
Change Requests - Scheduled/Standard
| CR# | Description | Opened | Window | Status |
|---|---|---|---|---|
No scheduled changes |
Change Requests - Root Cause / Post-Incident
| CR# | Description | Related INC | Opened | Status |
|---|---|---|---|---|
100451 |
Vocera Phones and Wyse devices went off network |
— |
— |
STALE — verify in iTrack |
Session Accomplishments (Claude Code)
Day-specific accomplishments here.
Personal
In Progress
| Project | Description | Status | Notes |
|---|---|---|---|
k3s Platform |
Production k3s cluster on kvm-01 |
Active |
Prometheus, Grafana, Wazuh deployed |
Wazuh Archives |
Enable archives indexing in Filebeat |
Active |
PVC fix pending |
kvm-02 Hardware |
Supermicro B deployment |
Active |
Hardware ready, RAM upgrade done |
Planned
| Project | Description | Target | Blocked By |
|---|---|---|---|
Vault HA (3-node) |
vault-02, vault-03 on kvm-02 |
Q2 2026 (slipped from Q1) |
kvm-02 deployment |
k3s HA (3-node) |
Control plane HA |
Q2 2026 (slipped from Q1) |
kvm-02 deployment |
ArgoCD GitOps |
k3s GitOps deployment |
After k3s stable |
— |
MinIO S3 |
Object storage for k3s |
After ArgoCD |
— |
Personal asset management (YAML + CLI + AsciiDoc) |
Q2 2026 |
Schema approved |
Active — Infrastructure
| Task | Details | Priority | Status | Due |
|---|---|---|---|---|
Wazuh agent deployment |
Deploy agents to all infrastructure hosts |
P2 |
Pending |
After archives fix |
k3s Platform |
Production k3s cluster on kvm-01 |
P1 |
In Progress |
— |
Wazuh Archives |
Enable archives indexing in Filebeat, PVC fix |
P1 |
In Progress |
— |
kvm-02 Hardware |
Supermicro B deployment, RAM upgrade done |
P1 |
In Progress |
— |
Active — Security & Encryption
| Task | Details | Priority | Status | Due |
|---|---|---|---|---|
Configure 4th YubiKey |
SSH FIDO2 keys |
P1 |
TODO |
— |
Cold storage M-DISC backup |
age-encrypted archives |
P1 |
TODO |
After YubiKey setup |
Active — Development & Tools
| Task | Details | Priority | Status | Due |
|---|---|---|---|---|
netapi Commercialization |
Go CLI rewrite with Cobra-style argument discovery, package for distribution |
P0 |
Active |
— |
Ollama API Service |
FastAPI (17 endpoints), productize — config audit, doc tools, runbook gen |
P0 |
Active |
— |
Shell functions (fe, fec, fef) |
File hunting helpers |
P3 |
TODO |
— |
Active — Documentation
| Task | Details | Priority | Status | Due |
|---|---|---|---|---|
D2 Catppuccin Mocha styling |
domus-* spoke repos (177 files total) |
P3 |
In Progress |
— |
Active — Financial
| Task | Details | Priority | Status | Due |
|---|---|---|---|---|
Amazon order history import |
Download CSV from Privacy Central → parse with awk → populate subscriptions tracker |
P1 |
Waiting |
Pending Amazon data export (requested 2026-04-04) |
Active — Education
| Task | Details | Priority | Status | Due |
|---|---|---|---|---|
No active education tasks — see education trackers |
Active — Personal & Life Admin
| Task | Details | Priority | Status | Due |
|---|---|---|---|---|
ThinkPad T16g Setup |
Arch install, stow dotfiles, Ollama stack, netapi dev env |
P0 |
Pending |
— |
P50 Arch to Ubuntu migration |
P2 |
In Progress |
— |
|
X1 Carbon Ubuntu installs |
2 laptops, LUKS encryption |
P2 |
In Progress |
— |
P50 Steam Test |
Test Flatpak Steam + apt cleanup of broken i386 packages |
P3 |
Pending |
— |
Documentation Sites
-
docs.domusdigitalis.dev - Private documentation hub
-
docs.architectus.dev - Public portfolio site
Education
Claude Code Mastery
| Resource | Details | Progress | Status |
|---|---|---|---|
Claude Code Full Course (4 hrs) |
Nick Saraev - YouTube comprehensive course |
26:49 / 4:00:00 |
IN PROGRESS |
Claude Code Certification |
Anthropic official certification (newly released) |
Not started |
GOAL |
Active Tracks (Focus)
-
Don Quijote - Primera Parte
Skills Mastery (Critical)
-
Regex Mastery - 10-module curriculum
-
AsciiDoc Docs - Documentation format
-
Antora Docs - Documentation pipeline
Certification Deadlines
-
CISSP - July 12, 2026 (10-week plan active — Week 1)
-
RHCSA 9 - Q3 2026 (after CISSP)
-
LPIC-1 - Renewal required (blocks LPIC-2)
Spanish C1 Certification Goals
| Certification | Provider | Target | Status | Strategy |
|---|---|---|---|---|
Instituto Cervantes / UNAM / Salamanca |
Q2 2026 |
ACTIVE |
Computer-based, faster results - take FIRST |
|
Q3/Q4 2026 |
PLANNED |
After SIELE success, harder exam |
||
2027 |
FUTURE |
Mastery level - requires extensive immersion |
| SIELE is computer-adaptive, results in 3 weeks. DELE is paper-based, results in 3-4 months. Do SIELE first to validate readiness. |
Don Quijote Writing Practice - DELE C1/C2 Initiative
Method:
-
Read chapter in original Spanish
-
Write personal analysis/understanding en espanol
-
AI review for grammar, vocabulary, register
-
Build comprehensive understanding of literary elements
Today’s Study
-
Focus: CISSP (41 days to July 12 exam — schedule exam today 06-01), MSCHAPv2 migration wrap-up
-
Secondary: RHCSA curriculum, Spanish SIELE C1
-
CISSP — Security & Risk Management (continuing). Schedule exam this afternoon.
-
RHCSA — continue curriculum phase
-
Spanish — Don Quijote reading + analysis (DTLA study day)
-
MSCHAPv2 — migration window closed 05-30, review final report
Regex Training (CRITICAL)
-
Status: 52 days carried over (since 2026-03-16)
-
Priority: After PeopleSoft, before Quijote
-
Session: Character classes, word boundaries
Infrastructure
Documentation Sites
| Site | URL | Status | Actions Needed |
|---|---|---|---|
Domus Digitalis |
Active |
Validate, harden, improve |
|
Architectus |
Active |
Public portfolio site - maintain |
HA Deployment Status
| System | Description | Status | Notes |
|---|---|---|---|
VyOS HA |
vyos-01 (kvm-01) + vyos-02 (kvm-02) with VRRP VIP |
✅ COMPLETE |
2026-03-07 - pfSense decommissioned |
BIND DNS HA |
bind-01 (kvm-01) + bind-02 (kvm-02) with AXFR |
✅ COMPLETE |
Zone transfer operational |
Vault HA |
Raft cluster (vault-01/02/03) |
✅ COMPLETE |
Integrated with PKI |
Keycloak Rebuild |
keycloak-01 corrupted, rebuild from scratch |
🔄 NEXT |
Priority P3 - SSO broken |
FreeIPA HA |
ipa-02 replica planned |
📋 PLANNED |
Linux auth redundancy |
AD DC HA |
home-dc02 replication |
📋 PLANNED |
Windows auth redundancy |
iPSK Manager HA |
ipsk-mgr-02 with MySQL replication |
📋 PLANNED |
PSK portal redundancy |
ISE HA |
PAN HA (ise-01 reconfigure) |
⏳ DEFERRED |
Wait until ise-02 stable |
ISE 3.5 Migration |
Upgrade path: 3.2p9 → 3.4 (P1) → 3.5 (target) |
📋 PLANNED |
After 3.4 Migration completes (Q2 2026) |
Single Points of Failure (CRITICAL)
| These systems have NO redundancy - outage impacts production. |
| System | Impact if Down | Mitigation |
|---|---|---|
ISE (ise-02) |
All 802.1X stops - wired and wireless auth fails |
ise-01 reconfiguration deferred until ise-02 stable |
Keycloak (keycloak-01) |
SAML/OIDC SSO broken (ISE admin, Grafana, etc.) |
NEXT PRIORITY - Rebuild runbook |
FreeIPA (ipa-01) |
Linux auth, sudo rules, HBAC fails |
ipa-02 replica planned |
AD DC (home-dc01) |
Windows auth, Kerberos, GPO fails |
home-dc02 replica planned |
iPSK Manager |
Self-service PSK portal unavailable |
ipsk-mgr-02 with MySQL replication planned |
Validation Tasks
| Task | Details | Status |
|---|---|---|
docs.domusdigitalis.dev validation |
Test all cross-references, search, rendering |
TODO |
docs.domusdigitalis.dev hardening |
HTTPS, CSP headers, security review |
TODO |
docs.architectus.dev validation |
Public site content review |
TODO |
Hub-spoke sync verification |
All components building correctly |
Ongoing |
Quick Commands
Git & GitHub CLI
gh repo create <name> --private --source . --remote origin --push
gh repo clone EvanusModestus/PowerShell ~/atelier/_projects/work/PowerShell
gh repo clone defaults to SSH. If key is passphrase-protected, load agent first: eval "$(ssh-agent -s)" && ssh-add ~/.ssh/id_ed25519_github
|
for repo in ~/atelier/_bibliotheca/domus-*/ ~/atelier/_projects/personal/domus-*/; do
[ -d "$repo/.git" ] || continue
name=$(basename "$repo")
git -C "$repo" log --since="2026-04-06" --until="2026-04-07" --format="%h %aI %s" 2>/dev/null |
awk -v r="$name" '{print r, $0}'
done
git log --oneline -- $(find . -name "*.adoc" -type f -newermt "$(date +%F)")
git restore --staged data/d001/api/ise-dataconnect/output/output-2026-04-24
Safe — removes from staging area only. Working tree is untouched. Use when you accidentally git add a plaintext or output file.
gh CLI — repo discovery and filtering
gh repo list --limit 100 --json name,description \
| jq -r '.[] | select(.name | test("domus|antora|asciidoc"; "i")) | "\(.name)\t\(.description)"'
gh repo list --limit 100 --json name,description,updatedAt \
| jq -r 'sort_by(.updatedAt) | reverse | .[:20] | .[] | "\(.updatedAt[:10])\t\(.name)\t\(.description)"'
gh repo list --limit 100 --json name,diskUsage \
| jq -r '.[] | "\(.diskUsage)\t\(.name)"' | sort -rn | head -10
gh repo clone EvanusModestus/<repo-name> ~/atelier/_bibliotheca/<repo-name>
find & grep
find . -name "*.adoc" -type f -newermt "$(date +%F)" | sort
-mtime 0 means "last 24 hours", not "today". -newermt "$(date +%F)" compares against midnight — exact.
|
find . -iname "*mschap*" -type f | sort
find . -type f \( -iname "*ise*" -o -iname "*mschap*" \) | sort
find . -type f -iregex '.*\(ise\|mschap\).*'
find . -type f -iname "*meeting*" \
-not -path "*/node_modules/*" \
-not -path "*/.git/*" \
-not -path "*/build/*"
find .drafts -type f -printf '%T@ %Tc %p\n' | sort -rn | awk '{$1="";print}' | head -3
grep -rl "pattern" . --include="*.adoc" # file count (which files)
grep -rn "pattern" . --include="*.adoc" # line matches (every occurrence)
grep -rc "pattern" . --include="*.adoc" | grep -v ':0$' # match count per file
grep -rn -E 'git init|gh repo create' docs/ --include='*.adoc' -B2 -A2
Search codex by content — which files contain a command?
find docs/modules/ROOT/examples/codex/powershell -type f -name "*.adoc" \
-exec grep -l 'Get-Process\|Start-Process\|pipeline\|Where-Object' {} \;
Pattern: find -exec grep -l returns only filenames with matches — like grep -rl but with find’s `-type f -name filtering. Use \| for OR in grep basic regex. Swap the pattern for any cmdlet or keyword to locate coverage across the codex.
find docs/modules/ROOT -name "powershell" -type d \
-exec sh -c 'echo "$1: $(find "$1" -type f | wc -l) files"' _ {} \;
for f in $(find docs/modules/ROOT/examples/codex/powershell -name "*.adoc" -type f); do
base=$(basename "$f")
dir_parent=$(basename $(dirname "$f"))
grep -rq "$dir_parent/$base" docs/modules/ROOT/pages/codex/powershell/ \
docs/modules/ROOT/examples/codex/powershell/*.adoc 2>/dev/null \
|| echo "ORPHAN: $f"
done
find → grep → open in nvim
nvim $(find -path '*oauth*' -name '*.adoc' -type f \
-exec grep -l 'timeout\|expire\|reconfig\|token' {} \;)
Command substitution $(…) feeds all matches as arguments to nvim — opens every hit as a buffer. :bn/:bp to cycle, :ls to list. One file? Opens directly. Five files? All loaded, ready to navigate.
nvim $(find docs/modules/ROOT -name '*.adoc' -type f \
-exec grep -l 'token.*expire\|oauth.*refresh' {} \;)
find -path '*oauth*' -name '*.adoc' -type f \
-exec grep -l 'timeout\|expire' {} \; \
-exec nvim {} \;
Trailing \| in grep patterns matches empty string — every file matches. Always end with a term, not a pipe: 'timeout\|expire\|token' not 'timeout\|expire\|token\|'.
|
Trace Antora partial inclusion chains
grep -rl 'commands/shell' docs/modules/ROOT/partials/
grep -rl 'quick-commands' docs/modules/ROOT | wc -l
file="commands/shell"
grep -rl "$file" docs/modules/ROOT/partials/ | while read f; do
parent=$(basename "$f" .adoc)
echo "$file -> $parent"
grep -rl "$parent" docs/modules/ROOT/pages/ | while read p; do
echo " -> $(basename "$p")"
done
done
Pattern: grep -rl finds which files contain the string. Chain two passes — first finds the assembler partial, second finds every page that includes it. Works for any partial in the Antora include hierarchy.
Multi-pattern file search — worklog partial discovery
find docs/modules/ROOT -name "*urgent.adoc*" -type f
find docs/modules/ROOT -name "*morning.adoc*" -type f
find docs/modules/ROOT -type f -regextype posix-extended \
-regex '.*(urgent|morning|work-chla|personal|education|infrastructure|quick-commands|related)\.adoc' \
| sort
Pattern: -regextype posix-extended enables | alternation without escaping. One process, one sort — versus 8 separate finds. The sort deduplicates visually and groups by path.
find docs/modules/ROOT -type f -name "*.adoc" \
| grep -E 'urgent|morning|work-chla|personal|education|infrastructure|quick-commands|related'
Trade-off: the pipeline version is more readable but spawns two processes. The regex version is a single find — faster on large trees, same result.
Cross-repo literary term search — bibliotheca-wide discovery
When searching for a term across the entire _bibliotheca (multiple repos, mixed file types), these patterns escalate from narrow to broad.
grep -rn --include='*.adoc' -c 'sanchuelo' . | grep -v ':0$'
grep -rl --include='*.adoc' -i 'sanchuelo' ~/atelier/_bibliotheca/ | sort
grep -rn --include='*.adoc' -i -B1 -A1 'sanchuelo' ~/atelier/_bibliotheca/domus-captures/
grep -rl -i 'sanchuelo' ~/atelier/_bibliotheca/ --include='*.txt' --include='*.adoc' | sort
find ~/atelier/_bibliotheca/ -type f \( -name '*.adoc' -o -name '*.txt' \) -print0 \
| xargs -0 grep -li 'sanchuelo' | sort
grep -rl -i 'sanchuelo' ~/atelier/_bibliotheca/ --include='*.adoc' --include='*.txt' | xargs nvim
Pattern escalation: #1 confirms the term exists and where. #2 expands to all repos. #3 shows context without opening files. #4 adds plain text sources (Quijote .txt originals). #5 is the safe version for automation. #6 opens everything for editing.
Trade-off: grep -r --include is faster for known file types. find | xargs grep is safer for paths with spaces and more extensible (add -name '*.md' etc.). For literary searches across the bibliotheca, #4 or #5 is usually the right starting point — the source texts are .txt, not .adoc.
Search daily notes — find commands, builds, and patterns across worklogs
Source: 2026-06-22 — searching for build-antora-page invocations and theme usage across partials
# Find files — filenames only
grep -rl 'build-antora-page' docs/modules/ROOT/partials/worklog/
# 3 lines of context around each hit
grep -rn -C3 'build-antora-page' docs/modules/ROOT/partials/worklog/
grep -rPn 'theme\s+(light-cyan|catppuccin|mocha)' docs/modules/ROOT/partials/
find docs/modules/ROOT/partials/worklog/daily-notes -name '*.adoc' \
-exec grep -l 'build-antora-page\|build-adoc\|--theme\|pdf' {} \;
find docs/modules/ROOT/partials/worklog/daily-notes -name '*.adoc' \
-exec awk '/build-antora-page|build-adoc|--theme/{print FILENAME": "$0}' {} \;
find docs/modules/ROOT/partials -name '*.adoc' -exec \
awk '/^\[source,bash\]/{block=1; buf=""} \
block{buf=buf"\n"$0} \
/^----$/ && block>1{if(buf~/build-antora-page/) print FILENAME":"buf; block=0} \
block{block++}' {} \;
The awk state machine: [source,bash] sets block=1 and starts buffering. Each line appends to buf. When the closing ---- arrives (and block>1 to skip the opening fence), check if the buffer contains the target command. If yes, print filename + entire block. Reset. One pass, arbitrary block size, no temp files.
find docs/modules/ROOT/partials/worklog -name '*.adoc' -exec \
sed -n '/\[source,bash\]/,/^----$/{/build-antora-page/p}' {} +
grep -rn 'build-antora-page\|build-adoc' data/ --include='*.adoc'
grep -rn 'build-antora-page' docs/modules/ROOT/partials/worklog/daily-notes/ \
| awk -F: '{print $1, $3}' | sort
Pattern escalation: grep -rl → "where is it". grep -rn -C3 → "what’s around it". find -exec awk → "extract the structured block". sed address ranges → "print between delimiters". Each tool has a different affordance — grep finds, awk extracts structure, sed filters ranges, find -exec scales across trees.
Email thread analysis — extract people, dates, commitments, silence
grep -P '(@\w+|^From:.*<)' comms.adoc
grep -nP '\d{1,2}/\d{1,2}/\d{2,4}|20\d{2}-\d{2}-\d{2}' comms.adoc
grep -niP '(I can |I will |I.ll |we will |we.ll )' comms.adoc
grep -niP '(\?|need to confirm|need to validate|TBD|pending)' comms.adoc
comm — set difference (who hasn’t replied)
# All recipients
grep -oP '<\K[^>]+' comms.adoc | sort -u > /tmp/all-recipients
# All senders
grep -P '^From:' comms.adoc | grep -oP '<\K[^>]+' | sort -u > /tmp/replied
# Who's silent — follow-up targets
comm -23 /tmp/all-recipients /tmp/replied
comm -23 outputs lines only in file 1 (recipients not in senders). Requires sorted input. grep -oP '<\K[^>]+' uses PCRE lookbehind — match < but don’t include it, capture until >.
Sort find results by modification time (newest first)
find discovers files but has no sort. Chain -printf with sort to order by mtime.
awk '{print $2}' truncates filenames with spaces — Familia Romana_ Lingva… becomes Familia. Always use the null-safe or sub() variants below for real data.
|
# Sort by mtime, strip epoch prefix — handles spaces in filenames
find ~/Downloads -maxdepth 1 -name '*.pdf' -printf '%T@ %p\n' | sort -rn | awk '{sub(/^[^ ]+ /,""); print}'
sub(/[ ]+ /,"") removes everything up to and including the first space (the epoch). {print $2} would split on every space — fatal for Familia Romana_ Lingva Latina.
# ISO 8601 timestamps — readable and lexicographically sortable
find ~/Downloads -maxdepth 1 -name '*.pdf' -printf '%T+ %p\n' | sort -r | head -20
%T+ renders YYYY-MM-DD+HH:MM:SS — no epoch math needed, still sorts correctly as text.
# Null-delimited: survives any filename (newlines, quotes, unicode)
find ~/Downloads -maxdepth 1 -name '*.pdf' -printf '%T@\t%p\0' | sort -zrn | awk -v RS='\0' -F'\t' '{print $2}'
-printf '%T@\t%p\0' — tab separates epoch from path, null terminates. sort -z sorts null-delimited records. awk -v RS='\0' -F'\t' reads null-terminated, splits on tab — $2 is now the full path regardless of spaces.
# GNU stat equivalent — works where -printf is unavailable
find ~/Downloads -maxdepth 1 -name '*latin*' -exec stat --format='%Y %n' {} + | sort -rn | awk '{sub(/^[^ ]+ /,""); print}'
-exec … {} + batches all files into one stat call (faster than \;). On macOS, use stat -f '%m %N' instead of --format='%Y %n'.
File intelligence — size, type, duplicates, age
Beyond finding files — interrogating them.
# Size in bytes (-printf %s), human-readable via numfmt
find ~/Downloads -type f -printf '%s\t%p\n' | sort -rn | head -10 | numfmt --to=iec --field=1
numfmt --to=iec --field=1 converts the first field from bytes to K/M/G. sort -rn on raw bytes is exact — ls -lhS rounds and sometimes mis-sorts.
# Files sharing a byte count — likely duplicates (confirm with md5sum)
find ~/Downloads -type f -printf '%s %p\n' | awk '{seen[$1]++; files[$1]=files[$1] "\n " $0} END {for (s in seen) if (seen[s]>1) print files[s]}'
# md5sum only files with duplicate sizes (two-pass: fast then precise)
find ~/Downloads -type f -printf '%s\n' | sort | uniq -d | while read -r size; do
find ~/Downloads -type f -size "${size}c" -exec md5sum {} +
done | sort | uniq -w32 -D
Two-pass: first find duplicate sizes (cheap), then md5sum only those (expensive). uniq -w32 -D compares first 32 chars (the hash) and prints all duplicates.
# Count files by MIME type (not extension — extensions lie)
find ~/Downloads -type f -exec file --mime-type -b {} + | sort | uniq -c | sort -rn
file --mime-type -b reports actual content type. -b suppresses filename. A .pdf that’s really text/html is a failed download.
# Files not accessed in 30 days — candidates for cleanup
find ~/Downloads -maxdepth 1 -type f -atime +30 -printf '%A+ %s\t%p\n' | sort | numfmt --to=iec --field=2
-atime 30` = access time older than 30 days. `-printf '%A' shows last access. Useful for Downloads cleanup without deleting something you just renamed.
# Which subdirectories consume the most space?
find . -maxdepth 1 -type d -exec du -sh {} + 2>/dev/null | sort -rh | head -20
Batch operations — rename, move, transform
# Dry run — show what would change (remove echo to execute)
find ~/Downloads -maxdepth 1 -type f -name '* *' -print0 | while IFS= read -r -d '' f; do
dir=$(dirname "$f")
base=$(basename "$f" | tr ' ' '-' | tr '[:upper:]' '[:lower:]')
echo mv "$f" "$dir/$base"
done
IFS= read -r -d '' — the holy trinity for null-safe filename reading. IFS= prevents whitespace trimming. -r prevents backslash interpretation. -d '' reads until null.
# Sort Downloads chaos into folders by type
find ~/Downloads -maxdepth 1 -type f -print0 | while IFS= read -r -d '' f; do
ext="${f##*.}"
case "$ext" in
pdf|epub) dest="books" ;;
jpg|png|svg) dest="images" ;;
sh|py|rb) dest="scripts" ;;
*) dest="other" ;;
esac
mkdir -p ~/Downloads/"$dest"
echo mv "$f" ~/Downloads/"$dest"/
done
${f##.} — parameter expansion: strip longest match of . from front, leaving only the extension. No basename or awk needed.
# Convert all epubs in a directory to asciidoc via pandoc
find . -name '*.epub' -type f -exec sh -c '
for epub; do
adoc="${epub%.epub}.adoc"
pandoc -f epub -t asciidoc "$epub" -o "$adoc" \
&& printf " → %s (%s lines)\n" "$adoc" "$(wc -l < "$adoc")" \
|| printf " ✗ failed: %s\n" "$epub"
done
' _ {} +
-exec sh -c '…' _ {} + — batch mode. _ fills $0 (script name, discarded). All matched files become $1, $2, … iterated by for epub. One sh invocation, not one per file.
xargs power patterns
# Checksum all PDFs in parallel (4 processes)
find ~/Downloads -name '*.pdf' -print0 | xargs -0 -P4 md5sum
-P4 runs 4 md5sum processes simultaneously. -print0 | xargs -0 is the null-safe pipeline — no filename can break it.
# Compare files pairwise with diff
find . -name '*.adoc' -print0 | xargs -0 -n2 diff --brief
-n2 feeds two arguments per invocation. Useful for pairwise comparisons, copy operations (-n2 with cp), or any command taking exactly two args.
# Backup every config file: cp <file> <file>.bak
find /etc -maxdepth 1 -name '*.conf' -print0 | xargs -0 -I{} cp {} {}.bak
-I{} replaces {} with each filename. Slower than + batching (one cp per file) but necessary when the filename must appear in a specific position.
Process substitution — diff without temp files
# What files exist in study-A but not study-B?
diff <(find data/d000/education/ciceron-study -type f -name '*.adoc' | sort) \
<(find data/d000/education/latin-study -type f -name '*.adoc' | sort)
<(cmd) creates a file descriptor from command output. diff sees two "files" — no temp files created, no cleanup needed.
# Side-by-side: file type census of two directories
paste <(find dir1 -type f -exec file --mime-type -b {} + | sort | uniq -c | sort -rn) \
<(find dir2 -type f -exec file --mime-type -b {} + | sort | uniq -c | sort -rn)
awk, sed, jq
awk — field extraction
awk '{print $2}' file.txt
awk -F: '{print $1, $3}' /etc/passwd
awk '/\[source,json\]/{getline; if ($0 ~ /^----/) {p=1; next}} p && /^----/{p=0; next} p' file.adoc
awk '{printf "%-30s %s\n", $1, $2}' file.txt
sed — stream editing
# Before
awk 'NR==73' /etc/ssh/sshd_config
# Change
sed -i '73s/#GSSAPIAuthentication no/GSSAPIAuthentication yes/' /etc/ssh/sshd_config
# After
awk 'NR==73' /etc/ssh/sshd_config
sed -n '10,20p' file.txt
sed — line-targeted replacement (verify-before / change / verify-after)
# 1. LOCATE: find the line number
grep -n 'adoc-pdf' zsh/.zshrc
# 2. VALIDATE: read the exact line before changing
awk 'NR==1760' zsh/.zshrc
# 3. CHANGE: target by line number — only hits that line
sed -i '1760s/alias adoc-pdf=/alias build-adoc=/' zsh/.zshrc
# 4. VERIFY: confirm change AND check for collateral
grep -n 'build-adoc\|adoc-pdf' zsh/.zshrc
Without the line number prefix (1760s/), sed replaces every match in the file — a shotgun. With it, surgical. The line number comes from grep -n.
awk 'NR==1218 || NR==1760' zsh/.zshrc
# grep found the error at line 44164 — read 50 lines of context
awk 'NR>=44160 && NR<=44210' session-dump.adoc
No head | tail chains. No sed -n '44160,44210p'. One awk, two numbers.
grep -oP with \K — value extraction from key-value logs
# ISE syslog — extract failure reasons
grep -oP 'FailureReason=\K[^,;]+' /var/log/syslog | sort | uniq -c | sort -rn
# ISE — extract MAC addresses
grep -oP 'Calling-Station-ID=\K[0-9A-Fa-f:.-]+' /var/log/syslog | sort -u
# ISE — extract NAS IPs
grep -oP 'NAS-IP-Address=\K[0-9.]+' /var/log/syslog | sort -u
# ISE — extract device names
grep -oP 'NetworkDeviceName=\K[^,;]+' /var/log/syslog | sort -u
\K resets the match start — everything before \K is required context but excluded from output. [^,;]+ captures until the next delimiter. Pipe to sort -u for unique, sort | uniq -c | sort -rn for counted frequency.
# Generic form — works for any key=value log format
grep -oP 'FIELD_NAME=\K[^,;]+' logfile | sort | uniq -c | sort -rn | head -20
jq — JSON processing
curl -s localhost:8080/stats | jq '.stats.total_files'
jq '.results[] | select(.category == "standards")' response.json
jq -r '.[] | [.title, .path] | @tsv' response.json | column -t -s $'\t'
gh api "repos/EvanusModestus/domus-captures/commits?path=docs/&per_page=10" |
jq -r '.[] | "\(.commit.author.date[:10]) \(.sha[:7]) \(.commit.message | split("\n")[0])"'
Shell Patterns
xargs — when the next command reads arguments, not stdin
| Next command reads… | Use |
|---|---|
stdin ( |
pipe directly |
arguments ( |
|
-I{} placeholdermkdir -p /tmp/adoc-backup-$(date +%F) && \
find . -name "*.adoc" -type f -newermt "$(date +%F)" | \
xargs -I{} cp {} /tmp/adoc-backup-$(date +%F)/
-P4 runs 4 at a timefind .drafts -name "*.adoc" -type f | xargs -P4 -I{} asciidoctor -o /dev/null {}
find . -name "*.adoc" -type f -print0 | xargs -0 wc -l
Process substitution — <(cmd) treats output as a file
diff <(grep '|' partials/trackers/work/adhoc/carryover.adoc | head -20) \
<(git show HEAD~1:partials/trackers/work/adhoc/carryover.adoc | grep '|' | head -20)
diff <(find docs/modules/ROOT/pages/projects/chla/mschapv2-migration -name "*.adoc" -type f | sort) \
<(grep -oP 'mschapv2-migration/[^[]+\.adoc' docs/modules/ROOT/nav.adoc | sort)
Command substitution — embed output as arguments
nvim "$(find data/ -name '*.adoc' -type f -printf '%T@ %p\n' | sort -rn | awk 'NR==1{print $2}')"
wc -l $(find docs/modules/ROOT -path '*mschapv2*' -name '*.adoc' -type f)
Conditional execution — capture, test, act
files=$(find .drafts -name 'in*' -type f) && [ -n "$files" ] && nvim $files
files=$(grep -rl '\[ \]' .drafts/*.adoc) && [ -n "$files" ] && nvim $files
grep -q 'TODO\|FIXME\|\[ \]' "$file" && nvim "$file"
Pattern: $(capture) → [ -n ] tests non-empty → && only proceeds if true.
grep -q is the idempotent guard — run repeatedly, only opens when there’s work.
Decrypt and open — find .age, decrypt, nvim in one shot
files=$(find . -name "*tcp-clock*.age" -type f) && \
[ -n "$files" ] && echo "$files" | xargs -I{} decrypt-file {} && \
nvim $(echo "$files" | sed 's/\.age$//')
Pattern: find .age only (never tries plaintext), sed derives the decrypted path, guard prevents empty nvim. Change the glob to match any project.
tee_clean — color on screen, clean text in file
tee_clean() {
tee >(sed 's/\x1b\[[0-9;]*m//g' > "$1")
}
# Color output on terminal, stripped in file
jq -C '.' data.json | tee_clean output.json
xq -C '.' data.xml | tee_clean output.json
# Wrap a whole block
{
echo "=== Summary ==="
jq -C '.[] | .name' data.json
} | tee_clean summary.txt
The >(cmd) is process substitution — tee writes to stdout AND to the subshell pipe. sed strips ANSI escape sequences (\x1b\[[0-9;]*m) before they hit the file.
Dependency check — verify toolchain in one shot
for cmd in asciidoctor asciidoctor-pdf pandoc rouge d2 mmdc age; do
printf "%-20s %s\n" "$cmd" "$(command -v $cmd >/dev/null 2>&1 && echo 'OK' || echo 'MISSING')"
done
Pattern: command -v checks if binary exists on PATH. >/dev/null 2>&1 suppresses output — we only care about exit code. Swap the tool list for any project’s dependencies.
printf safety — dashes as data, not options
--- as invalid optionprintf '---\n\n'
--- as dataprintf '%s\n\n' '---'
Kill stuck SSH sessions
lsof -i TCP -n -P | awk '/ssh.*ESTABLISHED/ {print $2, $9}'
lsof -i TCP -n -P | awk '/ssh.*kvm-01.*ESTABLISHED/ {print $2}' | sort -u | xargs kill
lsof -i TCP -n -P | awk '/ssh.*ESTABLISHED/ {print $2}' | sort -u | xargs kill
lsof -i TCP -n -P lists all TCP connections. awk filters for SSH + ESTABLISHED, prints only the PID ($2). sort -u deduplicates (multiple file descriptors per process). xargs kill sends SIGTERM to each.
File Descriptors & Redirection
The three file descriptors
| FD | Name | Purpose |
|---|---|---|
0 |
stdin |
input to the command |
1 |
stdout |
normal output (valid results) |
2 |
stderr |
error messages |
Split stdout and stderr into separate files
find / -name "*.conf" 1>results.txt 2>errors.txt
Suppress errors — 2>/dev/null
find / -name "*.conf" 2>/dev/null
Merge stderr into stdout — 2>&1
command 2>&1 | grep "pattern"
This sends both stdout and stderr through the pipe. Without 2>&1, only stdout reaches grep — errors print to the terminal and bypass the pipeline.
Heredoc patterns
cat <<'EOF'
Line 1
Line 2
EOF
git commit -m "$(cat <<'EOF'
feat: add new feature
Multi-line description here.
EOF
)"
API & curl/jq
domus-api — Documentation System REST API
cd ~/atelier/_projects/personal/domus-api && uv run uvicorn domus_api.main:app --host 0.0.0.0 --port 8080
curl -s localhost:8080/ | jq
curl -s 'localhost:8080/search?q=mandiant' | jq
curl -s 'localhost:8080/search?q=mandiant' | jq '.results[] | {path, title, match_count}'
curl -s 'localhost:8080/pages?category=standards' | jq
curl -s localhost:8080/attributes | jq
GitHub API
gh search code "vault seal" --owner EvanusModestus --json repository,path,textMatches |
jq '.[] | {repo: .repository.full_name, file: .path, match: .textMatches[].fragment}'
gh api 'repos/EvanusModestus/domus-captures/git/trees/main?recursive=1' |
jq '[.tree[] | select(.path | endswith(".adoc"))] | length'
Domus Workflows
Read content from terminal (meeting-ready)
bat docs/modules/ROOT/pages/2026/04/WRKLOG-$(date +%Y-%m-%d).adoc
bat docs/modules/ROOT/partials/trackers/work/priorities/current.adoc
bat docs/modules/ROOT/partials/trackers/work/adhoc/carryover.adoc
bat docs/modules/ROOT/partials/projects/mandiant-remediation/summary.adoc
Search and discovery
grep -rl "MSCHAPv2" docs/modules/ROOT/ --include="*.adoc" | sort
grep -rn "pattern" docs/modules/ROOT/partials/codex/ --include="*.adoc" -B1 -A3
ls -1 docs/modules/ROOT/pages/2026/04/WRKLOG-*.adoc
Tracker aging — calculate days from origin
echo $(( ($(date +%s) - $(date -d "2026-03-09" +%s)) / 86400 ))
Encrypted data access (d001)
age --decrypt -i ~/.secrets/.metadata/keys/master.age.key \
data/d001/projects/mandiant-remediation/findings-status-2026-04-16.adoc.age \
| bat --language asciidoc
for d in data/d001/projects/*/; do
total=$(find "$d" -type f | wc -l)
plain=$(find "$d" -type f ! -name '*.age' ! -name 'README.adoc' ! -name '.gitkeep' ! -name '*.py' | wc -l)
printf "%-25s %s files %s plaintext\n" "$(basename "$d")" "$total" "$plain"
done
d000 study builds
for d in p1-cap-03{7,8,9}; do
for f in data/d000/education/quijote-study/notas/$d/*.adoc; do
d000 build "$d/$(basename "$f" .adoc)" html --variant light-cyan
done
done
d000 build p1-cap-038/texto-anotado html --variant light-cyan
d000 build p1-cap-038/texto-anotado pdf --theme light-cyan
for d in p1-cap-03{7,8,9}; do
for f in data/d000/education/quijote-study/notas/$d/*.adoc; do
d000 build "$d/$(basename "$f" .adoc)" pdf --theme light-cyan
done
done
firefox data/d000/education/quijote-study/notas/p1-cap-03{7,8,9}/output/*.html &
firefox data/d000/education/quijote-study/notas/p1-cap-03{7,8,9}/output/*.pdf &
lp data/d000/education/quijote-study/notas/p1-cap-03{7,8,9}/output/*.pdf
d000 build annotated-text pdf --theme light-cyan
d000 build lpl-study/notas/texto-anotado pdf --theme light-cyan
d000 build de-oratore/libro-i/texto-anotado html --variant light-cyan
Available themes
ls ~/atelier/_bibliotheca/domus-asciidoc-build/themes/pdf/ | sed 's/-theme\.yml//'
# base blue burgundy catppuccin creative dark don-quijote green
# learning light-cyan navy operations orange purple reference royal
~/atelier/_bibliotheca/domus-asciidoc-build/docinfo/compose.sh --list
# light dark catppuccin royal light-cyan
ISE & Network Ops
ISE ERS API — endpoint CRUD
export ISE_HOST="{ise-ip}" ISE_USER="admin" ISE_PASS="$(gopass show -o ise/admin)"
curl -sk "https://$ISE_HOST:{ise-ers-port}/ers/config/identitygroup" \
-H "Accept: application/json" -u "$ISE_USER:$ISE_PASS" | jq '.SearchResult.resources[].name'
curl -sk "https://$ISE_HOST:{ise-ers-port}/ers/config/endpoint?filter=mac.EQ.AA:BB:CC:DD:EE:FF" \
-H "Accept: application/json" -u "$ISE_USER:$ISE_PASS" | jq '.SearchResult.total'
Certificate inspection
openssl x509 -in {cert-dir}/client.pem -text -noout | head -30
openssl x509 -in {cert-dir}/client.pem -enddate -noout
Network diagnostics
ss -tlnp | grep -E ':{port-https}|:{port-ssh}|:{port-ldaps}'
nc -zv {ise-ip} {ise-ers-port}
dig {ise-hostname} +short
ISE eval rotation — backup & restore
# SSH to ISE
ssh admin@ise-02.inside.domusdigitalis.dev
# Verify NAS repo
show repository nas-01
# Get encryption key (on workstation)
dsource d000 dev/storage
echo $ISE_BACKUP_KEY
# Run backup
backup pre-rotation-2026-06 repository nas-01 ise-config encryption-key plain <KEY>
ssh admin@ise-02.inside.domusdigitalis.dev
show repository nas-01
configure terminal
repository nas-01
url nfs://10.50.1.70:/volume1/ise_backups
exit
restore <backup-filename> repository nas-01 encryption-key plain <KEY>
VyOS — VRRP & VLAN inspection
show vrrp
show configuration commands | grep vrrp | grep 'address'
show configuration commands | grep 'firewall zone' | grep 'member'
show dhcp server leases
show arp
show interfaces
CUPS printing — validation & setup
command -v lpstat && echo "CUPS present" || echo "CUPS not installed"
lpstat -r # scheduler running?
lpstat -p -d # printers + default
sudo systemctl enable --now cups # start + persist
lpinfo -v # available backends/URIs
lpinfo -m | grep -i <brand> # available drivers
sudo lpadmin -p <name> -v <uri> -m everywhere -E
lpoptions -d <name>
lp file.pdf # default printer
lp -d <name> -o sides=two-sided-long-edge file.pdf
PowerShell (from zsh)
All PowerShell commands run inside pwsh -NoLogo -Command '…' from zsh. Running them bare fails — zsh interprets $, |, () as shell syntax.
|
Process management
pwsh -NoLogo -Command 'Get-Process | Sort-Object WorkingSet64 -Descending |
Select-Object -First 5 ProcessName, Id,
@{N="MB";E={[math]::Round($_.WorkingSet64/1MB)}} | Format-Table'
pwsh -NoLogo -Command 'Get-Process | Where-Object {$_.ProcessName -like "*teams*"} | Stop-Process'
pwsh -NoLogo -Command 'Start-Process "ms-teams"'
Export to JSON (pipe to jq)
pwsh -NoLogo -Command 'Get-Process | Sort-Object WorkingSet64 -Descending |
Select-Object -First 5 ProcessName, Id,
@{N="MB";E={[math]::Round($_.WorkingSet64/1MB)}} | ConvertTo-Json' | jq '.'
Never pipe Format-Table into ConvertTo-Json — it produces layout metadata, not data. Select-Object first, then ConvertTo-Json.
|
Wi-Fi management (netsh)
netsh wlan disconnect interface="Wi-Fi"
netsh wlan show networks mode=bssid
netsh wlan connect name="CHLA-Remote" interface="Wi-Fi"
SSH from PowerShell
ssh evan@modestus-razer.inside.domusdigitalis.dev
WSL ↔ Windows — Cross-Environment Commands
From zsh (WSL) — control Windows
pwsh -NoLogo -Command 'Get-Date'
pwsh -NoLogo -Command "$(cat <<'PS'
$procs = Get-Process | Where-Object { $_.WorkingSet64 -gt 100MB }
$procs | Sort-Object WorkingSet64 -Descending |
Select-Object ProcessName, Id, @{N="MB";E={[math]::Round($_.WorkingSet64/1MB)}} |
Format-Table -AutoSize
PS
)"
# Open in default Windows app
wslview /mnt/c/Users/erosado/Documents/report.pdf
# Open Explorer to current WSL directory
explorer.exe .
# Open specific Windows path
explorer.exe 'C:\Users\erosado\Downloads'
# Pipe anything to Windows clipboard
cat file.txt | clip.exe
# Copy a command's output
pwsh -NoLogo -Command 'Get-TransportRule | Format-List Name, State' | clip.exe
# Windows C: drive is at /mnt/c
ls /mnt/c/Users/erosado/Downloads/
# Copy from Windows to WSL
cp /mnt/c/Users/erosado/Downloads/report.pdf ~/atelier/
# Watch a Windows directory for new files
find /mnt/c/Users/erosado/Downloads -maxdepth 1 -mmin -5 -type f -printf '%T+ %p\n' | sort -r
From PowerShell — control WSL
wsl -e bash -c 'grep -rn "Ghost-Sender" ~/atelier/_bibliotheca/domus-captures/docs/'
$result = wsl -e bash -c 'git -C ~/atelier/_bibliotheca/domus-captures log --oneline -5'
$result
Process Management — Windows Side
pwsh -NoLogo -Command '
Get-Process | Sort-Object WorkingSet64 -Descending |
Select-Object -First 20 ProcessName, Id,
@{N="MB";E={[math]::Round($_.WorkingSet64/1MB)}},
@{N="CPU(s)";E={[math]::Round($_.CPU,1)}},
@{N="Handles";E={$_.HandleCount}} |
Format-Table -AutoSize'
pwsh -NoLogo -Command 'Get-Process | Where-Object { $_.ProcessName -like "*teams*" } |
Select-Object ProcessName, Id, @{N="MB";E={[math]::Round($_.WorkingSet64/1MB)}} |
Format-Table -AutoSize'
pwsh -NoLogo -Command 'Stop-Process -Name "Teams" -Force -ErrorAction SilentlyContinue'
pwsh -NoLogo -Command 'Stop-Process -Id 12345 -Force'
pwsh -NoLogo -Command 'Get-NetTCPConnection -State Listen |
Select-Object LocalAddress, LocalPort, OwningProcess,
@{N="Process";E={(Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName}} |
Sort-Object LocalPort | Format-Table -AutoSize'
pwsh -NoLogo -Command 'Get-NetTCPConnection -LocalPort 8080 -ErrorAction SilentlyContinue |
Select-Object LocalAddress, LocalPort, RemoteAddress, State,
@{N="Process";E={(Get-Process -Id $_.OwningProcess).ProcessName}}'
Services — Windows Side
pwsh -NoLogo -Command 'Get-Service | Where-Object { $_.Status -eq "Running" } |
Sort-Object DisplayName | Format-Table Name, DisplayName, Status -AutoSize'
pwsh -NoLogo -Command 'Get-Service -Name "WinRM" | Format-List Name, DisplayName, Status, StartType'
Restart-Service -Name "WinRM" -Force
System Info — Quick Health from zsh
pwsh -NoLogo -Command '
Write-Host "=== Windows System ===" -ForegroundColor Cyan
Write-Host "Hostname: $env:COMPUTERNAME"
Write-Host "User: $env:USERNAME"
Write-Host "OS: $((Get-CimInstance Win32_OperatingSystem).Caption)"
Write-Host "Uptime: $((Get-Date) - (Get-CimInstance Win32_OperatingSystem).LastBootUpTime)"
Write-Host "RAM: $([math]::Round((Get-CimInstance Win32_OperatingSystem).TotalVisibleMemorySize/1MB))GB total, $([math]::Round((Get-CimInstance Win32_OperatingSystem).FreePhysicalMemory/1MB))GB free"
Write-Host "CPU: $((Get-CimInstance Win32_Processor).Name)"
Write-Host "Disk C: $([math]::Round((Get-PSDrive C).Free/1GB))GB free of $([math]::Round(((Get-PSDrive C).Used + (Get-PSDrive C).Free)/1GB))GB"'
pwsh -NoLogo -Command 'Get-PSDrive -PSProvider FileSystem |
Select-Object Name, @{N="Used(GB)";E={[math]::Round($_.Used/1GB,1)}},
@{N="Free(GB)";E={[math]::Round($_.Free/1GB,1)}},
@{N="Total(GB)";E={[math]::Round(($_.Used+$_.Free)/1GB,1)}} |
Format-Table -AutoSize'
Exchange Online — Connect from zsh
pwsh -NoLogo -Command 'Connect-ExchangeOnline -UserPrincipalName erosado@chla.usc.edu'
MFA prompt opens in the Windows browser. After auth, the session persists in the pwsh process. For multi-command sessions, start pwsh interactively instead of one-shot commands.
|
pwsh -NoLogo
# Then inside pwsh:
# Connect-ExchangeOnline
# Get-TransportRule | Format-List Name, State
# exit
File Transfer Patterns
# WSL → Windows Downloads
cp ~/atelier/_bibliotheca/domus-captures/output/report.pdf /mnt/c/Users/erosado/Downloads/
# Windows → WSL (glob)
cp /mnt/c/Users/erosado/Downloads/*.{png,pdf,jpg} ~/atelier/_staging/
# Bulk move with null safety
find /mnt/c/Users/erosado/Downloads -maxdepth 1 -name '*.pdf' -mmin -60 -print0 |
xargs -0 -I{} cp {} ~/atelier/_staging/
inotifywait -m /mnt/c/Users/erosado/Downloads -e create -e moved_to |
awk '{printf "%s %s\n", strftime("%H:%M:%S"), $3}'
inotifywait requires inotify-tools. Install with sudo pacman -S inotify-tools if not present.
|
Security & Encryption
View encrypted files without writing to disk
age --decrypt -i ~/.secrets/.metadata/keys/master.age.key \
data/d001/projects/mandiant-remediation/findings-status-2026-04-16.adoc.age \
| bat --language asciidoc --file-name "findings-status-2026-04-16.adoc"
Batch re-encrypt — brace expansion + loop
for f in data/d001/projects/mandiant-remediation/{findings-status,guest-acl-update,siem-report}-2026-04-16.adoc; do
rm -f "${f}.age" && echo y | encrypt-file "$f"
done
Always rm -f the .age first. If you skip it, encrypt-file prompts about overwrite and may only delete the plaintext without re-encrypting.
|
Detect stale plaintext — files needing re-encryption
for f in data/d001/projects/*/*.adoc; do
age="${f}.age"
if [ -f "$f" ] && [ -f "$age" ]; then
pt_mod=$(/usr/bin/stat -c'%Y' "$f")
age_mod=$(/usr/bin/stat -c'%Y' "$age")
[ "$pt_mod" -gt "$age_mod" ] && echo "STALE: $f"
fi
done
Secure delete — shred for sensitive plaintext
shred -u data/d001/projects/mandiant-remediation/man-report.txt
On SSD/NVMe, shred is less effective (wear leveling), but better than rm which only removes the directory entry.
|
Pre-push audit — find all unencrypted project files
find data/d001/projects -type f ! -name '*.age' ! -name 'README.adoc' ! -name '.gitkeep' ! -name '*.py' | sort
System & Infrastructure
PipeWire audio validation
wpctl status # PipeWire status
pactl list sinks short # list audio sinks
pw-play /usr/share/sounds/freedesktop/stereo/bell.oga # test default sink
journalctl -b --grep='sof|cs35l56' --no-pager | tail -20 # kernel audio firmware
cat /proc/asound/cards # ALSA sound cards
gopass — personal document management
gopass-personal-docs # interactive entry creation
gopass-query bills # list recurring bills with totals
gopass-query storage # list storage units with gate codes
gopass-query export bills # export category to JSON
Makefile — daily workflow
make new-day # create today's worklog + update attributes
make serve # build + local server (port 8000)
make # build only
make sync-nav # sync worklog nav entries
make update-index # rebuild monthly index
KVM — VM & ISO management
ssh kvm-01 "sudo virsh list --all"
ssh kvm-02 "sudo virsh list --all"
ssh kvm-01 "ls -lh /mnt/nas/isos/*[Ii][Ss][Ee]* /var/lib/libvirt/images/*[Ii][Ss][Ee]* /mnt/onboard-ssd/isos/*[Ii][Ss][Ee]* 2>/dev/null"
ssh kvm-02 "ls -lh /mnt/nas/isos/*[Ii][Ss][Ee]* /mnt/ssd/libvirt/images/*[Ii][Ss][Ee]* 2>/dev/null"
sudo virsh console <vm-name> # Escape: Ctrl+]
ssh kvm-01 "mount | grep nas; ls /mnt/"
Per-project file dashboard
for d in data/d001/projects/*/; do
total=$(find "$d" -type f | wc -l)
plain=$(find "$d" -type f ! -name '*.age' ! -name 'README.adoc' ! -name '.gitkeep' ! -name '*.py' | wc -l)
echo "$(basename "$d") | ${total} files | ${plain} plaintext"
done
USB-C / Thunderbolt Charging Diagnostics
{
echo "=== Power Supply ==="
cat /sys/class/power_supply/*/status
echo ""
cat /sys/class/power_supply/*/type
echo ""
echo "=== UPower ==="
upower -d | grep -E 'state|percentage|energy-rate|voltage'
echo ""
echo "=== dmesg (typec/thunderbolt/PD) ==="
sudo dmesg | grep -iE 'typec|thunderbolt|ucsi|PD|power.delivery|charging' | tail -20
echo ""
echo "=== Pacman log (kernel/typec) ==="
grep -iE 'thunderbolt|typec|ucsi|^.*upgraded linux ' /var/log/pacman.log | tail -20
} | tee /tmp/INC-$(date +%F)-usbc-charging.txt
Pattern: { } groups commands into a single stdout stream. tee writes to file AND displays on screen. Reusable for any multi-command evidence capture — change the commands inside, keep the structure.
pacman — package inspection
Source: 2026-06-25 — curl error 77 investigation, checking for package upgrades
# Check package version and install date (awk field filter)
pacman -Qi curl | awk '/^Version|^Install Date/'