Daily Worklog: 2026-02-17
Overview
Date: 2026-02-17 (Tuesday)
Location: Remote
Focus: iPSK Policy Fix, Multi-Forge Automation Scripts (architectus-push, domus-push, create-architectus-repos.sh), tea CLI Fix, WSL SSH/SSL Fix
Carried Over from Yesterday
Critical Deliverables
Xianming Ding Linux Workstation (srt-9)
Location: SRT 9th Floor / Research / Xianming Ding
| Item | Status | Notes |
|---|---|---|
Certificate chain |
[ ] |
Root CA + Issuing CA + Client cert |
Private key |
[ ] |
Mode 0600, owned by root |
NetworkManager 802.1X |
[ ] |
EAP-TLS, password flags fixed |
EAP-TLS authentication |
[ ] |
MUST be EAP-TLS, NOT MAB |
dACL applied |
[ ] |
DACL_LINUX_RESEARCH_AD_AUTH |
AD connectivity |
[ ] |
kinit, ldapsearch verified |
SSH access |
[ ] |
From admin workstation |
UFW firewall |
[ ] |
Configured and enabled |
Session: iPSK Policy Fix (Morning)
Problem
MacBook (MAC: 80:A9:97:34:A1:20) connecting to Domus-IoT SSID was being denied.
ISE Failure Log:
Event: 5400 Authentication failed Failure Reason: 15039 Rejected per authorization profile Policy Set: Domus_MAB >> Default Authorization Profile: DenyAccess
Root Cause
Policy set ordering issue. Our new Domus_MAB policy (rank 1) was catching ALL Service-Type = Call Check traffic before Domus-IoT iPSK (rank 5) could match.
| Rank | Policy Set | Condition |
|---|---|---|
1 |
Domus_MAB |
Service-Type = Call Check (catches ALL MAB including iPSK) |
5 |
Domus-IoT iPSK |
Called-Station-ID endsWith Domus-IoT (never reached) |
Fix Applied
Rather than reorder policy sets, added iPSK-aware rules to Domus_MAB:
Authentication Rule
netapi ise add-auth-rule "Domus_MAB" "iPSK_Lookup" "iPSKManager" \
--dict "Radius" --attr "Called-Station-ID" --value "Domus-IoT" \
--operator contains --if-fail REJECT --if-not-found CONTINUE --rank 0
Authorization Rule
netapi ise add-authz-rule "Domus_MAB" "Domus_IoT_Wireless" "iPSK-Auth" \
--and "iPSKManager:ExternalGroups:equals:Domus-IoT"
Domus_MAB Rules After Fix
Authentication:
| # | Rule | Identity Source | If Not Found |
|---|---|---|---|
0 |
iPSK_Lookup |
iPSKManager |
CONTINUE |
1 |
Default |
Internal Endpoints |
CONTINUE |
Authorization:
| # | Rule | Condition | Profile |
|---|---|---|---|
0 |
Domus_IoT_Wireless |
iPSKManager:ExternalGroups = 'Domus-IoT' |
iPSK-Auth |
1-5 |
(other rules) |
… |
… |
6 |
Default |
(default) |
DenyAccess |
iPSK-Auth Profile (Critical)
The iPSK-Auth authorization profile returns the PSK to the WLC:
{
"advancedAttributes": [
{"cisco-av-pair": "psk-mode=ascii"},
{"cisco-av-pair": "iPSKManager:pskValue"}
],
"vlan": {"nameID": "iPSKManager:vlan"}
}
Without this profile, the WLC doesn’t get the PSK and the client can’t authenticate.
Result
Both MacBooks now connecting successfully via iPSK on Domus-IoT SSID.
Key Learning
When adding new policy sets with broad conditions (like Service-Type = Call Check), always consider what existing traffic patterns might be caught. Options:
-
Reorder policy sets - More specific policies first
-
Add conditional rules - Handle edge cases within the broader policy (what we did)
-
Narrow the condition - Add exclusions to prevent false matches
Session: CHLA srt-9 Deployment
Pre-Deployment Checklist
| Check | Command | Status |
|---|---|---|
SSH access |
|
[ ] |
ISE creds loaded |
|
[ ] |
Runbook reviewed |
linux-endpoint-playbook.adoc |
[ ] |
UFW section ready |
Phase 5 hardening |
[ ] |
Reference Runbooks (CHLA Internal)
| Runbook | Purpose |
|---|---|
linux-deployment-ise-playbook.adoc |
Full ISE deployment guide (ISE side) |
linux-endpoint-playbook.adoc |
Endpoint config from zero to EAP-TLS |
switch-ibns2-playbook.adoc |
Switch IBNS 2.0 config for 802.1X |
Location: PRJ-ISE-CHLA-LINUX-ANTORA/runbooks/.internal/
Session: UFW Firewall Configuration
Phase 5.1: UFW Firewall (from runbook)
UFW (Uncomplicated Firewall) provides host-based firewall as defense-in-depth alongside the ISE dACL.
Install and Enable
sudo apt install ufw -y
sudo ufw default deny incoming
sudo ufw default allow outgoing
Allow SSH (before enabling!)
sudo ufw allow ssh
# Or specific: sudo ufw allow from 10.50.0.0/16 to any port 22
Enable UFW
sudo ufw enable
sudo ufw status verbose
Expected Output
Status: active Logging: on (low) Default: deny (incoming), allow (outgoing), disabled (routed) New profiles: skip To Action From -- ------ ---- 22/tcp ALLOW IN Anywhere
Additional Rules (if needed)
# Allow ICMP (ping)
sudo ufw allow icmp
# Allow specific service
sudo ufw allow from 10.50.1.0/24 to any port 443
# Delete rule
sudo ufw delete allow ssh
Session: AsciiDoc Attribute Parsing Fix
Problem
Antora build showing warnings for missing {print} attribute:
WARN (asciidoctor): skipping reference to missing attribute: print
file: WRKLOG-2026-02-16.adoc
Root Cause
AsciiDoc interprets {text} as attribute references. In awk command examples like:
awk '/ERROR/ \{print}'
The {print} was being parsed as an attribute lookup, not as literal awk syntax.
Fix
Escape the opening curly brace with backslash:
awk '/ERROR/ \{print}'
This tells AsciiDoc to treat { literally. The backslash is consumed during parsing and won’t appear in the rendered output.
Files Fixed
-
WRKLOG-2026-02-16.adoc- 5 instances in awk quick reference table
Key Learning
When documenting shell/awk commands with {…} syntax:
-
Inside code blocks: Escape with
\{ -
Inside tables: Same escape pattern
-
Alternative: Use
\{print}for inline passthrough
Session: domus-ise-linux Sanitization
Removed client-specific references from domus-ise-linux to make documentation suitable for public sharing.
Changes Made
-
"Field-tested at CHLA (2026-02-06)" → "Field-validated"
-
"CHLAROOTCA/CHLASUBCA" → "ROOTCA/SUBCA"
-
"chla-chain.pem" → "ca-chain.pem"
-
"Appendix A: Adapting for CHLA" → "Adapting for Other Environments"
-
Theme comments: "CHLA Medical Blue" → "Enterprise Blue"
Files Updated (13 total)
-
disk-encryption.adoc, domain-join.adoc, networkmanager-wired.adoc
-
validation-scripts.adoc, linux-ad-auth-dacl.adoc
-
linux-eaptls-deployment-runbook.adoc
-
status-badges.adoc, validation-summary.adoc
-
home-theme.yml, home-theme-light.yml
-
Makefile, build-adoc.sh, CLAUDE.md
Documentation remains technically accurate - only identifying references removed.
Session: WSL Arch SSH Agent + Cisco Umbrella SSL Fix
Problem
Work WSL (Arch) instance failing to install lazy.nvim plugins:
-
SSH errors -
git@github.com: Permission denied (publickey)- 36 plugins failed -
SSL errors -
unable to get local issuer certificate (20)forgit.sr.ht/- 1 plugin failed (lsp_lines.nvim)
Root Cause Analysis
SSH Issue
SSH agent not running on WSL startup. Keys exist but weren’t loaded:
ssh-add -l 2>&1
Error connecting to agent: No such file or directory
Home Arch uses systemd user ssh-agent socket ($XDG_RUNTIME_DIR/ssh-agent.socket), but WSL doesn’t have systemd by default.
SSL Issue
Corporate network uses Cisco Umbrella for SSL inspection (MITM proxy):
curl -vI https://git.sr.ht 2>&1 | grep issuer
issuer: O=Cisco; CN=Cisco Umbrella Secondary SubCA lax-SG
The Umbrella CA isn’t in WSL’s certificate trust store, causing SSL verification failures.
Fix: Platform-Aware SSH Agent
Updated dotfiles-optimus shell configs to detect environment:
# zsh logic (fish equivalent also added)
if [[ -S "$XDG_RUNTIME_DIR/ssh-agent.socket" ]]; then
# Systemd user ssh-agent exists (native Linux)
export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/ssh-agent.socket"
else
# WSL/non-systemd: start ssh-agent if not running
if [[ -z "$SSH_AUTH_SOCK" ]] || ! ssh-add -l &>/dev/null; then
eval "$(ssh-agent -s)" > /dev/null
[[ -f ~/.ssh/id_ed25519_github ]] && ssh-add ~/.ssh/id_ed25519_github 2>/dev/null
fi
fi
Committed and pushed to dotfiles-optimus main branch.
Fix: Export Cisco Umbrella CA from Windows
PowerShell commands to work with certificate store programmatically:
List Certificates (like certmgr.msc)
# List all certs in trusted root store
Get-ChildItem Cert:\LocalMachine\Root | Format-Table Subject, Thumbprint, NotAfter
# Find Cisco/Umbrella specifically
Get-ChildItem Cert:\LocalMachine\Root | Where-Object { $_.Subject -match "cisco|umbrella" } | Format-List *
# Check cert details
Get-ChildItem Cert:\LocalMachine\Root | Where-Object { $_.Subject -match "Umbrella" } | `
Select-Object Subject, Issuer, NotBefore, NotAfter, Thumbprint
Export Certificate to PEM (for Linux)
$cert = Get-ChildItem Cert:\LocalMachine\Root | Where-Object { $_.Subject -match "Umbrella" }
[System.IO.File]::WriteAllText("C:\temp\cisco-umbrella-ca.crt", `
"-----BEGIN CERTIFICATE-----`n" + `
[Convert]::ToBase64String($cert.RawData, 'InsertLineBreaks') + `
"`n-----END CERTIFICATE-----")
# Verify export
Get-Content C:\temp\cisco-umbrella-ca.crt
Import to WSL Arch Trust Store
sudo cp /mnt/c/temp/cisco-umbrella-ca.crt /etc/ca-certificates/trust-source/anchors/
sudo update-ca-trust
# Verify
curl -vI https://git.sr.ht 2>&1 | grep -E "(SSL|verify|issuer)"
Other Useful PowerShell Cert Commands
# All certificate stores (full certmgr.msc tree)
Get-ChildItem Cert:\LocalMachine | ForEach-Object {
Write-Host "`n$($_.Location)\$($_.Name)"
Get-ChildItem $_.PSPath | Select-Object -First 5 Subject
}
# Current user vs machine stores
Get-ChildItem Cert:\CurrentUser\Root # User-level trusted roots
Get-ChildItem Cert:\LocalMachine\Root # Machine-level trusted roots
Key Learning
When maintaining identical Arch configs across native Linux and WSL:
-
SSH agent: Can’t assume systemd - need fallback to manual agent
-
SSL certificates: Corporate proxies require CA cert import to WSL trust store
-
dotfiles portability: Platform detection is essential for cross-environment configs
Session: domus-windows-ops Repository Creation
Motivation
After the WSL SSH/SSL troubleshooting session, realized PowerShell/Windows content needs a home in the domus-* ecosystem. Created domus-windows-ops as the Windows counterpart to domus-linux-ops.
Atelier Vault System
Credentials are stored in encrypted gocryptfs vaults. Must mount before using gh, API tokens, etc:
# Check vault status
vault status
# Mount credentials vault
vault mount credentials
# Now gh auth works
gh auth login
Vault locations:
| Vault | Purpose |
|---|---|
credentials |
GitHub tokens, API keys, service accounts |
work-sensitive |
Client-specific credentials |
network-configs |
Switch/router configs with passwords |
personal |
Personal accounts, SSH keys |
Repository Structure Created
domus-windows-ops/
├── docs/asciidoc/
│ ├── antora.yml # name: windows-ops
│ ├── modules/ROOT/
│ │ ├── nav.adoc
│ │ └── pages/
│ │ ├── index.adoc
│ │ └── certificates/
│ │ ├── index.adoc
│ │ └── wsl-integration.adoc
│ └── package.json
├── Makefile
├── README.adoc
└── .gitignore
Remotes Pattern
All domus-* repos use three remotes:
origin git@github.com:EvanusModestus/domus-windows-ops.git
gitlab git@gitlab.com:EvanusModestus/domus-windows-ops.git
gitea git@gitea:evanusmodestus/domus-windows-ops.git
Hub Integration
Add to domus-docs/antora-playbook.yml:
- url: https://github.com/EvanusModestus/domus-windows-ops
branches: main
start_path: docs/asciidoc
edit_url: false
Highlights from Yesterday (2026-02-16)
-
ZSH History Cookbook - Published to domus-linux-ops
-
BIND DNS Server - Deployed at 10.50.1.90 with AD SRV records
-
GSSAPI SSH Fix -
use_fully_qualified_names = Falsecritical for Kerberos SSH mapping -
P50 Cert Deployment - Gabriel’s workstation fully onboarded (wired + wireless)
-
krb5 Auto-Renewal - Added
krb5_renewable_lifetime = 7dandkrb5_renew_interval = 60to sssd.conf template
Session: Antora Playbook xref Resolution Fix
Problem
Building domus-windows-ops standalone produced xref errors:
ERROR (asciidoctor): target of xref not found: netapi::cli/cloudflare-commands.adoc ERROR (asciidoctor): target of xref not found: netapi::index.adoc
These xrefs existed in domus-docs pages which were included in the standalone playbook.
Root Cause
Missing components in playbook. When building a standalone component that includes cross-referenced docs (e.g., domus-docs hub pages), ALL components those docs reference must also be in the playbook.
The domus-docs pages had xrefs to:
-
netapi::→ domus-netapi-docs -
ise-ops::→ domus-ise-ops -
python-ops::→ domus-python
But only some components were in the domus-windows-ops playbook.
Fix
Added missing components to domus-windows-ops/docs/asciidoc/antora-playbook.yml:
- url: /home/evanusmodestus/atelier/_bibliotheca/domus-netapi-docs
start_path: docs/asciidoc
branches: HEAD
- url: /home/evanusmodestus/atelier/_bibliotheca/domus-ise-ops
start_path: docs/asciidoc
branches: HEAD
- url: /home/evanusmodestus/atelier/_bibliotheca/domus-python
start_path: docs/asciidoc
branches: HEAD
Key Learning: Antora Playbook Completeness
When building standalone, include ALL transitive dependencies.
If your playbook includes Component A, and Component A has xrefs to Component B, you MUST also include Component B - even if your component doesn’t directly reference it.
domus-windows-ops (your component)
└── includes domus-docs (hub)
└── has xrefs to netapi::, ise-ops::, python-ops::
└── MUST include all three in playbook
Prevention Checklist
When creating new domus-* component playbooks:
-
Always include the hub (
domus-docs) -
Check hub’s xrefs - run
grep -r 'xref:.*::' domus-docs/docs/ -
Include all referenced components even if you don’t use them directly
-
Test build -
make build 2>&1 | grep ERROR
Full Component List (for reference)
Current domus-* Antora components and their name: values:
| Repository | Component Name (xref prefix) |
|---|---|
domus-docs |
|
domus-linux-ops |
|
domus-windows-ops |
|
domus-infra-ops |
|
domus-ise-linux |
|
domus-ise-ops |
|
domus-netapi-docs |
|
domus-python |
|
domus-secrets-ops |
|
domus-identity-ops |
|
domus-automation-ops |
|
domus-siem-ops |
|
domus-o11y-ops |
|
Session: domus-windows-ops Certificate Documentation Expansion
Significantly expanded certificate management documentation:
Pages Created/Updated
| Page | Content |
|---|---|
|
Full PowerShell cert operations: navigate, list, find, export, import, delete, certutil commands |
|
Cisco Umbrella/Zscaler/Palo Alto fix workflow, automation script, app-specific fixes |
|
Advanced Cert: provider usage, bulk operations, inventory reports, private key handling |
Key Additions
-
Certificate store architecture explanation
-
Full property exploration for cert objects
-
Queries by EKU, key properties, date range, certificate chain
-
Bulk export/inventory scripts
-
Registry locations for troubleshooting
Session: Copilot Personalization Runbook
Created comprehensive Microsoft Copilot personalization guide for infosec engineers.
Location
domus-windows-ops/docs/asciidoc/modules/ROOT/pages/runbooks/copilot-personalization.adoc
Contents
-
Custom Instructions Template - Production-ready config for senior security engineer context
-
Strategic Memories - Facts to actively train Copilot
-
Prompt Patterns - 5 structured patterns (verification-first, security-aware, troubleshooting, docs generation, code review)
-
CLI Access - PowerShell and WSL options
-
Power User Tips - Context management, iterative refinement, output control
Competitive Edge
What colleagues likely miss: - Most use Copilot without any personalization - Structured prompts vs ad-hoc questions - Verification patterns built into workflow - Documentation acceleration capability
Session: domus-docs Deployment Trigger Update
Updated domus-docs/docs/modules/ROOT/pages/deployment.adoc:
Added Content
-
Multi-remote rebuild trigger command with xargs pattern
-
Example output for reference
-
CI Trigger Endpoint roadmap (Cloudflare API, netapi integration, GitHub Actions)
Trigger Command
cd /home/evanusmodestus/atelier/_bibliotheca/domus-docs && \
git commit --allow-empty -m "chore: trigger rebuild" && \
git remote | xargs -I {} git push {} main
Summary: Today’s Deliverables
Completed
| Item | Status |
|---|---|
iPSK policy fix for MacBooks |
[x] Done |
WSL SSH agent platform-aware config |
[x] Done (dotfiles-optimus) |
Cisco Umbrella SSL cert export docs |
[x] Done (domus-windows-ops) |
domus-windows-ops repository creation |
[x] Done (all 3 remotes) |
Antora playbook xref resolution |
[x] Done |
Certificate documentation expansion |
[x] Done (3 pages, 900+ lines) |
Copilot personalization runbook |
[x] Done (450+ lines) |
Deployment trigger docs |
[x] Done |
Outstanding / Owed
Source: Principia/02_Assets/TAB-CAPTURES/index/ops-rolling-engagements.adoc
Critical / In-Flight
| Item | Details | Priority |
|---|---|---|
srt-9 Linux Deployment |
Xianming Ding workstation - EAP-TLS, dACL, UFW |
CRITICAL |
Research Segmentation |
All Research endpoints → Untrusted VLAN per CISO decision |
HIGH |
Spikewell BYOD VPN |
dACL for SQL access, AD group membership validation |
HIGH |
Strongline Gateway Whitelist |
MAC address capture → Identity Group import |
HIGH |
iPSK HA with Ben Castillo |
Expansion and cleanup |
Medium |
Strategic Engagements (Active)
| Engagement | Status |
|---|---|
ChromeOS MS-CHAPv2 → EAP-TLS |
SCEP URL/AD template validation with Victor; Paul testing endpoint behavior |
QRadar → Sentinel Migration |
In progress |
Azure Legacy → Modern Landing Zone |
Planning |
BMS Legacy Windows Isolation |
Needs quarantine VLAN + server-specific dACL |
Cisco DNAC → Catalyst Center |
dot1x template cleanup |
Isensix AP NAC |
Log capture, onboarding validation |
NebulaOne Integration |
Pending tasks |
OCI Cloud Onboarding |
Planning |
Operational Issues
| Issue | Workaround/Status |
|---|---|
ISE 30-day Auth Export |
Weekly exports + concatenate; long-term: MnT API bulk pagination |
ChromeOS 24-hour session |
Imprivata badge tap workflow validation |
Wired/Wireless Failover |
ChromeOS network profile testing |
Next Actions
Immediate (Today/Tomorrow)
-
PRIORITY: Complete srt-9 deployment - Xianming Ding Linux workstation
-
Certificate chain deployment
-
NetworkManager 802.1X configuration
-
EAP-TLS authentication (NOT MAB)
-
dACL: DACL_LINUX_RESEARCH_AD_AUTH
-
AD connectivity verification
-
UFW firewall enabled
-
-
Spikewell BYOD VPN - implement dACL, validate AD group
-
Strongline gateway - capture MAC addresses, import to Identity Group
-
iPSK HA coordination with Ben Castillo
This Week
-
Research segmentation policy finalization (all untrusted per CISO)
-
ChromeOS SCEP validation with Victor
-
ChromeOS endpoint testing with Paul
-
ISE 30-day export workaround documentation
Tracking
-
BMS legacy Windows isolation plan
-
QRadar → Sentinel migration progress
-
DNAC → Catalyst Center template cleanup
-
Isensix AP NAC validation
Session: Architectus Multi-Remote Repository Creation Script
Overview
Script to create all 14 architectus repositories across GitHub, GitLab, and Gitea in a single operation.
Prerequisites
# Mount credentials vault
vault mount credentials
# Verify auth
gh auth status
glab auth status
Repository List
| Type | Repository |
|---|---|
Hub |
architectus-docs |
Technology |
architectus-linux, architectus-windows, architectus-networking, architectus-security, architectus-automation, architectus-cloud |
Humanities |
architectus-literature, architectus-languages, architectus-mathematics, architectus-music, architectus-philosophy |
Exploration |
architectus-radio, architectus-navigation |
Script: create-architectus-repos.sh
Save to ~/bin/create-architectus-repos.sh:
#!/usr/bin/env bash
# Create Architectus repositories across GitHub, GitLab, and Gitea
# Usage: create-architectus-repos.sh [--dry-run]
set -euo pipefail
# Configuration
GITHUB_USER="EvanusModestus"
GITLAB_USER="EvanusModestus"
GITEA_HOST="gitea" # SSH alias from ~/.ssh/config
GITEA_USER="evanusmodestus"
BASE_DIR="/home/evanusmodestus/atelier/_architectus"
DESCRIPTION_PREFIX="Architectus Documentation -"
# Repository definitions: name|description
REPOS=(
"architectus-docs|Hub - Landing page and aggregation"
"architectus-linux|Linux system administration, hardening, and automation"
"architectus-windows|Windows, Active Directory, PowerShell, and GPO"
"architectus-networking|Networking, routing, switching, NAC, and firewalls"
"architectus-security|Security, IAM, PKI, secrets management, and compliance"
"architectus-automation|Automation, Ansible, Terraform, CI/CD, and scripting"
"architectus-cloud|Cloud platforms - Azure, AWS, OCI, and hybrid"
"architectus-literature|Literature - Cervantes, Garcia Marquez, Reina Valera"
"architectus-languages|Languages - Spanish B2-C2, Portuguese A1-B2"
"architectus-mathematics|Mathematics - Foundations, calculus, linear algebra"
"architectus-music|Music - Theory, composition, and production"
"architectus-philosophy|Philosophy - Faith, reason, ethics, and epistemology"
"architectus-radio|Radio - Amateur radio, RF theory, ionospheric science"
"architectus-navigation|Navigation - Cartography, celestial, land and sea"
)
DRY_RUN=false
[[ "${1:-}" == "--dry-run" ]] && DRY_RUN=true
log() { echo "[$(date '+%H:%M:%S')] $*"; }
run() {
if $DRY_RUN; then
echo "[DRY-RUN] $*"
else
"$@"
fi
}
create_github() {
local repo="$1" desc="$2"
log "GitHub: Creating $repo"
run gh repo create "${GITHUB_USER}/${repo}" \
--public \
--description "${DESCRIPTION_PREFIX} ${desc}" \
--source "${BASE_DIR}/${repo}" \
--remote origin \
--push
}
create_gitlab() {
local repo="$1" desc="$2"
log "GitLab: Creating $repo"
run glab repo create "${repo}" \
--public \
--description "${DESCRIPTION_PREFIX} ${desc}" \
--defaultBranch main
# Add remote and push
cd "${BASE_DIR}/${repo}"
run git remote add gitlab "git@gitlab.com:${GITLAB_USER}/${repo}.git" 2>/dev/null || true
run git push gitlab main
}
create_gitea() {
local repo="$1" desc="$2"
log "Gitea: Creating $repo"
# Gitea API (adjust URL to your instance)
local GITEA_URL="https://gitea.domusdigitalis.dev"
local GITEA_TOKEN="${GITEA_TOKEN:-}"
if [[ -z "$GITEA_TOKEN" ]]; then
log "WARN: GITEA_TOKEN not set, skipping Gitea API call"
log " Manual creation required at ${GITEA_URL}"
else
run curl -s -X POST "${GITEA_URL}/api/v1/user/repos" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"name\": \"${repo}\", \"description\": \"${DESCRIPTION_PREFIX} ${desc}\", \"private\": false}"
fi
# Add remote and push regardless
cd "${BASE_DIR}/${repo}"
run git remote add gitea "git@${GITEA_HOST}:${GITEA_USER}/${repo}.git" 2>/dev/null || true
run git push gitea main 2>/dev/null || log " Push to gitea failed (repo may not exist yet)"
}
main() {
log "=== Architectus Repository Creation ==="
log "Repos: ${#REPOS[@]}"
log "Dry run: $DRY_RUN"
echo
for entry in "${REPOS[@]}"; do
IFS='|' read -r repo desc <<< "$entry"
if [[ ! -d "${BASE_DIR}/${repo}" ]]; then
log "SKIP: ${repo} - directory not found"
continue
fi
echo
log "=== Processing: $repo ==="
create_github "$repo" "$desc"
create_gitlab "$repo" "$desc"
create_gitea "$repo" "$desc"
done
echo
log "=== Complete ==="
log "Verify at:"
log " GitHub: https://github.com/${GITHUB_USER}?tab=repositories&q=architectus"
log " GitLab: https://gitlab.com/${GITLAB_USER}?filter=architectus"
}
main "$@"
Usage
# Make executable
chmod +x ~/bin/create-architectus-repos.sh
# Dry run first
create-architectus-repos.sh --dry-run
# Execute
create-architectus-repos.sh
Alternative: One-liner for Quick Push
If repos already exist on remotes, push all local repos:
for repo in docs linux windows networking security automation cloud literature languages mathematics music philosophy radio navigation; do
cd "/home/evanusmodestus/atelier/_architectus/architectus-${repo}"
echo "=== architectus-${repo} ==="
for remote in origin gitlab gitea; do
git push "$remote" main 2>/dev/null && echo " ✓ $remote" || echo " ✗ $remote"
done
done
Gitea Token Setup
To enable Gitea API creation:
# Generate token at: https://gitea.domusdigitalis.dev/user/settings/applications
# Save to vault
echo "YOUR_GITEA_TOKEN" > ~/.config/gitea/token
chmod 600 ~/.config/gitea/token
# Export before running script
export GITEA_TOKEN=$(cat ~/.config/gitea/token)
Verification Commands
# List all remotes for all repos
for d in /home/evanusmodestus/atelier/_architectus/architectus-*/; do
echo "=== $(basename "$d") ==="
git -C "$d" remote -v
done
# Check GitHub repo status
gh repo list EvanusModestus --limit 50 | grep architectus
# Check GitLab
glab repo list | grep architectus
Session: Multi-Forge Automation Fixes & Scripts
Overview
Extended the architectus repo creation script with multiple fixes, created push scripts for ongoing maintenance, and fixed critical bugs.
tea CLI Configuration Fix
Problem
tea repo list hanging or prompting for SSH passphrase despite key being in ssh-agent.
Root Causes
-
Wrong URL: Config had
url: gitea-01.inside.domusdigitalis.devbut Gitea runs on port 3000 -
SSH key bug:
ssh_key:line in config causes tea to prompt for passphrase even withssh_agent: true
Fix
# Fix URL to include port
sed -i 's|url: https://gitea-01.inside.domusdigitalis.dev|url: https://gitea-01.inside.domusdigitalis.dev:3000|' ~/.config/tea/config.yml
# Remove ssh_key line (tea bug - ignores ssh_agent setting)
sed -i '/ssh_key:/d' ~/.config/tea/config.yml
# Verify
tea repo list --login gitea | head -5
Key Learning
tea CLI has a bug where it ignores ssh_agent: true when ssh_key: is present. Remove the ssh_key line entirely - git operations use the system ssh-agent, tea only needs the API token for repo operations.
Script Fix: Directory Pollution Bug
Problem
Original script used cd to change into each repo directory but never returned. This caused cascading directory pollution - each repo got the next repo nested inside it.
Fix
Replace all cd commands with git -C "$dir":
# WRONG - pollutes directories
cd "${BASE_DIR}/${repo}"
git remote add origin ...
git push origin main
# CORRECT - operates in place
local dir="${BASE_DIR}/${repo}"
git -C "$dir" remote add origin ...
git -C "$dir" push origin main
New Script: architectus-push
Push all architectus repos to all three forges:
#!/usr/bin/env bash
# ~/bin/architectus-push
# Usage: architectus-push [--status]
set -euo pipefail
BASE_DIR="/home/evanusmodestus/atelier/_architectus"
REPOS=(architectus-{docs,linux,windows,networking,security,automation,cloud,literature,languages,mathematics,music,philosophy,radio,navigation})
REMOTES=("origin" "gitlab" "gitea")
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
RED='\033[0;31m'
NC='\033[0m'
status_only=false
[[ "${1:-}" == "--status" ]] && status_only=true
for repo in "${REPOS[@]}"; do
dir="${BASE_DIR}/${repo}"
[[ ! -d "$dir" ]] && { echo -e "${RED}SKIP${NC}: $repo"; continue; }
echo -e "=== ${GREEN}${repo}${NC} ==="
if $status_only; then
git -C "$dir" status -s
continue
fi
for remote in "${REMOTES[@]}"; do
if git -C "$dir" remote get-url "$remote" &>/dev/null; then
if git -C "$dir" push "$remote" main 2>/dev/null; then
echo -e " ${GREEN}✓${NC} $remote"
else
echo -e " ${RED}✗${NC} $remote"
fi
else
echo -e " ${YELLOW}-${NC} $remote (not configured)"
fi
done
done
echo -e "\n=== Complete ==="
Upgraded Script: domus-push
Upgraded the zsh function to a proper script with same features as architectus-push:
-
Color-coded output
-
Multi-remote support (origin, gitlab, gitea)
-
--statusflag for checking uncommitted changes -
Uses
git -Cto avoid directory changes
Location: ~/bin/domus-push
Disaster Recovery: Accidental Deletion
What Happened
Cleanup command to remove nested directories accidentally deleted the repos themselves:
# This matched the parent directories too!
find "$dir" -maxdepth 1 -type d -name "architectus-*" -exec rm -rf {} \;
Recovery (30 seconds)
Since we had JUST pushed to GitHub, recovery was trivial:
cd /home/evanusmodestus/atelier/_architectus/
for repo in architectus-{docs,linux,windows,networking,security,automation,cloud,literature,languages,mathematics,music,philosophy,radio,navigation}; do
git clone "git@github.com:EvanusModestus/${repo}.git"
done
# Re-add other remotes
for repo in architectus-{docs,linux,windows,networking,security,automation,cloud,literature,languages,mathematics,music,philosophy,radio,navigation}; do
git -C "$repo" remote add gitlab "git@gitlab.com:EvanusModestus/${repo}.git" 2>/dev/null || true
git -C "$repo" remote add gitea "git@gitea:evanusmodestus/${repo}.git" 2>/dev/null || true
done
Key Learning
Multi-remote push is insurance. Because all repos were pushed to GitHub moments before deletion, recovery was instant. This validates the multi-forge strategy.
Final Working Scripts
Location: ~/bin/
| Script | Purpose |
|---|---|
|
Create all 14 repos across GitHub/GitLab/Gitea (one-time) |
|
Push all architectus repos to all remotes (ongoing) |
|
Push all domus repos to all remotes (ongoing) |
tea Config (Working)
Location: ~/.config/tea/config.yml (symlinked from vault)
logins:
- name: gitea
url: https://gitea-01.inside.domusdigitalis.dev:3000 # Port required!
token: <from-vault>
default: true
# NO ssh_key line - causes prompting bug
ssh_host: gitea-01.inside.domusdigitalis.dev
ssh_agent: true
user: evanusmodestus
Session: gopass v3 Architecture Design
Motivation
v2 gopass store had realm-based organization (ARCANA, DOMUS, OPUS, PERSONAE, COMMERCIA) that didn’t align with the dsec domain model (d000, d001). Designed v3 from scratch with platform engineering principles.
Design Principles
-
Domain-aligned - Mirrors dsec structure (d000 = personal, d001 = client)
-
No top-level sprawl - Network secrets (RADIUS, SNMP, OSPF) nested under
domains/d000/network/ -
Clear separation - Passwords → gopass, API tokens/env bundles → dsec
-
Multi-remote from day one - GitHub, GitLab, Gitea
Final v3 Structure
v3/
├── domains/
│ ├── d000/ # Personal Infrastructure
│ │ ├── hardware/ # IPMI, printers, IoT
│ │ ├── identity/ # AD, IPA, Keycloak, LDAP
│ │ ├── network/
│ │ │ ├── devices/ # Switch/router/firewall logins
│ │ │ ├── radius/ # RADIUS shared secrets
│ │ │ ├── snmp/ # Community strings
│ │ │ ├── routing/ # OSPF/EIGRP/BGP keys
│ │ │ └── tacacs/ # TACACS+ secrets
│ │ ├── servers/
│ │ ├── storage/ # NAS, S3, backup
│ │ └── wifi/ # PSKs
│ └── d001/ # Client (CHLA)
│ ├── identity/
│ └── network/
├── keys/
│ ├── ssh/
│ │ ├── personal/ # github, gitlab, gitea
│ │ └── service/ # deploy keys, automation
│ ├── age/
│ ├── gpg/
│ └── encryption/ # borg, veracrypt, LUKS
├── certificates/ # Cert passphrases, PFX
├── licenses/ # Software licenses
└── personal/ # Consumer/personal
├── finance/ # Banking, investments
├── shopping/ # Amazon, eBay
├── social/ # Twitter, LinkedIn
├── streaming/ # Netflix, Spotify
├── gaming/ # Steam, GOG
├── email/
├── documents/ # Encrypted doc passwords
├── recovery/ # 2FA backup codes
├── travel/ # Airlines, hotels
├── health/ # Health portals
└── government/ # DMV, IRS, SSA
Implementation Commands
# Initialize v3 store
gopass init --store v3
# Create structure with .meta placeholders
echo "description" | gopass insert -m v3/path/to/.meta
# Add remotes
git -C ~/.local/share/gopass/stores/v3 remote add origin git@github.com:EvanusModestus/gopass-v3.git
git -C ~/.local/share/gopass/stores/v3 remote add gitlab git@gitlab.com:EvanusModestus/gopass-v3.git
git -C ~/.local/share/gopass/stores/v3 remote add gitea git@gitea:evanusmodestus/gopass-v3.git
# Create Gitea repo via API (push-to-create disabled)
curl -sk -X POST "https://gitea-01.inside.domusdigitalis.dev:3000/api/v1/user/repos" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"gopass-v3","private":true}'
# Push to all remotes
git -C ~/.local/share/gopass/stores/v3 push origin main
git -C ~/.local/share/gopass/stores/v3 push gitlab main
git -C ~/.local/share/gopass/stores/v3 push gitea main
# Verify sync
gopass sync
Remotes
| Remote | URL |
|---|---|
origin (GitHub) |
|
gitlab |
|
gitea |
|
Clone on Other Machines
gopass clone git@github.com:EvanusModestus/gopass-v3.git v3
Key Decisions
-
No migration - Built fresh, kept v2 intact as fallback
-
Network secrets nested - RADIUS/SNMP/routing under
domains/d000/network/not top-level -
Hardware vs network devices -
hardware/for IPMI/printers,network/devices/for switches -
Personal section - Full consumer password categories (finance, health, government, etc.)
Verification
gopass mounts
# gopass (/home/evanusmodestus/.password-store)
# ├── v2 (/home/evanusmodestus/.local/share/gopass/stores/v2)
# └── v3 (/home/evanusmodestus/.local/share/gopass/stores/v3)
gopass sync
# [v2] OK (no changes)
# [v3] OK (no changes)
Session: Architectus Cloudflare Pages Deployment (Full Programmability)
Overview
Deployed docs.architectus.dev to Cloudflare Pages using full CLI automation - zero dashboard interaction. Extended netapi with Pages project management commands.
Architecture Decision: CI + Direct Upload
Evaluated deployment strategies:
| Option | Description | Verdict |
|---|---|---|
Git-connected (Cloudflare builds) |
Cloudflare pulls repo, runs build |
Rejected - Only GitHub/GitLab, no Gitea support |
CI + Direct Upload |
CI builds, uploads artifacts to CF |
Selected - Multi-forge portable, full control |
Terraform + Git |
IaC creates git-connected project |
Partial - IaC good, but git lock-in bad |
Winner: Terraform creates the project, CI deploys. Best of both worlds.
netapi Cloudflare Pages Commands Added
Extended netapi/vendors/cloudflare/init.py and netapi/cli/cloudflare.py:
| Command | Purpose |
|---|---|
|
Get project details and build config |
|
Create direct-upload Pages project |
|
Update project build configuration |
|
Delete a Pages project |
|
Add custom domain to project |
Deployment Steps (All CLI)
1. Create Pages Project
netapi cloudflare pages create architectus-docs --output "build/site"
2. GitHub Actions Workflow
Updated .github/workflows/build.yml:
- name: Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
with:
apiToken: $\{{ secrets.CF_API_TOKEN }}
accountId: $\{{ secrets.CF_ACCOUNT_ID }}
command: pages deploy build/site --project-name=architectus-docs
3. GitHub Secrets
gh secret set CF_API_TOKEN --repo EvanusModestus/architectus-docs --body "$CF_API_TOKEN"
gh secret set CF_ACCOUNT_ID --repo EvanusModestus/architectus-docs --body "7f6981e005108a7a90b03ec3ff4b5197"
4. Add Custom Domain
netapi cloudflare pages domain architectus-docs docs.architectus.dev
5. Create DNS Record
CF_API_TOKEN=$CF_DNS_TOKEN netapi cloudflare dns add architectus.dev \
-t CNAME -n docs -c architectus-docs.pages.dev --proxied
6. Verify
curl -sI https://docs.architectus.dev | head -5
# HTTP/2 200
API Token Strategy (Least Privilege)
Created separate tokens for security:
| Token | Permissions | Scope |
|---|---|---|
|
Pages:Read, Access:Edit, Cache Purge |
IP-filtered to personal machine |
|
Zone:Zone:Read, Zone:DNS:Edit |
IP-filtered to personal machine |
|
Account:Cloudflare Pages:Edit |
No IP filter (GitHub Actions runners) |
Troubleshooting
403 on DNS Operations
Token had Zone:DNS:Edit but not Zone:Zone:Read. Added zone read permission.
403 on CI Deploy
CI token was IP-filtered. Created separate ci-pages-deploy-only token without IP restriction.
Verify Token Permissions
curl -s -H "Authorization: Bearer $CF_DNS_TOKEN" \
https://api.cloudflare.com/client/v4/user/tokens/verify | jq
Result
-
Site live: docs.architectus.dev
-
Full programmability: Zero dashboard interaction
-
Multi-forge ready: GitHub Actions deployed, GitLab CI next
-
Security: Separate tokens with minimal permissions
Files Modified
| File | Changes |
|---|---|
|
Added |
|
Added CLI commands: |
|
Changed from GitHub Pages to Cloudflare Pages deploy via wrangler |
Task: FoxHunt Micro Learning
Manager request - complete FoxHunt micro learning modules for compliance/training.