WRKLOG-2026-06-23
Summary
Tuesday. 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 |
|---|---|---|
PeopleSoft timesheet — 3 weeks outstanding |
■■■■■□□□□□ 5/10 |
|
TCP Clocks — code 000016014 approved (60 hrs) |
■■■■■■■■■■ 10/10 |
|
Secrets backup cleanup — d000 tier |
■■■■■■■■■■ 10/10 |
Script: |
alias-viewer.sh rewrite |
■■■■■■■■■■ 10/10 |
|
MSCHAPv2 d001 project restructure |
■■■■■■■■■■ 10/10 |
|
.zshrc cleanup — dots-quantum |
■■■■■■■■■■ 10/10 |
|
Linux Research — d000/d001 split |
■■■■■■■■■■ 10/10 |
|
Abnormal Security — d000/d001 restructure |
■■■■■■■■■■ 10/10 |
|
Essential tools inventory — triad audit |
■■■■■■■■■■ 10/10 |
|
find time patterns — mtime vs newermt |
■■■■■■■■■■ 10/10 |
|
tmux-quantum switchover runbook |
■■■■■■■■■■ 10/10 |
|
WLC 9800 RADIUS accounting audit |
■■■■■■■■■■ 10/10 |
|
STD-026 Network Device Interaction |
■■■■■■■■■■ 10/10 |
|
wlc-push.sh — automated CLI push tool |
■■■■■■■■■■ 10/10 |
|
WLC accounting fix — ISE-ACCT on all profiles |
■■■■■■■■■■ 10/10 |
|
PeopleSoft Timesheet — Catch-Up Submission
PeopleSoft Timesheet — Catch-Up Submission
Three weeks outstanding since last submitted timesheet (2026-06-01 through 2026-06-05).
| Pay Period | Status | Timesheet Partial |
|---|---|---|
2026-06-08 through 2026-06-12 |
FAILt submitted |
|
2026-06-15 through 2026-06-19 |
FAILt submitted |
|
2026-06-22 through 2026-06-26 |
🟡 Current week (partial) |
|
-
Fill in hours for week of 06-08 — reference daily notes + session logs
-
Fill in hours for week of 06-15 — reference daily notes + session logs
-
Verify PeopleSoft codes against
partials/trackers/work/peoplesoft-codes/codes.adoc -
Cross-check: projects marked NEEDS CODE in codes tracker
-
Submit all outstanding periods in PeopleSoft
-
Confirm approval routing to manager
|
Reconstruction Tip
Use daily notes to reconstruct hours:
|
TCP Clocks — PeopleSoft Code Approved
TCP Clocks — PeopleSoft Code Approved
Chris Muburi (PM) approved 60 hours to cover entirety of project work.
| Field | Value |
|---|---|
Project |
TCP Clocks Deployment |
Code |
000016014 |
Hours Approved |
60 |
Scope |
Full project — ISE onboarding, switch config, validation, go-live |
PM |
Chris Muburi |
Date Approved |
2026-06-23 |
-
Update
partials/trackers/work/peoplesoft-codes/codes.adoc— add code 000016014 to TCP Clocks row -
Backfill prior timesheets where TCP Clocks hours were unattributed
-
Track burn rate against 60-hour budget
Secrets Backup Cleanup — d000 Tier
Secrets — Backup Cleanup & Inventory Rebuild
Phase 1: Backup Pruning (✅ Complete)
~70 stale .backup-* files pruned to 26 (retention: 3 per file).
bash scripts/secrets-backup-cleanup.sh --dry-run
bash scripts/secrets-backup-cleanup.sh --keep 3
Phase 2: Tier Consolidation — Two-Tier Model
Decision: drop lab and staging. Dev absorbs lab (experiment + test). Prod stays prod.
| env file | dev | lab | staging | production |
|---|---|---|---|---|
app |
PASS |
✅ |
PASS |
PASS |
identifiers |
PASS |
✅ |
PASS |
PASS |
identity |
PASS |
✅ |
PASS |
PASS |
network |
PASS |
✅ |
PASS |
PASS |
storage |
PASS |
✅ |
PASS |
PASS |
http |
PASS |
✅ |
FAIL |
FAIL |
pki |
PASS |
FAIL |
PASS |
PASS |
vault |
PASS |
FAIL |
❌ |
FAIL |
observability |
PASS |
FAIL |
❌ |
FAIL |
cicd |
PASS |
FAIL |
❌ |
FAIL |
identity/ad-* |
PASS |
FAIL |
❌ |
FAIL |
network/ise |
PASS |
FAIL |
❌ |
FAIL |
network/wlc |
PASS |
FAIL |
❌ |
FAIL |
pki/byod |
PASS |
FAIL |
PASS |
PASS |
| env file | dev | prod |
|---|---|---|
app |
PASS |
PASS |
identifiers |
PASS |
PASS |
identity |
✅ (+ scoped) |
PASS |
network |
✅ (+ scoped) |
PASS |
storage |
PASS |
PASS |
http |
PASS |
— |
pki |
✅ (+ scoped) |
✅ (+ scoped) |
vault |
PASS |
— |
observability |
PASS |
— |
cicd |
PASS |
— |
Phase 3: DOMAIN_INVENTORY Rebuild
Generator script creates YAML skeleton from filesystem:
bash scripts/secrets-inventory-gen.sh --domain d000
bash scripts/secrets-inventory-gen.sh --domain d000 --output /tmp/generated.yaml
Hand-crafted inventory with descriptions and migration roadmap at /tmp/DOMAIN_INVENTORY.yaml.
Phase 4: Validate with yq
yq '.tiers | keys' /tmp/DOMAIN_INVENTORY.yaml
yq '.tiers.dev | keys' /tmp/DOMAIN_INVENTORY.yaml
yq '.tiers.prod | keys' /tmp/DOMAIN_INVENTORY.yaml
yq '.tiers.dev | to_entries | map(select(.value.status == "DEPRECATED")) | from_entries | keys' /tmp/DOMAIN_INVENTORY.yaml
yq '.tiers.dev.network.scoped | keys' /tmp/DOMAIN_INVENTORY.yaml
yq '.tiers.dev.identity.scoped | keys' /tmp/DOMAIN_INVENTORY.yaml
yq '.tiers.dev.pki.scoped | keys' /tmp/DOMAIN_INVENTORY.yaml
diff <(yq '.tiers | keys' /tmp/DOMAIN_INVENTORY.yaml) \
<(yq '.tiers | keys' /tmp/generated.yaml)
yq '.retired | to_entries[] | .key + ": " + .value.reason' /tmp/DOMAIN_INVENTORY.yaml
yq '.' /tmp/DOMAIN_INVENTORY.yaml > /tmp/before.yaml
age -e -R ~/.age/recipients/self.txt -o /tmp/test.yaml.age /tmp/DOMAIN_INVENTORY.yaml
age -d -i ~/.age/identities /tmp/test.yaml.age | yq '.' > /tmp/after.yaml
diff /tmp/before.yaml /tmp/after.yaml && echo "Round-trip clean" || echo "MISMATCH"
rm /tmp/before.yaml /tmp/after.yaml /tmp/test.yaml.age
Phase 5: Deploy
rm ~/.secrets/environments/domains/DOMAIN_INVENTORY.yaml.age
age -e -R ~/.age/recipients/self.txt \
-o ~/.secrets/environments/domains/DOMAIN_INVENTORY.yaml.age \
/tmp/DOMAIN_INVENTORY.yaml
age -d -i ~/.age/identities ~/.secrets/environments/domains/DOMAIN_INVENTORY.yaml.age \
| yq '.tiers | keys'
rm /tmp/DOMAIN_INVENTORY.yaml /tmp/generated.yaml
git -C ~/.secrets add environments/domains/DOMAIN_INVENTORY.yaml.age
git -C ~/.secrets commit -m "docs(d000): two-tier model — retire lab/staging, blast radius roadmap"
Phase 6: Remove Retired Tiers
| Only after verifying lab/staging credentials are redundant. |
diff <(age -d -i ~/.age/identities ~/.secrets/environments/domains/d000/lab/network.env.age \
| yq 'keys') \
<(age -d -i ~/.age/identities ~/.secrets/environments/domains/d000/dev/network.env.age \
| yq 'keys')
rm -rf ~/.secrets/environments/domains/d000/lab
rm -rf ~/.secrets/environments/domains/d000/staging
rm -rf ~/.secrets/environments/domains/d000/notes
git -C ~/.secrets add -A
git -C ~/.secrets commit -m "chore(d000): remove retired tiers (lab, staging, notes)"
Decisions Made
-
Two-tier model: dev + prod (lab and staging retired)
-
Blast radius: scoped subdirectories over flat files
-
Backup retention: 3 most recent per file
-
notes/— remove (empty, no purpose) -
Lab/staging credential diff — verify before deletion
-
Phase 2 scoped decomposition — network, identity, pki
alias-viewer.sh — Shell Alias Inspector
alias-viewer.sh — Shell Alias Inspector
Rewrote scripts/alias-viewer.sh from scratch. Original was broken: alias returns nothing in non-interactive shells, sed|sed|awk|sort|column pipeline was fragile, sort -t '│' fails on multi-byte Unicode.
| Problem | Fix |
|---|---|
|
|
|
Single |
|
|
|
|
All aliases in one flat list |
|
git, docker, kubernetes, jq-yq, python, terraform, vault, secrets, infra-session, hyprland, packages, systemd, domus-nav, navigation, history-vault, editor, files, safety, diff, grep, general
alias | ./scripts/alias-viewer.sh --stdin -c
alias | ./scripts/alias-viewer.sh --stdin -s git -x
alias | ./scripts/alias-viewer.sh --stdin -s cd -x -c
-
gawk asorti()— sort associative array keys without externalsort -
index()+substr()— parsename=valuewithout fragile-Ffield splitting -
2D associative arrays —
entries[category][name]for grouped storage -
Word-boundary regex —
(|[a-zA-Z0-9])term($|[^a-zA-Z0-9])prevents substring false positives -
\047octal escape — single quote inside awk without shell quoting hell
MSCHAPv2 Migration — d001 Project Restructure
Brought data/d001/projects/mschapv2-migration/ into compliance with STD-001, STD-020, STD-025.
Changes
-
STD-025 (Conditional Rendering): Rewrote
attributes.adocwithifdef::env-d001[]+ zeroedifndeffallback — 60 naked attribute definitions wrapped -
STD-001 (Project Structure): Created
metadata.adocandsummary.adocpartials. Demoted 4 misplaced assemblers (mschapv2-migration-team,mschapv2-queries,mschapv2-standard-report,mschapv2-weekly-report) to content-only partials -
STD-020 (Diagrams): Created
diagrams/withmigration-architecture.dot(Graphviz) andmigration-architecture.d2(D2), both Catppuccin Mocha. AddedREADME.adocattribute map -
STD-008 (Code Block Standard): Added
subs=attributes+to all[source,bash]blocks referencing{attributes}. Added Sheet 6 (policy-match-by-protocol) to weekly runbook -
File renames: Removed date stamps from PowerShell partials (
pwsh-cmds-2026-05-26-v2→pwsh-rsat-discovery,pwsh-reports-2026-05-27→pwsh-gpo-analytics). Cleaned API report names (dataconnect-auth-report→dataconnect-report, etc.) -
Root assembler: Rewritten with logical section grouping — summary → metadata → team → waves → runbook → queries → API reports → PowerShell
Build & Verify
build-adoc $(find data/d001/projects/mschapv2-migration -maxdepth 1 -name 'mschapv2-migration.adoc') html --variant light-cyan -a env-d001 && firefox $(find data -path '*/mschapv2-migration/output/mschapv2-migration.html') &
(find data/d001/projects/mschapv2-migration -maxdepth 1 -name 'mschapv2-migration.adoc') data/d001/projects/mschapv2-migration/mschapv2-migration.adoc
grep -rn 'Investigation Steps '(find data/d001/projects/mschapv2-migration -maxdepth 1 -name 'mschapv2-migration.adoc')
Investigation Steps
Partials (18 → 17, clean slugs)
attributes.adoc metadata.adoc summary.adoc
team.adoc queries.adoc weekly-report.adoc
dataconnect-report.adoc ers-report.adoc mnt-report.adoc
endpoint-profile.adoc wave1-chromebooks.adoc wave2-wyse.adoc
wave3-windows.adoc wave4-macos.adoc wave5-ios.adoc
pwsh-rsat-discovery.adoc pwsh-gpo-analytics.adoc
Next
-
Run weekly report to refresh migration numbers (see runbook in rendered doc)
-
d001 close mschapv2-migrationbefore commit
Linux Research — d000/d001 Split
Split linux-research into personal (d000, full API) and team-facing (d001, GUI + CLI only) versions. First onboarding case: Dr. Shahab’s workstation. Standard applies to all future Linux onboards including David Rukiza Ntashamaje.
Changes
-
d000 (personal): Full copy with netapi CLI, DataConnect SQL, playbook automation, ISE API helpers
-
d001 (team): Stripped all API references —
netapi-reference.adoc,queries.adoc,playbook/,sql/,scripts/removed. ISE verification replaced with GUI steps + screenshot placeholders -
Assembler: Rewritten to include
team/(ISE GUI phases) +endpoint/(Linux CLI) +switch/— no API sections -
Attributes: Added
{person-david}(David Rukiza Ntashamaje, Security Analyst). Added 17 missing attribute definitions (ad-domain,ad-base-dn,key-path,machine-key,wired-interface,nm-connection-wired, etc.) -
Scripts extracted: 5 inline scripts →
scripts/with monad pattern (pasteable run +[%collapsible]source):check-endpoint-connectivity.sh,full-endpoint-validation.sh,full-rollback.sh,verify-cert-key-match.sh,verify-luks-encryption.sh -
Stripped:
dsourcecredential lines fromphase-0-prereqs.adocandlinux-research-playbook.adoc -
Troubleshooting: Replaced
netapi ise mnt session/dc auth-historywith ISE Live Logs GUI instructions
Build & Verify
build-adoc $(find data/d000/projects/linux-research -maxdepth 1 -name 'linux-research.adoc') html --variant light-cyan -a env-d001 && firefox $(find data/d000 -path '*/linux-research/output/linux-research.html') &
build-adoc $(find data/d001/projects/linux-research -maxdepth 1 -name 'linux-research.adoc') html --variant light-cyan -a env-d001 && firefox $(find data/d001 -path '*/linux-research/output/linux-research.html') &
build-adoc $(find data/d001/projects/linux-research -maxdepth 1 -name 'linux-research.adoc') html --variant light-cyan && firefox $(find data/d001 -path '*/linux-research/output/linux-research.html') &
Conditional Attributes — ifdef in Practice
The partials/attributes.adoc file uses AsciiDoc’s ifdef preprocessor to switch between environments at build time. The same source files produce different output depending on which flag is passed.
With -a env-d001 (production values):
build-adoc $(find data/d001/projects/linux-research -maxdepth 1 -name 'linux-research.adoc') html --variant light-cyan -a env-d001
Every {attribute} in every partial resolves to the real CHLA value: real hostnames, real IPs, real policy names, real personnel. The rendered doc is operationally useful — David can follow it step by step.
Without -a env-d001 (zeroed fallback):
build-adoc $(find data/d001/projects/linux-research -maxdepth 1 -name 'linux-research.adoc') html --variant light-cyan
Every {attribute} resolves to UNDEFINED or 0.0.0.0. The document structure is intact but contains zero operational data. Safe to share externally, use in presentations, or commit to public repos.
How it works inside partials/attributes.adoc:
:switch-name: UNDEFINED
:switch-ip: 0.0.0.0
:ise-ppan: 0.0.0.0
The ifdef block only activates when the build flag matches. The ifndef block provides safe defaults when no flag is passed. This is defense-in-depth at the rendering layer — even if someone obtains the .adoc source, building without -a env-d001 produces nothing useful.
Four-layer security model:
| Layer | Protection |
|---|---|
1. Encryption |
|
2. Gitignore |
|
3. Conditional rendering |
|
4. Scrub rules |
Pre-push |
Verification
-
Zero
netapi,dc-run-sql,dsourcereferences in rendered HTML -
Zero
UNDEFINEDvalues with-a env-d001 -
Zero unresolved
{attribute}literals — all 17 missing definitions added -
1.6M HTML — ISE GUI + Linux CLI + switch + troubleshooting
Next
-
Capture ISE GUI screenshots for placeholder images
-
Onboard David’s workstation using this standard
-
Update
data/d000/projects/linux-research/attributes forenv-d000homelab values
.zshrc Cleanup — dots-quantum
.zshrc Cleanup — dots-quantum
Full rewrite of ~/atelier/_projects/personal/dots-quantum/zsh/.zshrc.
1,804 → ~1,250 lines (728 insertions, 1,241 deletions). Zero behavior change except bug fixes.
Bugs Fixed
| Location | Bug | Fix |
|---|---|---|
|
Alias with |
Converted to function: |
|
|
Changed to |
|
Unquoted |
All |
|
Unquoted |
|
|
|
|
Dead Code Removed
-
Duplicate
LANG/LC_ALLexports (lines 53-54, identical to 23-25) -
Duplicate history config (lines 215-226, overridden by 1082-1095 with 1M setting)
-
Duplicate keybindings (lines 765-776, repeated at 1057-1075 as "FINAL")
-
30 lines commented-out zsh-autocomplete config (disabled plugin)
-
WSL block (native Arch/Hyprland — not used)
-
Principia capture functions (
capture-ssh,capture-cmd,capture-encrypt,capture-conv,plog) — migrated to domus-captures
Restow Command
dq-stow -R zsh
Then open a new terminal to test. If broken, restore:
cd ~/atelier/_projects/personal/dots-quantum
git checkout -- zsh/.zshrc
dq-stow -R zsh
Shell Banner Redesign
64 tools across 15 categories in a box-drawn frame with dynamic padding. Visible-length tracking prevents ragged right edge from ANSI escape codes.
-
$'\033[36m'not'\033[36m'— escape bytes at assignment, not interpretation time -
_row()helper takes visible char count, pads with%${pad}s ""to exact width -
_cat()computes10 + 3 + cmd_vis_lenfor label + separator + content -
Live secrets state: shows
d000/devwhen loaded,unloadedwhen not -
domus-audio.shanddomus-media.shremoved from source list — files don’t exist
Test Checklist
-
New terminal opens without errors
-
Welcome banner displays correctly — right edge aligned
-
d000 listworks -
d001 listworks -
d001 close <project>creates backup and encrypts -
tlog echo "test"works without eval issues -
deltadiff file1 file2works (now a function) -
domus-checkruns without changing your working directory -
alias | ./scripts/alias-viewer.sh --stdin -cshows same 318 aliases -
ds d000 dev/networkloads secrets -
dsuclears secrets
Stow Inventory Audit
Audit of all 49 stow packages in dots-quantum/. Only 10 are actively stowed on this machine.
| Package | Files |
|---|---|
bash |
4 |
chromium |
1 |
fd |
1 |
fzf |
1 |
mimeapps |
1 |
oh-my-posh |
1 |
ripgrep |
1 |
vim |
4 |
w3m |
3 |
zsh |
3 |
| Package | Status | Notes |
|---|---|---|
aider |
2/3 linked |
1 file not stowed |
claude |
1/7598 linked |
Only top-level config needed — rest is cache/data |
git |
1/2 linked |
Investigate which file is missing |
ssh |
2/10 linked |
Expected — |
| Package | Files | Action |
|---|---|---|
shell |
19 |
Investigate — |
bin |
78 |
Investigate — |
systemd |
22 |
Investigate — user units. Are they deployed via |
tmux |
40 |
Investigate — tmux config. Direct copy or separate mechanism? |
hyprland |
17 |
Machine-specific — Hyprland not on WSL |
kitty |
18 |
Machine-specific — may not be on all machines |
waybar |
11 |
Machine-specific — Wayland compositor bar |
hosts |
5 |
Requires root — cannot stow to |
secrets |
5 |
Likely deployed via |
opencode |
770 |
Likely bundled app — not traditional stow target |
eww |
2 |
Wayland widget — machine-specific |
mako |
2 |
Wayland notifications — machine-specific |
wofi |
3 |
Wayland launcher — machine-specific |
fastfetch |
12 |
Likely stowed on desktop, not WSL |
cava |
9 |
Audio visualizer — machine-specific |
btop |
2 |
Optional — may use default config |
aerc |
4 |
Email client — evaluate if in use |
ghostty |
1 |
Terminal — evaluate if in use |
himalaya |
1 |
Email client — evaluate if in use |
jj |
1 |
Jujutsu VCS — evaluate if in use |
lazygit |
1 |
Git TUI — evaluate if in use |
glow |
1 |
Markdown renderer — evaluate if in use |
htop |
1 |
Optional — may use btop instead |
gpg |
2 |
Likely deployed separately for security |
libvirt |
1 |
KVM config — machine-specific |
thunar |
2 |
File manager — desktop-specific |
vscodium |
3 |
Editor — evaluate if in use |
wezterm |
1 |
Terminal — evaluate if in use |
zathura |
1 |
PDF viewer — evaluate if in use |
nvim-portable |
1 |
Neovim portable config — evaluate |
powershell |
1 |
WSL-specific — may only be on work machine |
fish |
1 |
Fish shell — evaluate if in use |
.claude |
1 |
Claude config — separate from |
share |
48 |
XDG share data — investigate contents |
Action Items
-
Investigate
shell+bin— 97 files not stowed but actively used. Are they deployed via a separate mechanism? If not, stow them:dq-stow shell bin -
Investigate
gitpartial — which file is missing its symlink? -
Investigate
tmux— 40 files not stowed. Is tmux config deployed differently? -
Investigate
systemd— 22 user units. Are they enabled viasystemctl --user enable? -
Review WSL needs — which packages should be stowed on work WSL vs home Arch?
-
Prune dead packages — fish, himalaya, aerc, ghostty, wezterm — still in use?
-
Create machine manifest — per-machine list of which packages to stow (e.g.,
machines/modestus-aw.txt,machines/wsl-chla.txt)
Implementation: Stow Missing Critical Packages
cd ~/atelier/_projects/personal/dots-quantum
# Dry run — see what would be linked
stow -n -v shell 2>&1 | head -20
stow -n -v bin 2>&1 | head -20
stow -n -v tmux 2>&1 | head -20
# If clean, stow them
dq-stow shell
dq-stow bin
dq-stow tmux
Backout: Unstow If Something Breaks
# Unstow a package (removes symlinks, leaves originals in dots-quantum)
dq-stow -D shell
dq-stow -D bin
dq-stow -D tmux
# Nuclear: restore .zshrc specifically
cd ~/atelier/_projects/personal/dots-quantum
git checkout -- zsh/.zshrc
dq-stow -R zsh
Backout: Full .zshrc Rollback
If the rewrite itself is broken (not just stow):
# 1. Revert the commit
git -C ~/atelier/_projects/personal/dots-quantum log --oneline -5
git -C ~/atelier/_projects/personal/dots-quantum revert HEAD
# 2. Reload
source ~/.zshrc
# 3. Or hard reset to previous commit (destructive — loses all changes since)
git -C ~/atelier/_projects/personal/dots-quantum reset --hard HEAD~1
find Time Patterns — File Search Across Linux Systems
The Problem
Two different questions, two different find invocations:
-
Last 24 hours — rolling window from now, crosses midnight
-
Today since midnight — calendar day, stops at 00:00:00
Confusing these is the most common find mistake.
The -mtime flag counts in 24-hour periods; -newermt compares against a timestamp string.
Last 24 Hours — -mtime -1
find . \
-path './.git' -prune -o \
-type f -mtime -1 \
-printf '%TY-%Tm-%Td %TH:%TM %p\n' |
sort -r |
head -50
-mtime -1 means "modified less than 1 day (24h) ago."
-path './.git' -prune -o eliminates .git/ without descending — critical in repos with thousands of pack objects.
Today Since Midnight — -newermt
First, understand what date gives you:
echo "now: $(date)"
echo "today: $(date -d today)"
echo "tomorrow: $(date -d tomorrow)"
Then bracket the calendar day:
find . \
-path './.git' -prune -o \
-type f \
-newermt "$(date +%F) 00:00:00" \
! -newermt "$(date -d tomorrow +%F) 00:00:00" \
-printf '%TY-%Tm-%Td %TH:%TM %p\n' |
sort -r
The pattern: -newermt START ! -newermt END creates a time window.
$(date +%F) outputs YYYY-MM-DD — works on RHEL, Arch, Debian, Alpine.
-newermt is a GNU extension.
On macOS or BusyBox, use -newer with a reference file, or gfind from coreutils.
|
Everything Modified Recently (Epoch Sort)
find . \
-path './.git' -prune -o \
-type f \
-printf '%T@ %TY-%Tm-%Td %TH:%TM %p\n' |
sort -nr |
head -40 |
cut -d' ' -f2-
%T@ prints the epoch timestamp (seconds since 1970), which sort -nr orders numerically in reverse.
cut -d' ' -f2- strips the epoch column, leaving only the human-readable date and path.
This is the most portable "what changed recently?" command — no date math, no -newermt, just raw sort on modification time.
Git Alternatives — When You’re Inside a Repo
When the question is "what changed?" rather than "what’s new on disk?", Git answers faster:
# Working tree status (staged + unstaged + untracked)
git status --short
# Tracked files with content changes (unstaged)
git diff --name-only
# Files changed in the latest commit
git show --name-only --pretty='format:' HEAD
git status --short is almost always what you want inside a repo.
find matters when you’re outside a repo, across multiple repos, or on systems where files arrive outside Git (logs, uploads, cron output, RHEL package updates).
When to Use Which
| Question | Tool |
|---|---|
What changed in the last 24 hours? |
|
What changed today (calendar day)? |
|
What are the most recently modified files? |
|
What did I change in this repo? |
|
What did the last commit touch? |
|
What changed across multiple repos? |
|
Improved Patterns — Tested 2026-06-23
Every command below was run against domus-captures and verified.
Multi-Prune — Skip Noise Directories
The single -path './.git' -prune is a starting point.
Real repos have node_modules, pycache, .claude — prune them all in one predicate group:
find . \
\( -path './.git' -o -path '*/.git' -o -path '*/node_modules' \
-o -path '*/__pycache__' -o -path '*/.claude' \) -prune \
-o -type f -mtime -1 \
-printf '%TY-%Tm-%Td %TH:%TM %p\n' |
sort -r | head -20
The \( … \) groups multiple -path tests with -o (OR).
The final -o after -prune means "otherwise, apply these tests."
Without the grouping parentheses, operator precedence makes -prune bind wrong and you get .git contents in output.
Extension Filtering — find + awk in One Pass
Instead of adding -name '.adoc' -o -name '.sh' -o -name '*.yaml' (verbose, error-prone with -o precedence), pipe to awk:
find . -path './.git' -prune -o \
-type f -mtime -1 \
-printf '%TY-%Tm-%Td %TH:%TM %p\n' |
awk '/\.(adoc|md|yaml|yml|sh)$/' |
sort -r | head -20
Why awk over grep: awk’s regex is applied to the full line — no need for grep -E.
The pattern /\.(adoc|md|yaml|yml|sh)$/ anchors to end-of-line, matching the filename at the end of -printf’s `%p.
Size + Time — See What’s Large and Recent
find . -path './.git' -prune -o \
-type f -mtime -1 \
-printf '%TY-%Tm-%Td %TH:%TM %8s %p\n' |
sort -r | head -20
%8s right-pads the file size to 8 columns — bytes aligned for scanning.
This reveals when an encrypted .age file is unexpectedly large (re-encryption bloat) or when a build artifact leaked into the tree.
Minute Precision — -mmin
-mtime counts in 24-hour chunks. -mmin counts in minutes:
find . -path './.git' -prune -o \
-type f -mmin -120 \
-printf '%TY-%Tm-%Td %TH:%TM %p\n' |
sort -r | head -20
-mmin -120 = modified in the last 2 hours.
Use case: "what did I touch during this Claude session?"
Epoch Sort Without cut — awk Replaces a Pipe Stage
The original uses cut -d' ' -f2- to strip the epoch.
awk does it without spawning another process:
find . -path './.git' -prune -o \
-type f \
-printf '%T@ %TY-%Tm-%Td %TH:%TM %p\n' |
sort -nr | head -20 |
awk '{$1=""; print substr($0,2)}'
$1="" blanks the epoch field; substr($0,2) trims the leading space.
One fewer fork — the difference matters when scanning 100K+ files on a remote RHEL system over SSH.
Cross-Repo Scan — Modification Count per Repo
find ~/atelier/_bibliotheca -maxdepth 1 -type d |
while read -r repo; do
count=$(find "$repo" -path '*/.git' -prune -o -type f -mtime -1 -print 2>/dev/null | wc -l)
[ "$count" -gt 0 ] && echo "$count $repo"
done | sort -nr
Shows which repos had activity in the last 24 hours.
On RHEL at work, point this at /opt/ or /var/log/ to see which services are writing.
stat-Based — RHEL 7 Portability
If -printf format differs across findutils versions, stat is the fallback:
find . -path './.git' -prune -o \
-type f -mtime -1 \
-exec stat --format='%y %n' {} \; 2>/dev/null |
awk '{print $1, $2, $NF}' |
sort -r | head -15
stat --format='%y' gives human-readable mtime.
-exec stat … {} \; forks once per file — slower than -printf, but works everywhere GNU coreutils is installed (RHEL 7/8/9, Ubuntu, Debian).
fd — Modern Alternative (Already Installed)
fd respects .gitignore by default and has cleaner syntax:
fd --type f --changed-within 1d --exclude .git .
No -printf, no -prune, no sort — fd handles all of it.
Trade-off: fd is not on RHEL by default. Install via cargo install fd-find or EPEL.
On your Arch workstation, it’s already at /usr/bin/fd. On work RHEL boxes, find is the universal tool.
RHEL/Enterprise Notes
-
RHEL 7+ ships GNU findutils —
-newermtand-printfboth work. -
SELinux contexts don’t affect
findoutput unless you add-lsor-printf '%Z'. -
For audit trails on RHEL:
ausearch -ts today -f /pathgives kernel-level file access, not just modification. -
On systems with
locateinstalled:locate -Sshows database age;updatedbrefreshes. Faster thanfindfor name searches across the entire filesystem, but doesn’t track modification time. -
RHEL 9 ships findutils 4.8 — all features here work. RHEL 7 ships 4.5 —
-newermtworks,-printfworks,-emptyworks. No version gating needed.
tmux-quantum Switchover — Replace tmux-config on Second Workstation
This runbook switches a workstation from tmux-config to tmux-quantum.
Both machines share the same directory structure (~/atelier/_projects/personal/).
Current State (This Machine — Reference)
~/.config/tmux → ~/atelier/_projects/personal/tmux-quantum (symlink)
~/.config/tmuxinator → ~/.config/tmux/sessions (symlink)
~/atelier/_projects/personal/tmux-config/ (archived, not deployed)
Pre-Flight — Verify on the Other Machine
# What's currently deployed?
file ~/.config/tmux
ls -la ~/.config/tmux
# Is it a symlink to tmux-config, or a direct clone?
readlink -f ~/.config/tmux
# Confirm directory structure matches
ls ~/atelier/_projects/personal/tmux-config
If ~/.config/tmux points to tmux-config or IS the tmux-config repo, proceed.
If it’s something else entirely, investigate before overwriting.
Step 1 — Back Up Running Config
# Save current tmux.conf in case you need to roll back
cp ~/.config/tmux/tmux.conf /tmp/tmux.conf.bak
# If you have a local.conf with machine-specific bindings, save it
cp ~/.config/tmux/local.conf /tmp/local.conf.bak 2>/dev/null
# Capture current tmux server state
tmux list-sessions 2>/dev/null
tmux show-options -g > /tmp/tmux-options-before.txt 2>/dev/null
Step 2 — Clone tmux-quantum
git clone git@github.com:EvanusModestus/tmux-quantum.git \
~/atelier/_projects/personal/tmux-quantum
Step 3 — Remove Old Symlink, Create New
# Remove old symlink (or unlink if it's a symlink)
rm ~/.config/tmux
# If it was a directory (not a symlink), move it instead:
# mv ~/.config/tmux ~/.config/tmux-old
# Create new symlink
ln -sf ~/atelier/_projects/personal/tmux-quantum ~/.config/tmux
# Link tmuxinator sessions
ln -sf ~/.config/tmux/sessions ~/.config/tmuxinator
Step 4 — Machine-Specific local.conf
tmux-quantum gitignores local.conf — this is where per-machine bindings go.
# Start from the example
cp ~/.config/tmux/local.conf.example ~/.config/tmux/local.conf
# Edit for this machine — paths, display size, etc.
nvim ~/.config/tmux/local.conf
If you backed up a local.conf from the old config, review it — don’t blindly copy.
Keybinding tables may differ between tmux-config and tmux-quantum.
Step 5 — Install TPM Plugins
# Bootstrap TPM if not already present
test -d ~/.config/tmux/plugins/tpm || \
git clone https://github.com/tmux-plugins/tpm ~/.config/tmux/plugins/tpm
# Reload config
tmux source-file ~/.config/tmux/tmux.conf
# Inside tmux: prefix + I (capital I) to install plugins
Step 6 — Verify
# Confirm symlink
readlink -f ~/.config/tmux
# Confirm config loads without errors
tmux source-file ~/.config/tmux/tmux.conf && echo "clean"
# Confirm keybindings loaded (check for vim-style navigation)
tmux list-keys | grep -E 'select-pane.*-[UDLR]'
# Confirm prefix is C-a (not C-b)
tmux show-option -g prefix
# Confirm tmuxinator sessions are visible
tmuxinator list
# Run the conflict checker
bash ~/.config/tmux/scripts/check-conflicts.sh
Step 7 — Archive tmux-config
# Don't delete — archive in place
# The repo stays at ~/atelier/_projects/personal/tmux-config
# It's just no longer deployed anywhere
# Verify it's not symlinked
file ~/.config/tmux
# Should show: symbolic link to .../tmux-quantum
Rollback (If Something Breaks)
# Point back to the old config
rm ~/.config/tmux
ln -sf ~/atelier/_projects/personal/tmux-config ~/.config/tmux
tmux source-file ~/.config/tmux/tmux.conf
Quick Reference — Differences from tmux-config
| Aspect | tmux-config | tmux-quantum |
|---|---|---|
Architecture |
Single monolithic |
9 domain modules in |
Keybinding conflicts |
Possible (no detection) |
|
Themes |
Hardcoded hex colors |
Variable-based ( |
Plugin config |
Scattered across files |
Consolidated in |
Sessions |
Separate tmuxinator install |
Sessions bundled in |
Machine-specific |
Edit main config |
Gitignored |
Essential Tools Inventory — The Development Triad
The three repos that constitute the core development environment — the shell/desktop layer, the multiplexer, and the editor. Each is independently deployable, deliberately modular, and designed to share muscle memory across contexts.
dots-quantum — Hyprland Dotfiles Manager
Location: ~/atelier/_projects/personal/dots-quantum/
Deployment: GNU Stow — 49 packages, symlink-based
Remotes: GitHub + GitLab + Gitea (triple mirror)
History: 93 commits since 2026-03-16
| Layer | Scope |
|---|---|
Window manager |
Hyprland compositor (791-line config, 9 utility scripts: wallpaper, dj-mode, shader-toggle, snap-corner, toggle-pip, eww-toggle) |
Status bar |
Waybar with 9 module scripts (cpu, memory, network, weather) |
Shells |
zsh (68 KB — 46 commands, ~43 functions, 104 aliases), bash (48 KB), fish (44 KB) |
Shared shell modules |
|
Terminals |
kitty, ghostty, wezterm |
Editors |
nvim-portable (single-file zero-plugin init for servers), aider, vscodium, opencode |
CLI tools |
|
Libraries |
|
System services |
systemd timers — 7-timer accountability: pacman-news, project-stale, dotfiles-sync, daily-audit, security-remediation, cert-expiry-monitor, weekly-review |
Desktop |
wofi (launcher), mako (notifications), eww (widgets) |
Monitoring |
btop (Catppuccin), fastfetch (12 presets), htop, cava |
Secrets |
ssh/ + gpg/ + hosts/ + secrets/ — gitignored, age-encrypted |
-
Public packages (18 default): bash, git, hyprland, waybar, wofi, mako, kitty, ghostty, fish, oh-my-posh, btop, lazygit, fastfetch, fzf, ripgrep, fd, bin, opencode
-
Private packages (local-only, never committed): ssh, gpg, hosts, secrets
-
Local overrides:
~/.config/hypr/local.conf,~/.bashrc.local
dots-quantum’s tmux/ package contains only tmuxinator sessions — the actual tmux config is owned by tmux-quantum.
|
tmux-quantum — Library-Style Multiplexer
Location: ~/atelier/_projects/personal/tmux-quantum/
Deployment: Direct symlink (ln -sf → ~/.config/tmux/)
Remote: GitHub
History: 8 commits, stable
Supersedes: tmux-config (archived, not deployed)
| Module | Responsibility |
|---|---|
|
Terminal, prefix ( |
|
Pane/window navigation (h/j/k/l), copy mode (Enter, v/V/C-v, y) |
|
Click, scroll, resize |
|
Platform-detected: wl-copy (Wayland) / xclip (X11) |
|
Splits (|/-), resize (H/J/K/L), zoom (z), mark (m/M), swap (~), layouts (M-1..5), sync (S) |
|
Session management (N=new, X=kill, w/W=choose tree), nested tmux (F10) |
|
Popup workflows: |
|
Status bar via |
|
tmux 3.2+ features |
-
aethelred — purple gradient (active)
-
solar-flare — warm orange
-
tokyo-night — cool blue
tmux-resurrect, tmux-continuum, tmux-yank, tmux-open, tmux-prefix-highlight, tmux-thumbs, tmux-sessionist
Prefixed by domain: cfg- (6), proj- (7), lrn- (2), lab- (1), data- (1), daily- (1)
-
scripts/check-conflicts.sh— verifies no same-table keybinding collisions across all lib/ modules -
All plugin config consolidated in
plugins/tpm.conf— no scatter -
Claude Code integration: rules, validator agent,
/tmux-qaskill
domus-nvim — Full IDE Configuration
Location: ~/atelier/_projects/personal/domus-nvim/
Deployment: Symlink or NVIM_APPNAME
Remote: GitHub
History: 72 commits since 2026-02-12
Requires: Neovim 0.11.0+
| Category | Key Plugins |
|---|---|
Editor fundamentals (14) |
treesitter + textobjects + context, autopairs, Comment.nvim, mini.ai + mini.surround, flash.nvim, rainbow-delimiters, todo-comments, undotree |
UI/Visual (15) |
Catppuccin Mocha (primary), lualine, indent-blankline, noice + nvim-notify, alpha-nvim (dashboard), which-key, zen-mode, screenkey (teaching) |
Coding/LSP/Completion (17) |
mason + mason-lspconfig, nvim-lspconfig, blink.cmp (completion), LuaSnip, conform (formatting), nvim-lint, glance (peek), refactoring, copilot + CopilotChat, cloak (secret hiding) |
Git (4) |
vim-fugitive, gitsigns, lazygit.nvim, diffview |
Navigation/Tools (11) |
telescope + fzf-native + extensions, harpoon, oil, toggleterm, auto-session |
Language-specific (14) |
iron.nvim (Python REPL), markdown-preview, cisco.vim, junos.vim, rainbow_csv, vim-dadbod (database) |
Debugging (4) |
nvim-dap + UI + virtual-text + mason-nvim-dap |
lua_ls, pyright, rust_analyzer, ts_ls, html, cssls, jsonls (SchemaStore), yamlls (kubernetes), bashls, dockerls, ansiblels, marksman, taplo, clangd
stylua (Lua), ruff (Python), prettier (JS/TS/JSON/YAML/HTML/CSS), shfmt (shell), rustfmt, clang-format, sqlfluff, taplo
ruff, eslint_d, selene, markdownlint, yamllint, jsonlint, hadolint, golangci-lint, tflint
-
Leader:
<Space> -
Vim-standard LSP bindings (gd, gr, K)
-
Harpoon for rapid file switching (
<Space>1-4) -
Telescope for fuzzy everything (
<Space>ff/fg) -
Git via
<Space>g*or LazyGit (<Space>lg) -
DAP debugging via
<F5-F12>
-
Python — iron.nvim REPL (ipython), ruff formatting + linting, pyright type checking
-
Rust — rust-analyzer with clippy on save, inlay hints
-
AsciiDoc — custom ftplugin, linting, 15 exotic syntax snippets
-
C/C++ — clangd LSP, clang-format, codelldb DAP
-
SQL — vim-dadbod with UI + completion
-
nvim-portablein dots-quantum = minimal single-file init for servers (zero plugins, same muscle memory) -
domus-nvim = full IDE for workstations (88 plugins, 13 LSPs, DAP)
The Triad — How They Interlock
| Aspect | dots-quantum | tmux-quantum | domus-nvim |
|---|---|---|---|
Deployment |
GNU Stow (49 packages) |
Direct symlink |
Symlink / NVIM_APPNAME |
Owns |
Shell, desktop, CLI tools, system services |
Multiplexer config, session templates |
Editor plugins, LSP, completion, debugging |
Theme |
Catppuccin Mocha (btop, waybar, mako) |
Variable-based ( |
Catppuccin Mocha (primary) |
Secret model |
age-encrypted + gitignored packages |
|
cloak.nvim hides inline secrets |
Claude integration |
Stow rules, settings |
Rules, validator agent, |
|
Overlap |
tmuxinator sessions only (tmux/ package) |
None — standalone |
None — standalone |
The three repos share no configuration overlap.
dots-quantum owns everything except ~/.config/tmux/ (tmux-quantum) and ~/.config/nvim/ (domus-nvim).
Each can be updated, tested, or rebuilt independently without affecting the others.
CAB Rehearsal — Abnormal Security CR-2026-06-25
Dispositio — CAB Presentation Structure
Exordium (Opening — 30 seconds)
"Good morning. CR-2026-06-25 — Abnormal Security tester group expansion. We’re requesting approval to add server team and all IS staff to the pilot group that’s been running for six weeks with zero incidents. No policy changes, no mail flow changes, no downtime. We’re adding people to a group."
Narratio (Context — 60 seconds)
"We’re migrating from Cisco ESA to Abnormal Security for email threat detection. Abnormal is API-based — it connects to M365 via Graph API and analyzes email behavior without changing mail flow. It doesn’t sit inline like ESA did.
For the past six weeks, 21 InfoSec team members have operated under four Microsoft Defender policies scoped to a single Exchange security group — Abnormal-Pilot-Users. Anti-spam, safe links, safe attachments, and an external sender subject modification rule. All four policies are governed by group membership. Adding someone to the group turns on protection. Removing them turns it off."
Propositio (The Ask — 20 seconds)
"We’re asking to expand this group in two phases:
-
Wednesday 06-25, 09:00: Add server team members
-
Thursday 06-26, 09:00: Add all IS staff
Jason Landeros executes the PowerShell. Carlos Sandoval and Alejandro Mejia monitor for 24 hours after each phase. Tyler Horsford from Abnormal is on-call for both windows."
Confirmatio (Evidence — 90 seconds)
Use these proof points. Don’t rush — each one is a complete sentence:
-
"Six-week pilot. 21 users. Zero incidents. Zero false positive escalations."
-
"No policy changes — we’re adding members to an existing group. The four Defender policies are already live and scoped."
-
"No mail flow changes — ESA remains inline until MX cutover, which is a separate CR."
-
"No downtime — group membership propagation takes 15-30 minutes. Users don’t notice."
-
"Five-minute rollback — remove newly added members from the group. Policies revert immediately."
-
"During the pilot, we identified and mitigated a ghost-sender vulnerability through a transport rule — a Direct Send exploitation via our external MX that was allowing spoofed internal messages."
-
"We also identified DKIM is disabled across all domains — that’s a separate remediation item, not part of this CR."
Peroratio (Close — 15 seconds)
"Low-risk expansion of a proven pilot. Group membership change only. Rollback is instant. Requesting approval for Wednesday and Thursday implementation windows."
Anticipated Questions & Answers
| Question | Answer |
|---|---|
"What happens if Abnormal flags legitimate email?" |
"Abnormal is in read/write mode — it can auto-remediate. But we have 24-hour monitoring by Carlos and Alejandro after each phase. If we see false positives, we remove the affected users from the group within 5 minutes and they revert to ESA-only protection." |
"What’s the rollback?" |
"Remove the newly added members from |
"Why two phases instead of all at once?" |
"Risk containment. Server team is a smaller group — if we see any issues Wednesday, we don’t expand Thursday. If Wednesday is clean, Thursday is low risk." |
"Does this change mail flow?" |
"No. ESA remains inline — mail still flows through the Cisco MX. Abnormal connects via M365 Graph API and analyzes post-delivery. The MX cutover is a separate CR with a separate timeline." |
"What about the Graph API permissions?" |
"Already approved in the May 2026 CR. No permission changes in this CR — we’re only adding group members." |
"How do you know 21 users is enough to validate?" |
"We ran for six weeks across InfoSec — security analysts, engineers, and management. That’s the team most likely to receive targeted phishing and BEC. Zero incidents across that attack surface is strong evidence." |
"What did you find during the pilot?" |
"Three investigations: a ghost-sender vulnerability we mitigated with a transport rule, a spam bypass via user safe-sender lists (SCL override), and a Hoxhunt simulation delivery that we confirmed as intentional. All documented." |
"Is Abnormal replacing ESA completely?" |
"Yes, eventually. But ESA stays inline until MX cutover — that’s a separate CR after full org rollout. This CR only expands the pilot group." |
"What if someone complains about missing email?" |
"We run a message trace in Exchange Online to verify delivery status. If the message was quarantined by one of the four policies, we release it and add a policy exception if warranted. Carlos has the message trace commands ready." |
"Who owns this after implementation?" |
"InfoSec. Carlos Sandoval handles day-to-day Abnormal operations. I handle policy and integration. Sarah Clizer has executive oversight." |
Your Strengths Walking In
You are not "new to this." In six weeks you:
-
Built 5 Defender policy templates from scratch (anti-spam, anti-phish, anti-malware, safe attachments, safe links)
-
Discovered DKIM disabled across all domains — nobody else caught it
-
Investigated and mitigated a ghost-sender exploit (InfoGuard Labs research → transport rule deployment)
-
Ran three email security investigations (ghost-sender, spam bypass, Hoxhunt)
-
Wrote 907 lines of CR documentation with full PowerShell implementation, pre/post validation, and rollback
-
Mapped the entire ESA → EOP/MDO policy migration path
Network engineers understand authentication, trust chains, and policy enforcement. Email security is the same pattern — different protocol, same logic. You belong in that room.
Workflow addendum 2026-06-23 15:59
The dispositio summary needed revision. The workflow below documents two methods for performing a multi-line block replacement in an AsciiDoc file: one using Neovim, the other using sed with a heredoc. Both follow the same lifecycle: identify → backup → implement → verify → rollback.
Locate the target
Use grep -rn to return both the filename and line number in a single pass.
grep -rn '=== Dispositio' data/d001/proj*/abnor*
data/d001/projects/abnormal-security/partials/cr-tester-expansion-2026-06-18.adoc:156:=== Dispositio
Assign the path to a variable to avoid repetition in every subsequent command. The find filter excludes .bak files and constrains the match to partials/ to guarantee a single result.
TARGET=$(find data/d001/proj*/abnor* -name 'cr-tester-ex*.adoc' ! -name '*.bak' -path '*/partials/*')
echo "$TARGET"
Identify the line range
Print from the === Dispositio heading through the closing ==== delimiter to confirm the exact block boundaries. AsciiDoc admonition blocks use ==== for both open and close, so sed range addressing stops on the first match. awk with a delimiter counter handles this correctly.
awk 'NR==156{found=0} NR>=156{print; if(/^====$/) found++; if(found==2) exit}' "$TARGET"
=== Dispositio
[TIP]
====
Abnormal Security has been running in read/write mode for six weeks alongside Microsoft Defender for Office 365. Twenty-one pilot users across InfoSec and two server team members — the people most likely to be targeted by BEC and credential phishing. The platform performed exactly as expected throughout the pilot.
All protection is governed by a single Exchange security group. Four Defender policies — anti-spam, safe links, safe attachments, and the external sender modification rule — are scoped to that group. This change adds members to the group. That's it. No policy modifications, no mail flow changes, no configuration drift, no downtime.
We expand in two phases: remaining server team on Wednesday, all of Information Services on Thursday. Each phase gets 24 hours of monitored validation before the next. If anything surfaces, we remove the newly added members — policies revert within minutes. The pre-change state is captured as a JSON export for audit traceability.
This is a group membership change with instant rollback. The architecture makes it low-risk by design.
====
Backup
Create a backup before any modification. This is the rollback point regardless of method.
cp "$TARGET"{,.bak}
Implement — Method A: Neovim
Open the file at the target line.
v +156 "$TARGET"
Inside Neovim, get the ending line number and delete the range:
:156 " jump to line 156
/^====$/ " search forward — lands on opening ====
n " advance to closing ====
:. " note this line number (e.g. 176)
:156,176d " delete the entire block
O " open line above cursor — paste revised content
Implement — Method B: sed with heredoc
Write the replacement block to a temp file.
cat > /tmp/dispositio.adoc << 'EOF'
=== Dispositio
[TIP]
====
We've successfully piloted Abnormal Security alongside Microsoft Defender for Office 365 for six weeks with the team most likely to face targeted attacks — our InfoSec staff and two server team members. The platform delivered exactly what we expected, and we're ready to expand.
The rollout is straightforward. All protection runs through a single Exchange security group with four scoped Defender policies (anti-spam, safe links, safe attachments, and external sender modification). We're simply adding members to this existing group — no policy changes, no mail flow adjustments, no configuration drift, and no downtime.
We'll move in two phases:
* *Wednesday* — remaining server team
* *Thursday* — all of Information Services
Each phase gets 24 hours of monitored validation before the next. If anything surfaces, we remove the new members and revert within minutes — the protection policies remain unchanged, so there's no ripple effect. We've captured the pre-change state as a JSON export for audit traceability.
This is a group membership change with instant rollback baked into the design. The architecture itself makes it low-risk.
====
EOF
Get the ending line number dynamically, then delete and insert in a single pass.
END=$(awk 'NR>=157 && /^====$/{ if(++n==2){print NR; exit} }' "$TARGET")
sed -i -e "156,${END}d" -e "155r /tmp/dispositio.adoc" "$TARGET"
|
The |
Verify
Diff the dispositio block between the backup and the working copy using process substitution. This scopes the comparison to only the modified block without generating intermediate files.
diff <(awk 'NR==156{f=0} NR>=156{print; if(/^====$/) f++; if(f==2) exit}' "$TARGET".bak) \
<(awk 'NR==156{f=0} NR>=156{print; if(/^====$/) f++; if(f==2) exit}' "$TARGET")
If the file is tracked in git and has been committed at least once, the backup can be replaced with the last committed version:
diff <(git show HEAD:"$TARGET" | awk 'NR==156{f=0} NR>=156{print; if(/^====$/) f++; if(f==2) exit}') \
<(awk 'NR==156{f=0} NR>=156{print; if(/^====$/) f++; if(f==2) exit}' "$TARGET")
|
Process substitution ( |
Confirm the replacement block visually:
awk 'NR==156{found=0} NR>=156{print; if(/^====$/) found++; if(found==2) exit}' "$TARGET"
Rollback
If the diff reveals unintended changes, restore from backup.
cp "$TARGET"{.bak,}
If the file is git-tracked:
git checkout -- "$TARGET"
Clean up
Once the change is confirmed, remove the backup and temp file.
rm "$TARGET".bak /tmp/dispositio.adoc
tee docs/modules/ROOT/partials/worklog/daily-notes/2026-06/2026-06-23/dispositio-revision.adoc << 'ADOC'
==== Workflow addendum 2026-06-23 15:59
The dispositio summary needed revision. The workflow below documents two methods for performing a multi-line block replacement in an AsciiDoc file: one using Neovim, the other using sed with a heredoc. Both follow the same lifecycle: *identify → backup → implement → verify → rollback*.
===== Locate the target
Use `grep -rn` to return both the filename and line number in a single pass.
[source,bash]
grep -rn '=== Dispositio' data/d001/proj*/abnor*
.Output [source,console]
data/d001/projects/abnormal-security/partials/cr-tester-expansion-2026-06-18.adoc:156:=== Dispositio
Assign the path to a variable to avoid repetition in every subsequent command. The `find` filter excludes `.bak` files and constrains the match to `partials/` to guarantee a single result. [source,bash]
TARGET=$(find data/d001/proj*/abnor* -name 'cr-tester-ex*.adoc' ! -name '.bak' -path '/partials/*') echo "$TARGET"
===== Identify the line range Print from the `=== Dispositio` heading through the closing `====` delimiter to confirm the exact block boundaries. AsciiDoc admonition blocks use `====` for both open and close, so `sed` range addressing stops on the first match. `awk` with a delimiter counter handles this correctly. [source,bash]
awk 'NR==156{found=0} NR>=156{print; if(/^====$/) found++; if(found==2) exit}' "$TARGET"
.Output [source,asciidoc]
Dispositio
|
Abnormal Security has been running in read/write mode for six weeks alongside Microsoft Defender for Office 365. Twenty-one pilot users across InfoSec and two server team members — the people most likely to be targeted by BEC and credential phishing. The platform performed exactly as expected throughout the pilot. All protection is governed by a single Exchange security group. Four Defender policies — anti-spam, safe links, safe attachments, and the external sender modification rule — are scoped to that group. This change adds members to the group. That’s it. No policy modifications, no mail flow changes, no configuration drift, no downtime. We expand in two phases: remaining server team on Wednesday, all of Information Services on Thursday. Each phase gets 24 hours of monitored validation before the next. If anything surfaces, we remove the newly added members — policies revert within minutes. The pre-change state is captured as a JSON export for audit traceability. This is a group membership change with instant rollback. The architecture makes it low-risk by design. |
===== Backup Create a backup before any modification. This is the rollback point regardless of method. [source,bash]
cp "$TARGET"{,.bak}
===== Implement — Method A: Neovim Open the file at the target line. [source,bash]
v +156 "$TARGET"
Inside Neovim, get the ending line number and delete the range: [source,vim]
:156 " jump to line 156 /^====$/ " search forward — lands on opening ==== n " advance to closing ==== :. " note this line number (e.g. 176) :156,176d " delete the entire block O " open line above cursor — paste revised content
===== Implement — Method B: sed with heredoc Write the replacement block to a temp file. [source,bash]
cat > /tmp/dispositio.adoc << 'EOF' === Dispositio
|
We’ve successfully piloted Abnormal Security alongside Microsoft Defender for Office 365 for six weeks with the team most likely to face targeted attacks — our InfoSec staff and two server team members. The platform delivered exactly what we expected, and we’re ready to expand. The rollout is straightforward. All protection runs through a single Exchange security group with four scoped Defender policies (anti-spam, safe links, safe attachments, and external sender modification). We’re simply adding members to this existing group — no policy changes, no mail flow adjustments, no configuration drift, and no downtime. We’ll move in two phases:
Each phase gets 24 hours of monitored validation before the next. If anything surfaces, we remove the new members and revert within minutes — the protection policies remain unchanged, so there’s no ripple effect. We’ve captured the pre-change state as a JSON export for audit traceability. This is a group membership change with instant rollback baked into the design. The architecture itself makes it low-risk. |
EOF
Get the ending line number dynamically, then delete and insert in a single pass.
END=$(awk 'NR>=157 && /^====$/{ if(++n==2){print NR; exit} }' "$TARGET")
sed -i -e "156,${END}d" -e "155r /tmp/dispositio.adoc" "$TARGET"
|
The |
Verify
Diff the dispositio block between the backup and the working copy using process substitution. This scopes the comparison to only the modified block without generating intermediate files.
diff <(awk 'NR==156{f=0} NR>=156{print; if(/^====$/) f++; if(f==2) exit}' "$TARGET".bak) \
<(awk 'NR==156{f=0} NR>=156{print; if(/^====$/) f++; if(f==2) exit}' "$TARGET")
If the file is tracked in git and has been committed at least once, the backup can be replaced with the last committed version:
diff <(git show HEAD:"$TARGET" | awk 'NR==156{f=0} NR>=156{print; if(/^====$/) f++; if(f==2) exit}') \
<(awk 'NR==156{f=0} NR>=156{print; if(/^====$/) f++; if(f==2) exit}' "$TARGET")
|
Process substitution ( |
Confirm the replacement block visually:
awk 'NR==156{found=0} NR>=156{print; if(/^====$/) found++; if(found==2) exit}' "$TARGET"
Rollback
If the diff reveals unintended changes, restore from backup.
cp "$TARGET"{.bak,}
If the file is git-tracked:
git checkout -- "$TARGET"
Clean up
Once the change is confirmed, remove the backup and temp file.
rm "$TARGET".bak /tmp/dispositio.adoc
ADOC
// ── Work — Abnormal Security ────────────────────── === Abnormal Security — d000/d001 Restructure // Partial: Abnormal Security — d000/d001 Restructure Restructured `abnormal-security` to gold standard (STD-001/020/025). Split into personal (d000, full API) and team-facing (d001, PowerShell Exchange + GUI only). ==== Changes * **attributes.adoc:** Created from scratch — previous file was misnamed API queries. 50+ attributes with `ifdef::env-d001[]` + zeroed fallback: personnel (9), domains (6), portals (6), policy names (6), distribution groups, phase labels * **metadata.adoc + summary.adoc:** Created per STD-001 — phase status table, blockers (DKIM disabled, AutoForwarding on), investigation summary * **Assembler:** 8 sections with `leveloffset=+1` — Project Overview, Policy Migration, Policy Validation, Investigations, Change Requests, Audit Output, Operational * **Demoted:** `abnormal-security-playbook.adoc` and `esa-policy-migration-review.adoc` — stripped `= Title` headers and nested includes * **d001 stripped:** `api-queries.adoc`, `sentinel-integration.adoc`, `abnormal-security-queries.adoc` removed (Abnormal REST API + Sentinel KQL → d000 only) * **d000:** Full copy with all API content, encrypted at rest ==== Open / Close [source,bash]
d001 open abnormal-security d001 close abnormal-security
[source,bash]
d000 open abnormal-security d000 close abnormal-security
==== Build & Verify .d001 — team (PowerShell Exchange + GUI, no Abnormal API) [source,bash]
d001 open abnormal-security build-adoc $(find data/d001/projects/abnormal-security -maxdepth 1 -name 'abnormal-security.adoc') html --variant light-cyan -a env-d001 && firefox $(find data/d001 -path '*/abnormal-security/output/abnormal-security.html') &
.d001 — zeroed fallback (safe to share) [source,bash]
build-adoc $(find data/d001/projects/abnormal-security -maxdepth 1 -name 'abnormal-security.adoc') html --variant light-cyan && firefox $(find data/d001 -path '*/abnormal-security/output/abnormal-security.html') &
.d000 — personal (full Abnormal REST API + Sentinel KQL) [source,bash]
d000 open abnormal-security build-adoc $(find data/d000/projects/abnormal-security -maxdepth 1 -name 'abnormal-security.adoc') html --variant light-cyan -a env-d001 && firefox $(find data/d000 -path '*/abnormal-security/output/abnormal-security.html') &
==== Verification
* Zero build errors
* Zero `UNDEFINED` with `-a env-d001`
* Zero unresolved `\{attribute}` literals
* Zero Abnormal REST API / Sentinel KQL references in d001 rendered HTML
* 1.6M HTML with structured TOC (8 top-level sections)
==== Next
* CR-2026-06-25 — tester group expansion (this week)
* Present policy migration status at meeting
* DKIM remediation — all domains `Enabled: False`
* AutoForwardingMode policy change
==== Workflow addendum 2026-06-23 15:59
The dispositio summary needed revision. The workflow below documents two methods for performing a multi-line block replacement in an AsciiDoc file: one using Neovim, the other using sed with a heredoc. Both follow the same lifecycle: *identify → backup → implement → verify → rollback*.
===== Locate the target
Use `grep -rn` to return both the filename and line number in a single pass.
[source,bash]
grep -rn '=== Dispositio' data/d001/proj*/abnor*
.Output [source,console]
data/d001/projects/abnormal-security/partials/cr-tester-expansion-2026-06-18.adoc:156:=== Dispositio
Assign the path to a variable to avoid repetition in every subsequent command. The `find` filter excludes `.bak` files and constrains the match to `partials/` to guarantee a single result. [source,bash]
TARGET=$(find data/d001/proj*/abnor* -name 'cr-tester-ex*.adoc' ! -name '.bak' -path '/partials/*') echo "$TARGET"
===== Identify the line range Print from the `=== Dispositio` heading through the closing `====` delimiter to confirm the exact block boundaries. AsciiDoc admonition blocks use `====` for both open and close, so `sed` range addressing stops on the first match. `awk` with a delimiter counter handles this correctly. [source,bash]
awk 'NR==156{found=0} NR>=156{print; if(/^====$/) found++; if(found==2) exit}' "$TARGET"
.Output [source,asciidoc]
Dispositio
|
Abnormal Security has been running in read/write mode for six weeks alongside Microsoft Defender for Office 365. Twenty-one pilot users across InfoSec and two server team members — the people most likely to be targeted by BEC and credential phishing. The platform performed exactly as expected throughout the pilot. All protection is governed by a single Exchange security group. Four Defender policies — anti-spam, safe links, safe attachments, and the external sender modification rule — are scoped to that group. This change adds members to the group. That’s it. No policy modifications, no mail flow changes, no configuration drift, no downtime. We expand in two phases: remaining server team on Wednesday, all of Information Services on Thursday. Each phase gets 24 hours of monitored validation before the next. If anything surfaces, we remove the newly added members — policies revert within minutes. The pre-change state is captured as a JSON export for audit traceability. This is a group membership change with instant rollback. The architecture makes it low-risk by design. |
===== Backup Create a timestamped backup before any modification. This is the rollback point. [source,bash]
cp "$TARGET"{,.bak-$(date +%Y%m%dT%H%M%S)}
===== Implement — Method A: Neovim Open the file at the target line. [source,bash]
v +156 "$TARGET"
Inside Neovim, get the ending line number and delete the range: [source,vim]
:156 " jump to line 156 /^====$/ " search forward — lands on opening ==== n " advance to closing ==== :. " note this line number (e.g. 176) :156,176d " delete the entire block O " open line above cursor — paste revised content
===== Implement — Method B: sed with heredoc Write the replacement block to a temp file. [source,bash]
cat > /tmp/dispositio.adoc << 'EOF' === Dispositio
|
We’ve successfully piloted Abnormal Security alongside Microsoft Defender for Office 365 for six weeks with the team most likely to face targeted attacks — our InfoSec staff and two server team members. The platform delivered exactly what we expected, and we’re ready to expand. The rollout is straightforward. All protection runs through a single Exchange security group with four scoped Defender policies (anti-spam, safe links, safe attachments, and external sender modification). We’re simply adding members to this existing group — no policy changes, no mail flow adjustments, no configuration drift, and no downtime. We’ll move in two phases:
Each phase gets 24 hours of monitored validation before the next. If anything surfaces, we remove the new members and revert within minutes — the protection policies remain unchanged, so there’s no ripple effect. We’ve captured the pre-change state as a JSON export for audit traceability. This is a group membership change with instant rollback baked into the design. The architecture itself makes it low-risk. |
EOF
Get the ending line number dynamically, then delete and insert in a single pass.
END=$(awk 'NR>=157 && /^====$/{ if(++n==2){print NR; exit} }' "$TARGET")
sed -i -e "156,${END}d" -e "155r /tmp/dispositio.adoc" "$TARGET"
|
The |
Verify
Diff the backup against the modified file to confirm only the intended block changed.
diff "$TARGET".bak-* "$TARGET"
Confirm the replacement block visually:
awk 'NR==156{found=0} NR>=156{print; if(/^====$/) found++; if(found==2) exit}' "$TARGET"
Rollback
If the diff reveals unintended changes, restore from backup.
cp "$TARGET".bak-* "$TARGET"
Once confirmed, clean up the backup and temp file.
rm "$TARGET".bak-* /tmp/dispositio.adoc
ISE API Scripts — Inventory, Endpoint Profile, DataConnect
ISE API Scripts Reference
Shell scripts for ISE endpoint discovery, switch location, and operational stats. All require dsource credentials and helper functions loaded.
Load Prerequisites
dsource d001 dev/network/ise
Paste the ERS, MnT, and DataConnect helpers from shared partials:
cat data/shared/partials/ise-ers/helpers.adoc
cat data/shared/partials/ise-mnt/helpers.adoc
cat data/shared/partials/ise-dataconnect/helpers.adoc
Full ISE Inventory — dACLs, Profiles, Policy Sets, Rules, Sessions
source data/shared/scripts/ise-inventory.sh | tee /tmp/ise-inventory-$(date +%F).txt
ise-inventory.sh (click to expand)
Unresolved include directive in modules/ROOT/partials/worklog/daily-notes/2026-06/2026-06-23/ise-api-scripts.adoc - include::example$ise/ise-inventory-summary.sh[]
Outputs:
-
dACLs — full list with count
-
Authorization profiles — full list
-
Guest-related dACLs and authz profiles (filtered by
guest|cwa) -
Policy sets (OpenAPI) — name, state, ID
-
Authentication rules per policy set — name, state, identity store
-
Authorization rules per policy set — name, state, profiles
-
MnT active session count + ISE version
Single Endpoint Deep-Dive — 3-API Combined (DataConnect + ERS + MnT)
Replicates ~95% of the ISE GUI "General Attributes" page for a single MAC.
MAC="14:F6:D8:7B:31:80" . $(find data -name 'full-endpoint-profile.sh')
full-endpoint-profile.sh — what it returns
-
DataConnect — 5-table JOIN: RADIUS auth history, accounting, NAD, endpoint identity, profiler
-
ERS — endpoint details: identity group, static assignment, custom attributes, profile
-
MnT — active session: auth method, NAS IP/port, session ID, posture, dACL applied
-
Auth history — last 10 authentications with timestamp, method, status, authz profile
Output saved to /tmp/ise-endpoint-profile/
DataConnect SQL Queries — Bulk Reporting
Run any .sql file against ISE DataConnect (Oracle, port 2484):
dc-run-sql $(find data -name 'migration-executive-summary.sql') | tee /tmp/exec-summary.json | jq '.'
Available SQL files
find data/d001/projects/mschapv2-migration/sql -name '*.sql' -exec basename {} \; | sort
Key queries:
-
migration-executive-summary.sql— device counts, migration %, auth totals (30d) -
migration-trend-weekly.sql— week-over-week protocol distribution (90d) -
migration-wave-summary.sql— per-wave device counts and failure rates -
migration-device-detail.sql— one row per MAC with owner, location, switches -
migration-stale-devices.sql— devices not seen in 30+ days -
linux-inventory-summary.sql— Linux endpoint count by profile and identity group -
linux-inventory-ise-profiler.sql— Linux endpoints via ISE profiler classification -
linux-inventory-ise-radius.sql— Linux endpoints by RADIUS auth behavior
Quick Commands
netapi ise get-endpoints --group "Linux-Research-Workstations" --all -f json | jq -r '.[].mac'
netapi ise get-endpoint 14:F6:D8:7B:31:80 -f json | jq '{groupId, identityGroup, mac, staticGroupAssignment}'
netapi ise dc auth-history 14:F6:D8:7B:31:80 -f json | jq '.[] | {time, status, authz_profile}'
netapi ise mnt session 14:F6:D8:7B:31:80
netapi ise mnt coa 14:F6:D8:7B:31:80
netapi ise get-policy-sets -f json | jq -r '.[].name'
ISE Session Investigation — Home Lab
ISE Session Investigation — Home Lab
Investigated endpoint visibility on d000 ISE using the ise-api-reference scripts. These are the proven, battle-tested scripts at data/d000/infra/ise-api-reference/scripts/.
Prerequisites
dsource d000 dev/network/ise
Active Sessions
bash $(find data/d000/infra/ise-api-reference/scripts/mnt -name 'active-sessions.sh') table
MAC IP USER NAS 64:32:A8:C4:C7:19 64:32:A8:C4:C7:19 10.50.1.40 A0:59:50:C9:92:EE A0:59:50:C9:92:EE 10.50.1.40 56:38:C0:55:C5:8F 56:38:C0:55:C5:8F 10.50.1.40 24:FD:FA:04:A8:33 24:FD:FA:04:A8:33 10.50.1.40
Session Detail — Razer Workstation
bash $(find data/d000/infra/ise-api-reference/scripts/mnt -name 'session-detail.sh') 64:32:A8:C4:C7:19
{
"user_name": "64:32:A8:C4:C7:19",
"nas_ip_address": "10.50.1.40",
"calling_station_id": "64:32:A8:C4:C7:19",
"cpmsession_id": "2801320A00000161DE36DEF9",
"destination_ip_address": "10.50.1.20",
"acs_server": "ise-01",
"auth_acs_timestamp": "2026-06-18T21:49:59.690-07:00",
"nas_port_id": "capwap_90000002",
"endpoint_policy": "Intel-Device",
"acct_status_type": "Start"
}
Finding: ThinkPad P16g Not in ISE Sessions
e0:d5:5d:6c:e1:66 (P16g wired MAC) — no active session in MnT, no auth history. ERS shows the endpoint exists (profiler classified it) but identityStore: ipsk-mgr-01 with group Profiled. The workstation connects upstream of the ISE-controlled switch — port likely has no 802.1X/MAB configured, or it’s on a trunk/management VLAN bypassing ISE.
Available Scripts
| Script | Usage |
|---|---|
|
|
|
|
|
|
|
|
|
|
All scripts auto-source lib/ise-common.sh — no manual function loading. Portable across d000/d001 via dsource.
WLC 9800 — RADIUS Accounting Audit
WLC 9800 — RADIUS Accounting Audit
ISE MnT returns framed_ip_address: null for all sessions on NAS 10.50.1.40 (9800-WLC-01). Full audit of AAA, RADIUS, accounting, credentials, wireless profiles, HA/SSO, and AP management.
IOS-XE Output Modifiers
| Modifier | Unix Analog | Regex | Use Case |
|---|---|---|---|
|
|
|
Line-match — anchors, alternation, character classes |
|
|
|
Inverse match — strip comments from running-config |
|
|
|
Full config block from match to next top-level line |
|
|
|
From first match to EOF |
|
|
|
Count matching lines |
|
|
|
Display + save to flash simultaneously |
|
|
|
Save to flash (suppresses display) |
|
|
|
Append to existing file on flash |
|
|
|
XML-structured output |
IOS-XE regex uses . for "anything" (not alone — that’s glob). The $ anchor fails on most show output due to trailing whitespace. Use unanchored suffix patterns instead.
|
Capturing Audit Output to Workstation
Three methods for capturing IOS-XE command output to local files — from simplest to most automated.
Method 1: SSH + Unix tee (interactive)
SSH with forced TTY, pipe through tee to capture everything to a local file while watching live:
ssh -tt 9800-wlc-01 2>&1 | tee /tmp/wlc-audit-2026-06-23.txt
Log in, paste audit commands, exit. The entire session — prompts, commands, output — lands in /tmp. Clean control characters after:
col -b < /tmp/wlc-audit-2026-06-23.txt | sed '/^ *$/d' > /tmp/wlc-audit-clean.txt
col -b strips backspaces and control sequences. sed '/^ *$/d' removes blank lines.
Redact credentials before storing:
sed -i 's/\(key 7\|password 0\|secret 0\|ascii 0\) .*/\1 <REDACTED>/g' /tmp/wlc-audit-clean.txt
Method 2: script (full terminal recording)
script captures every byte — including ANSI colors, cursor movement, timing. Raw but complete:
script /tmp/wlc-session-2026-06-23.txt
ssh 9800-wlc-01
Paste commands, exit the WLC, then exit the script session. Clean with col -b.
Method 3: IOS-XE tee to flash (on-device)
Save output directly to WLC flash — useful for pre-change baselines:
show running-config | tee flash:pre-audit-2026-06-23.txt
Diff after changes:
show archive config differences flash:pre-audit-2026-06-23.txt system:running-config
Copy from flash to workstation via SCP:
scp 9800-wlc-01:flash:pre-audit-2026-06-23.txt /tmp/
Key Differences
| Method | Captures | Credentials | Automation |
|---|---|---|---|
SSH + |
Full session including prompts |
Visible in file — redact after |
Paste commands manually |
|
Raw terminal bytes (ANSI included) |
Visible — redact after |
Paste commands manually |
IOS-XE |
Single command output only |
On-device flash — access-controlled |
Per-command, not bulk |
SSH + tee is the sweet spot — live visibility, local capture, paste the full audit script in one shot.
Full Audit Script
28-check audit script at data/d000/infra/wlc-api-reference/scripts/cli/wlc-audit.txt. View it, then paste into a WLC SSH session:
cat $(find data/d000/infra/wlc-api-reference/scripts/cli -name 'wlc-audit.txt')
Key regex patterns:
| Pattern | What it catches |
|---|---|
|
Any Type 0 plaintext credential — |
|
Full credential type inventory — every credential with its obfuscation type |
|
Device tracking + DHCP snooping — root cause check for |
|
Accounting update method — |
|
AES master key status — prerequisite for Type 8 mgmtuser encryption |
|
Active interfaces — GigE, TenGig, VLAN, Loopback |
|
HA/SSO redundancy state |
|
VLAN database with names |
|
Per-AP config — tag assignments by MAC prefix |
Audit Output — 2026-06-23
Captured via ssh -tt 9800-wlc-01 2>&1 \| tee /tmp/wlc-audit-2026-06-23.txt.
01: Platform
Cisco IOS XE Software, Version 17.15.04d
C9800-CL (VXE) processor, 12260012K/3075K memory
9800-WLC-01 uptime: 10 weeks, 1 day, 11 hours, 48 minutes
Control processor uptime: 3 weeks, 2 days, 9 hours, 14 minutes
License: AIR Network Advantage + AIR DNA Advantage
Configuration register: 0x2102
02: AAA + RADIUS
aaa new-model
aaa group server radius RADIUS_SERVER_GROUP_DAY0
aaa group server radius ISE-GROUP
aaa authentication login authentication_login_day0 group RADIUS_SERVER_GROUP_DAY0
aaa authentication dot1x authentication_dot1x_day0 group RADIUS_SERVER_GROUP_DAY0
aaa authentication dot1x ISE-Auth group ISE-GROUP
aaa authorization exec default local
aaa authorization network default group radius
aaa authorization network ISE-AuthZ group ISE-GROUP
aaa accounting update newinfo
aaa accounting identity ISE-ACCT start-stop group ISE-GROUP
aaa accounting network ISE-Acct start-stop group ISE-GROUP
aaa server radius dynamic-author
aaa session-id common
radius server ISE-1
radius server ISE-2
03: RADIUS Servers
radius server ISE-1
address ipv4 10.50.1.20 auth-port 1812 acct-port 1813
timeout 5
retransmit 3
key 7 <REDACTED>
radius server ISE-2
address ipv4 10.50.1.21 auth-port 1812 acct-port 1813
timeout 5
retransmit 3
key 7 <REDACTED>
04: RADIUS Server Groups
aaa group server radius RADIUS_SERVER_GROUP_DAY0
server name RADIUS_SERVER_DAY0_1
aaa group server radius ISE-GROUP
server name ISE-1
server name ISE-2
ip radius source-interface Vlan100
deadtime 5
mac-delimiter hyphen
05: CoA / Dynamic Authorization
aaa server radius dynamic-author
client 10.50.1.20 server-key 7 <REDACTED>
client 10.50.1.21 server-key 7 <REDACTED>
06: Policy Profiles
wireless profile policy POLICY-DOMUS_IoT
aaa-override
vlan 40
no shutdown
wireless profile policy POLICY-DOMUS_SECURE
aaa-override
aaa-policy Domus-AAA-Policy
accounting-list ISE-ACCT
nac
vlan DATA_VLAN
no shutdown
wireless profile policy default-policy-profile
aaa-override
autoqos mode voice
description "default policy profile"
service-policy input platinum-up
service-policy output platinum
vlan VLAN0100
no shutdown
07: WLAN Profiles
wlan Domus-IoT 5 Domus-IoT
mac-filtering ISE-AuthZ
security wpa psk set-key ascii 0 <REDACTED>
no security wpa akm dot1x
security wpa akm psk
no shutdown
wlan Domus-Secure 4 Domus-Secure
security dot1x authentication-list ISE-Auth
no shutdown
08: AP Profile
ap profile default-ap-profile
description "default ap profile"
mgmtuser username admin password 0 <REDACTED> secret 0 <REDACTED>
09: Device Tracking + DHCP Snooping
Empty — not configured.
11: Plaintext Credential Scan
show running-config | include ( 0 .*[A-Za-z]) security wpa psk set-key ascii 0 <REDACTED>
mgmtuser username admin password 0 <REDACTED> secret 0 <REDACTED>
Two Type 0 credentials remain. Require AES master key for encryption.
12: Full Credential Inventory
show running-config | include (key|secret|password|set-key).*(0|7|8|9) client 10.50.1.20 server-key 7 <REDACTED>
client 10.50.1.21 server-key 7 <REDACTED>
database archive pkcs12 password 7 <REDACTED>
password 7 <REDACTED>
username admin privilege 15 secret 9 $9$...
username apiuser privilege 15 secret 9 $9$...
key 7 <REDACTED>
key 7 <REDACTED>
security wpa psk set-key ascii 0 <REDACTED>
mgmtuser username admin password 0 <REDACTED> secret 0 <REDACTED>
Credential type distribution: Type 9 (scrypt) for local users ✅, Type 7 for RADIUS/CoA keys ✅, Type 0 for PSK + mgmtuser ❌.
13: Encryption Status
service password-encryption ✅ Enabled
key config-key password-encrypt ❌ Not configured
password encryption aes ❌ Not configured
14: Active Interfaces
GigabitEthernet1 is up, line protocol is up
GigabitEthernet3 is up, line protocol is up
Vlan100 is up, line protocol is up , Autostate Enabled
15: VLANs
vlan 10 DATA_VLAN
vlan 20 VOICE_VLAN
vlan 30 GUEST_VLAN
vlan 40 IOT_VLAN
vlan 100 MANAGEMENT_VLAN
vlan 110 SECURITY_VLAN
vlan 120 SERVICES_VLAN
vlan 666 NATIVE_VLAN_UNUSED
vlan 999 CRITICAL_AUTH_VLAN
16: HA/SSO
Hardware Mode = Duplex
Configured Redundancy Mode = sso
Operating Redundancy Mode = sso
Maintenance Mode = Disabled
Current Software state = ACTIVE
Uptime in current state = 3 weeks, 2 days, 4 hours, 26 minutes
Current Software state = STANDBY HOT
Uptime in current state = 3 weeks, 2 days, 4 hours, 21 minutes
17: AP Summary
Number of APs: 1
C9130AX-01 CW9166I-B 8c88.812a.0000 10.50.10.117 Registered
18: AP Tag Assignments
ap 78bc.1a36.82c0
policy-tag HomeRF-Tag
ap dc8c.3796.20a6
policy-tag TAG-DOMUS
rf-tag TAG-DOMUS-RF
site-tag TAG-DOMUS-SITE
19: Wireless Tags
wireless tag site TAG-DOMUS-SITE
description "Domus site settings"
wireless tag policy TAG-DOMUS
wlan Domus-IoT policy POLICY-DOMUS_IoT
wlan Domus-Secure policy POLICY-DOMUS_SECURE
wireless tag policy default-policy-tag
wlan Domus-IoT policy POLICY-DOMUS_IoT
wlan Domus-Secure policy POLICY-DOMUS_SECURE
wireless tag rf TAG-DOMUS-RF
24ghz-rf-policy Low_Client_Density_rf_24gh
5ghz-rf-policy Low_Client_Density_rf_5gh
20: NTP
ntp server 10.50.1.1
ntp server 129.6.15.28
ntp server 132.163.96.5
Clock is synchronized, stratum 3, reference is 10.50.1.1
21: Logging
logging console warnings
logging monitor errors
logging host 10.50.1.134
22: HTTP/HTTPS
ip http server
ip http authentication local
ip http secure-server
ip http secure-trustpoint WLC-MGMT-CERT
23: Certificates
| Trustpoint | CN / Purpose | Status |
|---|---|---|
TP-self-signed-1917173558 |
IOS-Self-Signed-Certificate |
✅ Issued |
SLA-TrustPoint |
Cisco Licensing Root CA |
✅ CA only |
WLC_CA |
CA-vWLC_WLC-LAB |
✅ CA only |
WLC-LAB_WLC_TP |
WLC-LAB_WLC_TP (exportable) |
✅ Issued |
HOME-ROOT-CA |
HOME-ROOT-CA |
⚠️ CA not authenticated |
WLC-MGMT-CERT |
9800-wlc-01.inside.domusdigitalis.dev |
✅ Issued — Vault CA, used for HTTPS |
Findings
| Check | Status | Finding |
|---|---|---|
AAA new-model |
PASS |
Enabled |
dot1x auth |
PASS |
|
Network authz |
PASS |
|
RADIUS server group |
PASS |
ISE-GROUP: both servers, source Vlan100, deadtime 5, mac-delimiter hyphen |
Accounting (POLICY-DOMUS_SECURE) |
PASS |
|
Accounting (POLICY-DOMUS_IoT) |
FAIL |
No |
Accounting (default-policy-profile) |
FAIL |
No |
Accounting update |
PARTIAL |
|
Device tracking |
FAIL |
Not configured — WLC cannot learn client IPs |
DHCP snooping |
FAIL |
Not configured — WLC cannot intercept DHCP assignments |
NAC (POLICY-DOMUS_SECURE) |
PASS |
|
NAC (POLICY-DOMUS_IoT) |
FAIL |
No |
Session timeout |
PARTIAL |
Not configured — no forced reauth interval |
CoA |
PASS |
Both ISE nodes registered as dynamic-author clients |
RADIUS servers |
PASS |
ISE-1 (10.50.1.20) + ISE-2 (10.50.1.21), ports 1812/1813 |
RADIUS shared secret |
PASS |
Converted to |
CoA server-key |
PASS |
Converted to |
WPA PSK (Domus-IoT) |
FAIL |
Plaintext |
AP mgmtuser |
FAIL |
Plaintext |
Local users |
PASS |
|
NTP |
PASS |
Synchronized stratum 3, reference 10.50.1.1 |
Logging |
PASS |
Syslog to 10.50.1.134, console warnings, monitor errors |
SNMP |
PARTIAL |
Not configured |
HTTPS |
PASS |
Vault-issued cert |
HA/SSO |
PASS |
Active/Standby HOT, SSO operational, 3+ weeks uptime |
APs |
PASS |
1 registered — C9130AX-01 (CW9166I-B) at 10.50.10.117 |
Root Cause — framed_ip_address: null
Device tracking is not configured. The 9800 never learns client IP addresses post-DHCP, so aaa accounting update newinfo has no IP to report in Interim-Update records. ISE only receives the Accounting-Start (pre-DHCP) and never gets framed_ip_address.
Lesson Learned: Credential Exposure
Four classes of credentials found during audit:
| Credential | Location | Resolution |
|---|---|---|
RADIUS shared secret |
|
■■■■■■■■■■ 10/10 — |
CoA server-key |
|
■■■■■■■■■■ 10/10 — |
AP mgmtuser |
|
❌ Requires |
WPA PSK |
|
❌ Requires AES master key |
service password-encryption reaches RADIUS keys and line passwords (→ Type 7) but does not reach mgmtuser or set-key ascii. Those require the AES master key infrastructure.
Type 8 in mgmtuser context means AES encryption (master-key-dependent), not PBKDF2-SHA256 used by username secret 8.
Credential Encryption Reference
| Type | Algorithm | Reversible | Scope |
|---|---|---|---|
0 |
Plaintext |
N/A |
Never acceptable |
7 |
Vigenère |
Yes — trivially |
RADIUS keys, line passwords. Enabled by |
8 (global) |
PBKDF2-SHA256 |
No |
|
8 (mgmtuser) |
AES |
With master key |
AP credentials. Requires |
9 |
scrypt |
No |
|
Master Key Configuration — Future CR
Enabling AES password encryption converts all Type 0 and Type 7 credentials to Type 6/8. Global change affecting RADIUS keys, PSK, mgmtuser, and all stored credentials.
Prerequisites:
-
Store master key in secrets manager — unrecoverable if lost
-
Baseline to flash:
show running-config \| tee flash:pre-masterkey.txt -
Test in maintenance window — AP reconnection behavior is version-dependent
-
Document as CR per STD-005
Reference:
Remediation
| Priority | Action |
|---|---|
P0 ✅ |
RADIUS shared secret — converted from |
P0 ✅ |
CoA server-key — converted to |
P1 |
Enable |
P1 |
Add |
P2 |
Add |
P2 |
Configure |
P2 |
Add |
P3 |
Configure |
P3 |
Configure SNMP for monitoring. |
net-push — Multi-Device Network CLI Tool
net-push — Multi-Device Network CLI Tool
Built a hardened, multi-device CLI automation tool for pushing commands to network devices via SSH with full output capture. Evolved from a single-purpose wlc-push.sh into a generalized net-push.sh that works with any SSH-managed device.
Problem
Interacting with network devices (WLC, switches, firewalls) requires:
-
Typing commands from memory or pasting from notes — error-prone
-
No local capture of output — evidence lost
-
Credentials managed ad-hoc — password in clipboard, shell history risk
-
No safety net for destructive commands —
write memoryis one typo away
Solution
net-push.sh — a single tool that:
-
Reads device inventory from YAML
-
Pulls credentials from gopass at runtime via file descriptor (never in env/argv/disk)
-
Sends command files over SSH via pexpect
-
Captures full output to timestamped files with 600 permissions
-
Auto-redacts credentials in output
-
Blocks destructive commands unless operator types the device hostname
-
Verifies its own integrity via SHA-256 before touching credentials
Architecture
data/d000/infra/scripts/cli/
├── net-push.sh # Multi-device push tool (chmod 700)
├── devices.yaml # Device inventory — hosts, gopass paths, prompts
└── .net-push.sha256 # Integrity hash (auto-generated)
data/d000/infra/wlc-api-reference/scripts/cli/
├── wlc-audit.txt # 28-check WLC audit
├── wlc-push.sh # Original single-device tool (superseded)
└── patches/
├── accounting-fix.txt
└── write-memory.txt
Device Inventory
devices:
9800-wlc-01:
host: 9800-wlc-01
gopass: v3/domains/d000/network/devices/9800-wlc-01/ssh/admin
prompt: "#"
type: ios-xe
destructive: "write memory|write mem|wr mem|wr$|erase|reload|delete"
vyos-01:
host: vyos-01
gopass: v3/domains/d000/network/devices/firewall/vyos-01/vyos-admin
prompt: "\\$"
type: vyos
destructive: "commit|save|reboot|delete"
Adding a device: one YAML block. The prompt field is regex — # for IOS-XE, \\$ for VyOS/Linux.
Usage
# List all devices
bash $(find data/d000/infra/scripts/cli -name 'net-push.sh') --list
# Dry run — preview commands without sending
bash $(find data/d000/infra/scripts/cli -name 'net-push.sh') 9800-wlc-01 wlc-audit.txt --dry-run
# Run audit — all show commands, read-only
bash $(find data/d000/infra/scripts/cli -name 'net-push.sh') 9800-wlc-01 wlc-audit.txt
# Apply a patch — destructive command requires hostname confirmation
bash $(find data/d000/infra/scripts/cli -name 'net-push.sh') 9800-wlc-01 patches/write-memory.txt
Security Model
| Layer | Mechanism |
|---|---|
Credential isolation |
gopass → fd 3 → Python |
Script integrity |
SHA-256 hash verified before any credential fetch. Tampered script aborts with diff. |
Destructive gate |
Commands matching |
Output permissions |
|
Output redaction |
|
fd cleanup |
|
GPG boundary |
gopass requires GPG unlock. GPG key protected by passphrase + YubiKey (if configured). |
Disk encryption |
LUKS full-disk. |
Destructive Command Flow
$ net-push 9800-wlc-01 patches/write-memory.txt
WARNING: Patch contains destructive commands for 9800-wlc-01 (ios-xe):
2: write memory
Type the device name to confirm [9800-wlc-01]: _
Wrong input → aborted. Correct input → proceeds with credential fetch and execution.
Work Usage Considerations
For use at CHLA or any enterprise environment:
| Consideration | Status |
|---|---|
Automated SSH to production devices |
Verify with security team — some orgs require all changes through PAM/change tools |
Credential storage |
gopass + GPG is strong but may not satisfy CyberArk/PAM requirements |
Audit trail |
Local |
CAB process |
Config changes must follow STD-021 (iTrack change records). net-push captures are evidence, not approval. |
Network segmentation |
Workstation must have SSH access to target devices — verify ACLs |
Recommendation: use for audit/read-only commands at work without restriction. Use for config changes only within an approved change window with CAB approval (STD-021).
Evolution
| Version | Milestone |
|---|---|
v1 |
|
v2 |
|
v3 |
|
ISE MnT — Session Report Script
ISE MnT — Session Report Script
Created session-report.sh — joins active sessions with per-session detail to produce enriched tables. Demonstrates jq join pattern: loop source → fetch detail → extract + flatten → tabulate.
Formats
# Full enriched table
bash $(find data/d000/infra/ise-api-reference/scripts/mnt -name 'session-report.sh') table
# JSON array — all fields, pipeable to more jq
bash $(find data/d000/infra/ise-api-reference/scripts/mnt -name 'session-report.sh') json
# Compact — MAC, IP, accounting status
bash $(find data/d000/infra/ise-api-reference/scripts/mnt -name 'session-report.sh') summary
# Split other_attr_string into key-value pairs for one MAC
bash $(find data/d000/infra/ise-api-reference/scripts/mnt -name 'session-report.sh') attrs E0:D5:5D:6C:E1:66
Sample Output — table
MAC IP USER NAS ACCT PROFILE 64:32:A8:C4:C7:19 — 64:32:A8:C4:C7:19 10.50.1.40 Start Intel-Device A0:59:50:C9:92:EE — A0:59:50:C9:92:EE 10.50.1.40 Start Windows10-Workstation 56:38:C0:55:C5:8F — 56:38:C0:55:C5:8F 10.50.1.40 Start Unknown 24:FD:FA:04:A8:33 — 24:FD:FA:04:A8:33 10.50.1.40 Start Unknown
MAB sessions still show — for IP — these are stale pre-fix sessions. New sessions after re-auth will have framed_ip_address populated.
jq Patterns Used
| Pattern | What it does |
|---|---|
|
Extract fields → tab-separated for |
|
Slurp individual JSON objects into array |
|
Inject shell variable into jq filter |
|
Regex named capture — split |
|
Null coalescing — default for missing fields |
P16g Session Detail — Post-Fix Verification
bash $(find data/d000/infra/ise-api-reference/scripts/mnt -name 'session-detail.sh') E0:D5:5D:6C:E1:66 | jq '{user: .sessionParameters.user_name, ip: .sessionParameters.framed_ip_address, nas: .sessionParameters.nas_ip_address, acct: .sessionParameters.acct_status_type, profile: .sessionParameters.endpoint_policy}'
framed_ip_address populated after radius-profiling + dot11-tlv-accounting fix{
"user": "modestus-p16g.inside.domusdigitalis.dev",
"ip": "10.50.1.204",
"nas": "10.50.1.40",
"acct": "Stop",
"profile": "Intel-Device"
}
lsof Diagnostics — Process & File Handle Investigation
lsof Diagnostics — Process & File Handle Investigation
Created a set of diagnostic scripts using lsof for investigating open files, network connections, serial console sessions, and process file descriptors. Each script is a single concern — one tool, one question answered.
Scripts Created
| Script | Purpose | Usage |
|---|---|---|
|
Identify processes holding serial/console ports (picocom, minicom, screen) |
|
|
Network connections by state, port, or process name |
|
|
All files open by a process — name or PID (command substitution: |
|
|
Who has a file/device/directory open |
|
Picocom Console Session
Active serial console to LAB-3560CX-01 via USB-to-serial adapter:
pgrep -a picocom
390232 picocom -b 9600 /dev/ttyUSB0
Investigating file descriptors held by picocom:
bash $(find data/d000/infra/scripts/diagnostics -name 'lsof-by-pid.sh') picocom
Command Substitution Patterns
lsof-by-pid.sh demonstrates the key pattern — resolve a name to PID via command substitution, then feed to lsof:
# Name → PIDs → lsof (what the script does internally)
lsof -p $(pgrep -d, picocom)
# Multiple PIDs joined with comma
lsof -p $(pgrep -d, kitty)
# Inline PID resolution for network filtering
lsof -i -a -p $(pgrep ssh-agent)
lsof Network Filters
# ESTABLISHED connections only
bash $(find data/d000/infra/scripts/diagnostics -name 'lsof-network.sh') established
# What's listening on RADIUS port
bash $(find data/d000/infra/scripts/diagnostics -name 'lsof-network.sh') 1812
# All SSH connections
bash $(find data/d000/infra/scripts/diagnostics -name 'lsof-network.sh') ssh
# Connections by any process name
bash $(find data/d000/infra/scripts/diagnostics -name 'lsof-network.sh') claude
Directory Structure
data/d000/infra/scripts/diagnostics/
├── lsof-serial.sh # Serial/console port ownership
├── lsof-network.sh # Network connections by state/port/process
├── lsof-by-pid.sh # All file handles by process (name or PID)
└── lsof-files.sh # Who holds a file/device/directory
Ollama — Model Architecture & Shell Integration
Ollama — Model Architecture & Shell Integration
RTX 5090 (24GB VRAM) + i9-275HX (24 cores) + 62GB RAM.
Previously: 4 base models, no Modelfiles, broken aliases (quick/coder/analyst didn’t exist).
Model Lineup
| Alias | Model Name | Base | Temp | Purpose |
|---|---|---|---|---|
|
quick |
qwen3:30b-a3b |
0.7 |
Fast general Q&A, brainstorming |
|
coder |
deepseek-r1:32b |
0.3 |
Code review, debugging, scripts, regex |
|
analyst |
deepseek-r1:32b |
0.4 |
Log analysis, data investigation, incidents |
|
neteng |
deepseek-r1:32b |
0.2 |
ISE config review, Cisco CLI, dACL design |
|
docs |
qwen3:30b-a3b |
0.5 |
AsciiDoc, Antora standards, technical writing |
Domain Functions
-
ise-review <file>— review ISE config/policy export -
ise-debug [file]— analyze RADIUS auth failures (file or stdin) -
ise-api "description"— generate ISE ERS API curl commands
-
adoc-review <file>— check AsciiDoc standards compliance -
adoc-draft "description"— draft an AsciiDoc partial
-
analyze <file>— auto-detect file type, analyze with analyst model -
aisum— pipe long output, get concise summary
-
aicommit— generate commit message from staged diff -
aidiff [ref]— explain a diff -
aifix "error"— suggest fix for error message
-
aih,aich,aiah,ainh,aidh— heredoc to each model
-
aigpu— GPU VRAM usage + loaded models -
aikill— unload all models to free VRAM -
airebuild— recreate all models from Modelfiles
Architecture
dots-quantum/ollama/.config/ollama/modelfiles/
├── quick.Modelfile # qwen3:30b-a3b — general
├── coder.Modelfile # deepseek-r1:32b — code
├── analyst.Modelfile # deepseek-r1:32b — data/logs
├── neteng.Modelfile # deepseek-r1:32b — ISE/network
├── docs.Modelfile # qwen3:30b-a3b — AsciiDoc
└── setup.sh # Creates all models from Modelfiles
Modelfiles are stowable via dq-stow ollama. System prompts embed real environment context (IPs, VLANs, domain, cert paths, API patterns).
Test Checklist
-
dq-stow ollamalinks Modelfiles to~/.config/ollama/modelfiles/ -
bash ~/.config/ollama/modelfiles/setup.shcreates all 5 models -
ai— opens quick session -
aic— opens coder session -
ain— opens neteng session (ISE context) -
aid— opens docs session (AsciiDoc rules) -
ask "what is EAP-TLS"— one-shot, no session -
ise-review <config-file>— ISE-specific review -
adoc-review <file.adoc>— standards check -
aigpu— shows VRAM usage -
aikill— frees VRAM
SSH Config — Missing Host Mappings After Modular Migration
SSH Config — modestus-p50 Unreachable
ssh modestus-p50 fails — hostname echoes back unresolved. Not a missing SSH config entry — there is no DNS A record in BIND for modestus-p50.
Diagnosis
ssh -G modestus-p50 | awk '/^hostname/'
grep 'modestus-p50' ~/atelier/_projects/personal/dots-quantum/ssh/.ssh/config.d/20-d000-key-hierarchy.conf
grep 'modestus-p50' ~/atelier/_projects/personal/dots-quantum/ssh/.ssh/config.d/80-d000-hosts.conf
The June 21 DNS inventory (see dns-inventory-loop.adoc) already confirmed:
modestus-p50 — | No A record (retired?) modestus-p16g — | No A record (DHCP — currently 10.50.1.204) thinkpad-x1 — | No A record (retired?) gabriel-workstation — | No A record
The dig loop includes these hosts — they returned nothing because BIND has no record to resolve.
Step 1 — Find the P50’s current IP via ISE MnT
ISE session report shows A0:59:50:C9:92:EE profiled as Windows10-Workstation — likely candidate.
bash $(find data/d000/infra/ise-api-reference/scripts/mnt -name 'session-report.sh')
bash $(find data/d000/infra/ise-api-reference/scripts/mnt -name 'session-detail.sh') A0:59:50:C9:92:EE
P50_IP=$(bash $(find data/d000/infra/ise-api-reference/scripts/mnt -name 'reshape-session.sh') \
A0:59:50:C9:92:EE | jq -r '.identity.ip')
printf "modestus-p50 IP: %s\n" "$P50_IP"
reshape-session.sh returns .identity.ip → .framed_ip_address from the MnT session. Command substitution $(…) captures that output as a shell variable — no copy-paste, no intermediary. The nested $(find …) resolves the script path, the outer $(…) captures its output.
Step 2 — Snapshot zone state BEFORE edit
ZONE_BEFORE=$(dig +noall +answer axfr inside.domusdigitalis.dev @10.50.1.90 | \
awk '$4=="A" {h=$1; sub(/\.$/,"",h); printf "%-45s %s\n", h, $5}' | sort -k2,2V)
This captures the full A-record inventory before the edit. After adding the record, diff via process substitution will show exactly what changed — no temp files on disk.
Step 3 — Create DNS A record in BIND
ssh bind-01 sudo named-checkzone inside.domusdigitalis.dev /var/named/inside.domusdigitalis.dev.zone
ssh bind-01 sudo vim /var/named/inside.domusdigitalis.dev.zone
Add the record:
modestus-p50 IN A <IP-FROM-STEP-1>
ssh bind-01 sudo named-checkzone inside.domusdigitalis.dev /var/named/inside.domusdigitalis.dev.zone
ssh bind-01 sudo rndc reload inside.domusdigitalis.dev
Step 4 — Verify with process substitution
diff <(echo "$ZONE_BEFORE") \
<(dig +noall +answer axfr inside.domusdigitalis.dev @10.50.1.90 | \
awk '$4=="A" {h=$1; sub(/\.$/,"",h); printf "%-45s %s\n", h, $5}' | sort -k2,2V)
Only the new modestus-p50 record should appear as a > line. If anything else changed, investigate.
paste <(dig +short modestus-p50.inside.domusdigitalis.dev @10.50.1.90) \
<(dig +short modestus-p50.inside.domusdigitalis.dev @10.50.1.91) \
| awk '{printf "primary=%-15s secondary=%s\n", $1, $2}'
Both columns must match. If secondary is empty, force zone transfer:
ssh bind-01 sudo rndc notify inside.domusdigitalis.dev
Step 5 — Regenerate SSH host mappings
The dig loop from dns-inventory-loop.adoc already includes modestus-p50 in its brace expansion. Now that DNS resolves, regenerate:
for name in kvm-0{1,2} nas-0{1,2} vyos-0{1,2} vyos-vip bind-0{1,2} ipa-0{1,2} \
keycloak-0{1,2} ipsk-mgr-0{1,2} vault-0{1,2,3} certmgr-01 \
home-dc0{1,2} 9800-wlc-0{1,2} wlc-0{1,2} k3s-master-0{1,2,3} \
ise-0{1,2} gitea-01 3560cx-01 9300-01 ipmi-0{1,2} \
wazuh wazuh-indexer wazuh-dashboard wazuh-workers wazuh-api \
prometheus alertmanager grafana \
modestus-{p16g,aw,razer} thinkpad-x1 modestus-p50 gabriel-workstation; do
ip=$(dig +short "${name}.inside.domusdigitalis.dev" @10.50.1.90)
[ -n "$ip" ] && printf "Host %s\n HostName %s\n\n" "$name" "$ip"
done >| ~/atelier/_projects/personal/dots-quantum/ssh/.ssh/config.d/80-d000-hosts.conf
Step 6 — Verify SSH resolution
ssh -G modestus-p50 | awk '/^hostname|^user|^port/'
Step 7 — Re-encrypt hosts file
age -e -R ~/.age/recipients/self.txt \
-o ~/atelier/_projects/personal/dots-quantum/ssh/.ssh/config.d/80-d000-hosts.conf.age \
~/atelier/_projects/personal/dots-quantum/ssh/.ssh/config.d/80-d000-hosts.conf
Concepts — Process Substitution vs Command Substitution
| Mechanism | Syntax | What it does |
|---|---|---|
Command substitution |
|
Captures stdout as a string — assigns to variable or embeds inline |
Process substitution |
|
Presents stdout as a file descriptor — feeds to commands expecting file arguments |
Command substitution answers: "what did this command say?" Process substitution answers: "treat this command’s output as if it were a file."
diff needs two files. paste needs two files. Process substitution gives them files without creating files. The shell opens /dev/fd/NN, pipes the command’s output through it, and passes the path to the consumer.
The nested pattern in step 1 — P50_IP=$(bash $(find …) | jq -r …) — is two command substitutions: inner $(find …) resolves the script path, outer $(…) captures the final output. Each $(…) is an independent subshell; they compose because the inner one resolves first, left to right.
User override
The old monolithic config set User gabriel for modestus-p50 / gabriel-workstation. The key hierarchy sets User evanusmodestus. If Gabriel’s account is needed, add a User override in 80-d000-hosts.conf:
Host modestus-p50 gabriel-workstation
HostName <IP>
User gabriel
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/'