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:

  1. Reorder policy sets - More specific policies first

  2. Add conditional rules - Handle edge cases within the broader policy (what we did)

  3. Narrow the condition - Add exclusions to prevent false matches

Session: CHLA srt-9 Deployment

Pre-Deployment Checklist

Check Command Status

SSH access

ssh user@srt-9

[ ]

ISE creds loaded

dsource d001 dev/network

[ ]

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:

  1. Inside code blocks: Escape with \{

  2. Inside tables: Same escape pattern

  3. 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:

  1. SSH errors - git@github.com: Permission denied (publickey) - 36 plugins failed

  2. SSL errors - unable to get local issuer certificate (20) for git.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:

  1. SSH agent: Can’t assume systemd - need fallback to manual agent

  2. SSL certificates: Corporate proxies require CA cert import to WSL trust store

  3. 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 = False critical for Kerberos SSH mapping

  • P50 Cert Deployment - Gabriel’s workstation fully onboarded (wired + wireless)

  • krb5 Auto-Renewal - Added krb5_renewable_lifetime = 7d and krb5_renew_interval = 60 to 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:

  1. Always include the hub (domus-docs)

  2. Check hub’s xrefs - run grep -r 'xref:.*::' domus-docs/docs/

  3. Include all referenced components even if you don’t use them directly

  4. 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

home::

domus-linux-ops

linux-ops::

domus-windows-ops

windows-ops::

domus-infra-ops

infra-ops::

domus-ise-linux

ise-linux::

domus-ise-ops

ise-ops::

domus-netapi-docs

netapi::

domus-python

python-ops::

domus-secrets-ops

secrets-infrastructure::

domus-identity-ops

identity-ops::

domus-automation-ops

automation-ops::

domus-siem-ops

siem-ops::

domus-o11y-ops

o11y::

Session: domus-windows-ops Certificate Documentation Expansion

Significantly expanded certificate management documentation:

Pages Created/Updated

Page Content

certificates/index.adoc

Full PowerShell cert operations: navigate, list, find, export, import, delete, certutil commands

certificates/corporate-proxy.adoc

Cisco Umbrella/Zscaler/Palo Alto fix workflow, automation script, app-specific fixes

certificates/cert-store-deep-dive.adoc

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

  1. Wrong URL: Config had url: gitea-01.inside.domusdigitalis.dev but Gitea runs on port 3000

  2. SSH key bug: ssh_key: line in config causes tea to prompt for passphrase even with ssh_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)

  • --status flag for checking uncommitted changes

  • Uses git -C to 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-architectus-repos.sh

Create all 14 repos across GitHub/GitLab/Gitea (one-time)

architectus-push

Push all architectus repos to all remotes (ongoing)

domus-push

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

  1. Domain-aligned - Mirrors dsec structure (d000 = personal, d001 = client)

  2. No top-level sprawl - Network secrets (RADIUS, SNMP, OSPF) nested under domains/d000/network/

  3. Clear separation - Passwords → gopass, API tokens/env bundles → dsec

  4. 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)

git@github.com:EvanusModestus/gopass-v3.git

gitlab

git@gitlab.com:EvanusModestus/gopass-v3.git

gitea

ssh://git@gitea-01.inside.domusdigitalis.dev:2222/evanusmodestus/gopass-v3.git

Clone on Other Machines

gopass clone git@github.com:EvanusModestus/gopass-v3.git v3

Key Decisions

  1. No migration - Built fresh, kept v2 intact as fallback

  2. Network secrets nested - RADIUS/SNMP/routing under domains/d000/network/ not top-level

  3. Hardware vs network devices - hardware/ for IPMI/printers, network/devices/ for switches

  4. 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

pages get <project>

Get project details and build config

pages create <name> --output <dir>

Create direct-upload Pages project

pages update <project> --output <dir>

Update project build configuration

pages delete <project>

Delete a Pages project

pages domain <project> <domain>

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

CF_API_TOKEN (local)

Pages:Read, Access:Edit, Cache Purge

IP-filtered to personal machine

CF_DNS_TOKEN (local)

Zone:Zone:Read, Zone:DNS:Edit

IP-filtered to personal machine

ci-pages-deploy-only (CI)

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

netapi/vendors/cloudflare/init.py

Added create_pages_project, update_pages_project, delete_pages_project, add_pages_domain

netapi/cli/cloudflare.py

Added CLI commands: pages get, pages create, pages update, pages delete, pages domain

architectus-docs/.github/workflows/build.yml

Changed from GitHub Pages to Cloudflare Pages deploy via wrangler

Task: FoxHunt Micro Learning

Manager request - complete FoxHunt micro learning modules for compliance/training.