[{"content":"ever since Apple redesigned System Settings in macOS Ventura, doing basic network engineering tasks on a Mac—switching an Ethernet dongle between a static IP and DHCP, checking which utun interface belongs to which VPN, or spinning up an 802.1Q tagged VLAN interface to test a trunk port—means clicking through four layers of modal sheets.\nOn Linux, you have nmtui and impala. On macOS, you\u0026rsquo;re stuck running verbose /usr/sbin/networksetup and /usr/sbin/scutil commands by hand, and starting in macOS Sonoma, Apple even removed the classic airport CLI utility.\nSo I built nettui (OSX_network_TUI): a single, compiled Rust binary that puts all of macOS\u0026rsquo;s wired interfaces, Wi-Fi telemetry, VPN tunnels, 802.1Q VLANs, and Network Locations into a fast keyboard-driven terminal UI.\nWhat nettui Does nettui is organized into three tabs (1 Interfaces, 2 Wi-Fi, and 3 Locations), backed by an asynchronous worker thread so UI rendering stays locked at 60 fps even while scanning Wi-Fi channels or reconfiguring interfaces.\n1. Wired \u0026amp; Wireless Interface Management (1 Interfaces) Unified Service \u0026amp; Live Interface View: Correlates high-level macOS network service names (Wi-Fi, USB 10/100/1G LAN) with their underlying BSD devices (en0, en7, bridge0), priority order, hardware MAC address, active media speed/duplex, MTU, and live getifaddrs(3) IPv4/IPv6 bindings. Interactive IPv4 Modal (e / Enter): Switch any interface between DHCP, Manual (IP, Subnet Mask, Router), or Manual with DHCP Router without typing a single networksetup flag. DNS \u0026amp; Search Domains (n, s): Set custom resolvers (or clear them back to automatic DHCP), renew DHCP leases (r), toggle IPv6 (6), disable/enable hardware ports (t), and adjust MTU (m). 2. Native VPN Tunnel Discovery \u0026amp; Control (1 Interfaces) Queries /usr/sbin/scutil --nc to list every configured VPN service on the system—including Tailscale, WireGuard, IKEv2, L2TP, and Cisco IPSec. Shows live tunnel status (Connected, Connecting, Disconnected), dynamically assigned utun* device names, and tunnel IPv4 addresses. Pressing c (or Enter) connects or disconnects the selected VPN tunnel immediately. 3. 802.1Q VLAN Creation \u0026amp; Tag Editing (1 Interfaces) When testing trunk ports or segmented lab networks (like my CORE VLAN 10 and LAB VLAN 30 subnets), nettui lets you create virtual 802.1Q VLAN interfaces (v) on top of any physical Ethernet adapter, edit the VLAN tag (1–4094) or parent device (g), or tear the VLAN down (x). Tab 2: Live Wi-Fi Telemetry \u0026amp; AP Scanning via CoreWLAN Because Apple removed the legacy /System/Library/PrivateFrameworks/Apple80211.framework/.../airport binary in macOS 14.4, shell-wrapper TUIs broke overnight.\nnettui links directly against Apple\u0026rsquo;s native CoreWLAN and CoreLocation Objective-C frameworks in Rust using objc2:\nLive Radio Metrics: Displays real-time RSSI (-51 dBm), noise floor (-92 dBm), calculated SNR (41 dB), signal bars (▂▄▆█), active PHY mode (802.11ax / Wi-Fi 6), channel number, band (2.4 GHz, 5 GHz, 6 GHz), channel width (80 MHz), transmit rate (1200 Mbps), and security mode (WPA3 Personal, WPA2 Enterprise, etc.). Surrounding AP Scanner (s): Performs a hardware scan, groups multiple BSSIDs advertising the same SSID (showing ×2, ×3 AP counts), and sorts by signal strength. Keychain Integration: Join scanned or hidden networks (Enter / c), pulling saved credentials from the macOS System Keychain or prompting for a passphrase in-TUI, and manage or forget preferred networks (Tab / f). Solving the macOS Sonoma/Sequoia SSID Redaction Problem in a CLI Binary One of the trickiest macOS engineering details in nettui is how Apple handles Wi-Fi privacy on modern macOS releases.\nStarting in macOS Sonoma, CWInterface::ssid() and CWNetwork::ssid() return nil (\u0026lt;redacted\u0026gt;) unless the calling process has been granted Location Services permission by the user. Normally, macOS only allows bundled .app applications (which have a Contents/Info.plist containing NSLocationUsageDescription) to request CoreLocation authorization.\nHow do you get macOS locationd to show a Location Services prompt for a standalone command-line binary in /usr/local/bin/nettui?\nThe answer lies in the Mach-O linker. In build.rs, nettui instructs clang / ld to embed Info.plist directly into the compiled binary\u0026rsquo;s __TEXT,__info_plist section and ad-hoc signs the executable with a stable bundle identifier (com.nettui.cli):\n// build.rs — Embed Info.plist into __TEXT,__info_plist so CoreLocation works in a single binary fn main() { println!(\u0026#34;cargo:rerun-if-changed=Info.plist\u0026#34;); println!(\u0026#34;cargo:rustc-link-arg=-Wl,-sectcreate,__TEXT,__info_plist,Info.plist\u0026#34;); } When you press L inside nettui (or run nettui --authorize-location), CoreLocation reads the embedded plist from the Mach-O header, triggers the native macOS permission dialog, and immediately unredacts all SSIDs and BSSIDs in the terminal.\nSecurity \u0026amp; Privilege Escalation Zero Shell Execution: Every external system utility (networksetup, scutil, ifconfig, ipconfig) is invoked directly via std::process::Command argument arrays—never sh -c—eliminating command injection even with hostile SSID names. On-Demand sudo Modal: Read operations run completely unprivileged (user). When you apply a write operation (such as changing an IP address or creating a VLAN), nettui checks sudo -n true first; if your sudo timestamp has expired, it pops up an in-TUI password modal, validates credentials via sudo -S -v, and executes the change cleanly without dropping out of the alternate screen. Headless CLI Flags \u0026amp; Building from Source In addition to the interactive TUI, nettui includes non-interactive flags for quick terminal checks and scripts:\n# Clone and build the release binary git clone https://github.com/jdansak/OSX_network_TUI.git cd OSX_network_TUI cargo build --release # Launch interactive TUI ./target/release/nettui # Print a formatted status summary of all interfaces, Wi-Fi, and locations ./target/release/nettui --status # Scan and print nearby Wi-Fi access points to stdout ./target/release/nettui --scan The full source code is available on GitHub at jdansak/OSX_network_TUI.\n","permalink":"https://jlte.us/blog/nettui-macos-network-tui-rust/","summary":"Inside nettui (OSX_network_TUI): a native Rust terminal UI for macOS that manages wired/wireless interfaces, VPN tunnels, Wi-Fi scanning, and 802.1Q VLANs—including how to embed an Info.plist in a CLI binary to unredact SSIDs on macOS Sonoma/Sequoia.","title":"Building nettui: A Native Rust Terminal UI for macOS Networking, VPNs, Wi-Fi, and 802.1Q VLANs"},{"content":"When you move between a macOS laptop, Ubuntu servers, Proxmox LXC containers, and Arch Linux workstations, keeping your shell environment and CLI toolchain consistent becomes a chore. Shell bootstrap scripts always start simple and eventually turn into a brittle mess of if [[ \u0026quot;$OSTYPE\u0026quot; == \u0026quot;darwin\u0026quot;* ]] conditionals that fail halfway through a fresh setup.\nI built two self-contained Ansible playbooks (common/tools and common/interface) around three rules:\nZero static inventory required. You can clone the playbook onto a brand-new laptop or point it at an ad-hoc IP address without editing an inventory/hosts file first. First-class support for macOS (Darwin), Debian/Ubuntu (Debian), and Arch Linux (Archlinux). Full idempotency and --check mode compatibility. Running the playbook repeatedly yields changed=0 after the first pass. The Zero-Inventory Pattern (-i \u0026quot;localhost,\u0026quot;) Passing a trailing comma in -i tells Ansible to parse the argument as an inline host list rather than looking for an inventory file on disk. Combined with hosts: all in playbook.yml:\n--- - name: Install Common Developer CLI Tools hosts: all gather_facts: true roles: - role: common_tools You can run the playbook locally on your current Mac or Linux box:\n# Run locally on the current machine (macOS or Linux) ansible-playbook playbook.yml -c local -i \u0026#34;localhost,\u0026#34; # Dry-run check without modifying the system ansible-playbook playbook.yml -c local -i \u0026#34;localhost,\u0026#34; --check Or run it against any remote host over SSH on the fly:\nansible-playbook playbook.yml -i \u0026#34;192.168.1.50,\u0026#34; -u goobey Normalizing the CLI Toolchain Across Homebrew, APT, and Pacman The common_tools role installs a consistent set of modern CLI utilities across every machine:\nTool Binary Replaces Purpose ripgrep rg grep Fast recursive regex search that respects .gitignore fd fd find Fast, intuitive file finder bat bat cat Syntax highlighting, line numbers, and Git gutter indicators lsd lsd ls Filetype icons, tree view, and colorized permissions fzf fzf — Interactive fuzzy finder for files, history, and processes zoxide z cd Frecency-based directory jumper btop btop top / htop Interactive CPU, memory, disk, and network monitor glow glow — Terminal Markdown viewer for reading documentation over SSH starship starship PS1 Fast, minimal cross-shell prompt fastfetch fastfetch neofetch Fast hardware and OS diagnostic summary vortix vortix manual VPN CLI Terminal UI for managing WireGuard, OpenVPN, and NetBird tunnels Dispatching by os_family Instead of repeating when: conditions on every task, roles/common_tools/tasks/main.yml dispatches cleanly by ansible_facts['os_family']:\n--- - name: Install tools on Debian / Ubuntu ansible.builtin.include_tasks: debian.yml when: ansible_facts[\u0026#39;os_family\u0026#39;] == \u0026#39;Debian\u0026#39; - name: Install tools on Arch Linux ansible.builtin.include_tasks: arch.yml when: ansible_facts[\u0026#39;os_family\u0026#39;] == \u0026#39;Archlinux\u0026#39; - name: Install tools on macOS ansible.builtin.include_tasks: macos.yml when: ansible_facts[\u0026#39;os_family\u0026#39;] == \u0026#39;Darwin\u0026#39; On macOS (macos.yml), Homebrew refuses to run as root, so become: false is explicit on the community.general.homebrew task, while debian.yml and arch.yml elevate with become: true.\nFixing Debian \u0026amp; Ubuntu\u0026rsquo;s batcat and fdfind Naming Quirks Because of legacy package name collisions in Debian and Ubuntu, APT installs bat as /usr/bin/batcat and fd-find as /usr/bin/fdfind. Relying on shell aliases breaks non-interactive scripts, Neovim plugins, and fzf preview commands.\nTo make bat and fd work identically everywhere, the role creates user-local symlinks inside ~/.local/bin:\n- name: Ensure ~/.local/bin exists for user symlinks ansible.builtin.file: path: \u0026#34;{{ user_bin_dir }}\u0026#34; state: directory mode: \u0026#34;0755\u0026#34; when: create_convenience_symlinks | bool - name: Create convenience symlink for bat (batcat -\u0026gt; bat) on Debian/Ubuntu ansible.builtin.file: src: /usr/bin/batcat dest: \u0026#34;{{ user_bin_dir }}/bat\u0026#34; state: link force: false when: - ansible_facts[\u0026#39;os_family\u0026#39;] == \u0026#39;Debian\u0026#39; - create_convenience_symlinks | bool - name: Create convenience symlink for fd (fdfind -\u0026gt; fd) on Debian/Ubuntu ansible.builtin.file: src: /usr/bin/fdfind dest: \u0026#34;{{ user_bin_dir }}/fd\u0026#34; state: link force: false when: - ansible_facts[\u0026#39;os_family\u0026#39;] == \u0026#39;Debian\u0026#39; - create_convenience_symlinks | bool Installing GitHub Release Binaries Idempotently (vortix) For tools distributed via GitHub Releases rather than distro repos—such as vortix—the playbook checks whether the binary is already on PATH before downloading the architecture-matched package:\n- name: Check if vortix is installed ansible.builtin.command: which vortix register: vortix_check failed_when: false changed_when: false - name: Download Vortix .deb package (GitHub Releases) ansible.builtin.get_url: url: \u0026#34;https://github.com/Harry-kp/vortix/releases/download/v{{ vortix_version }}/vortix_{{ vortix_version }}_{{ \u0026#39;amd64\u0026#39; if ansible_architecture == \u0026#39;x86_64\u0026#39; else \u0026#39;arm64\u0026#39; }}.deb\u0026#34; dest: \u0026#34;/tmp/vortix_{{ vortix_version }}.deb\u0026#34; mode: \u0026#34;0644\u0026#34; when: - install_vortix | bool - vortix_check.rc != 0 Bootstrapping Tokyo Night, Envy Code R, Neovim, and tmux The companion playbook (common/interface) sets up the terminal interface:\nEnvy Code R Nerd Font Mono: Automatically selects ~/Library/Fonts on macOS or ~/.local/share/fonts/EnvyCodeR-Nerd-Font on Linux, extracts the Regular, Bold, and Italic TTF weights from the Nerd Fonts archive, and runs fc-cache -f on Linux only when changes occur. Neovim + lazy.nvim: Renders ~/.config/nvim/init.lua from init.lua.j2 and pre-clones lazy.nvim (stable branch, --depth 1) so Neovim starts cleanly on first launch. tmux + TPM: Renders ~/.tmux.conf and pre-clones both TPM (tmux-plugins/tpm) and tokyo-night-tmux. Passing -e \u0026quot;theme_variant=night\u0026quot; at runtime switches both Neovim and tmux between Tokyo Night variants (storm, night, moon, day). ","permalink":"https://jlte.us/blog/ansible-multi-os-workstation-playbooks/","summary":"Structuring idempotent Ansible playbooks that run on macOS, Ubuntu/Debian, and Arch Linux with -c local -i \u0026rsquo;localhost,\u0026rsquo; — normalizing Homebrew, APT, and Pacman, fixing Debian binary naming quirks, and bootstrapping Neovim + tmux.","title":"Zero-Inventory Ansible Playbooks for macOS, Ubuntu, and Arch Linux"},{"content":"If you have kids in school using Canvas LMS (*.instructure.com), checking grades as a Parent / Observer through the web UI is surprisingly painful. To figure out why a grade moved—or whether an assignment is missing across two kids and twelve courses—you have to switch observed students in a dropdown, open each course one by one, click into the Grades tab, and mentally cross-reference category weights (Tests 55%, Quizzes 30%, Homework 15%).\nI wanted a single command I could run from my terminal (or on a weekday cron job) that would pull every observed student\u0026rsquo;s courses, overall scores, assignment category weights, and submission statuses in seconds—and render them as both a colorized terminal table and a self-contained HTML dashboard.\nThat turned into canvas-scraper: a cross-platform Go binary with zero runtime dependencies.\nArchitecture: Two-Tier Authentication \u0026amp; Local Cache Many school Canvas instances sit behind SSO login flows, SAML redirects, or MFA prompts where generating a personal Canvas API token in the UI is disabled for student/parent accounts. On the other hand, driving a full headless browser to click through twelve course pages on every run is slow and fragile.\ncanvas-scraper uses a two-tier hybrid approach:\n┌────────────────────────────────────────────────────────────────────────┐ │ Canvas Scraper (Go) │ └────────────────────────────────────────────────────────────────────────┘ │ │ ▼ (If session expired / fresh) ▼ (If session valid) ┌───────────────────────────┐ ┌───────────────────────────┐ │ Headless Chrome (Chromedp)│ │ Go HTTP Client (REST API) │ │ - Authenticates securely │ │ - Fetches users/observees│ │ - Saves session cookies │───────►──────────│ - Fetches courses/scores │ │ to storage_state.json │ │ - Fetches groups/submits │ └───────────────────────────┘ └───────────────────────────┘ │ ▼ ┌───────────────────────────┐ │ Local Cache Envelope │ │ (.cache_raw.json) │ └───────────────────────────┘ Session Persistence (storage_state.json): On the very first run (or when cookies expire), canvas-scraper uses chromedp to complete the login flow and writes the session cookies to storage_state.json in Playwright-compatible format. (If your school requires MFA/2FA, running ./canvas-scraper --headed once lets you approve the prompt in a visible window.) Direct Go HTTP Client against /api/v1/: Once storage_state.json exists, Chrome isn\u0026rsquo;t launched at all—the scraper attaches the session cookies to a native Go http.Client and queries Canvas\u0026rsquo;s REST endpoints directly: Discovers observed students via /api/v1/users/self/observees. Pulls active courses and current grading-period scores via /api/v1/courses?enrollment_state=active\u0026amp;include[]=total_scores\u0026amp;include[]=current_grading_period_scores. Joins assignment groups, category weights, and assignments in a single request per course (/api/v1/courses/:id/assignment_groups?include[]=assignments) with polite jittered pacing (150–300ms) between courses. Instant Local Data Cache (CACHE_TIMER): Raw API results are cached in output/.cache_raw.json (default 10m, configurable to 2h, 7d, etc.). Any subsequent CLI filter or sort runs instantaneously against the local cache in under 10 milliseconds without touching Canvas servers. Interactive Single-File HTML Dashboard (--web) Passing --web generates output/canvas_grades.html and automatically opens it in your default browser:\nThe generated HTML file has zero external CSS or JavaScript dependencies—everything is inlined so you can email it, drop it on a phone, or archive it:\nAutomatic Light \u0026amp; Dark Mode via prefers-color-scheme. Courses Summary Table with color-coded grade badges (A in green, B in cyan, C in yellow, \u0026lt;70% in red) and inline category weight formulas. Per-Course Assignment Cards with direct clickable links back to each assignment on Canvas, points earned/possible, percentage, category weight, and status badges (Graded, Missing, Late, Excused, Unsubmitted). Instant CLI Filtering \u0026amp; Sorting Because filtering runs against the local cache envelope, you can slice the gradebook from the command line immediately:\n# Sort assignments by urgency (Missing/Overdue -\u0026gt; Late -\u0026gt; Submitted -\u0026gt; Graded) ./canvas-scraper --sort status # Filter to a specific student on a Parent/Observer account ./canvas-scraper --student Alex # Show only assignments due in the next 7 days ./canvas-scraper --due-within 7d # Investigate trouble spots: Missing work or graded items below 75% in major categories (\u0026gt;= 30% weight) ./canvas-scraper --missing-days 1 ./canvas-scraper --below 75 --min-weight 30 AI-Ready Markdown (--markdown), CSV \u0026amp; JSON Deliverables In addition to the terminal UI and HTML dashboard, canvas-scraper writes structured data files to output/:\ncanvas_grades.md (--markdown): A clean Markdown briefing containing an executive summary table, a pre-extracted Action Items section (all missing, overdue, or late assignments across all courses), and full course breakdowns—formatted so you can pipe it straight into a local LLM (glow output/canvas_grades.md or an AI study-plan prompt). courses_summary.csv, assignment_weights.csv, assignments_detail.csv, and canvas_grades.json: Ready for spreadsheet tracking or custom scripts. Download or Build from Source Pre-compiled binaries for macOS (Apple Silicon \u0026amp; Intel), Linux (amd64 \u0026amp; arm64), and Windows (.exe) are published automatically via GitHub Actions:\nGitHub Repository: jdansak/Canvas-Scraper Latest Release Binaries: github.com/jdansak/Canvas-Scraper/releases/latest ","permalink":"https://jlte.us/blog/canvas-scraper-go-gradebook-cli-and-dashboard/","summary":"Inside canvas-scraper: a single-binary Go tool for Canvas LMS that combines one-time headless Chrome authentication with fast REST API scraping, local caching, instant CLI filtering, and interactive HTML / Markdown exports.","title":"Building canvas-scraper: A Fast Go CLI \u0026 Web Dashboard for Canvas LMS Gradebooks"},{"content":"AMD\u0026rsquo;s Ryzen AI MAX+ 395 (Strix Halo) with the integrated Radeon 8060S (gfx1150 / RDNA 3.5) and 128 GB of unified LPDDR5x memory is a sweet spot for local LLM inference. Because the CPU and GPU share the same physical memory pool, you can expose 100 GiB directly to the GPU via the Graphics Translation Table (GTT)—enough to run gpt-oss-120b (MXFP4) or Qwen3.6-35B-Q8 with a 262k context window completely offloaded to the GPU.\nGetting RDNA 3.5 (gfx1150), ROCm 7.1, llama.cpp, and multi-model routing working reliably on Ubuntu involves a few sharp edges. To make the machine (max) 100% reproducible, the entire setup lives in a 4-stage Ansible playbook suite (workerNode/site.yaml).\nworkerNode/ ├── site.yaml # Master playbook (runs all 4 stages in order) ├── llama_ROCm.yaml # Stage 1: Kernel GTT/TTM, ROCm 7.1, llama.cpp build \u0026amp; systemd router ├── llama_router.yaml # Stage 2: Downloads the 4 active GGUF models using hf CLI ├── llama_https.yaml # Stage 3: Generates SAN TLS certs \u0026amp; configures HTTPS (:8443) └── opencode_bootstrap.yaml # Stage 4: Installs OpenCode CLI, opencode.json, agents \u0026amp; 30 skills Stage 1: Unlocking a 100 GiB GPU GTT Aperture Out of the box, the Linux amdgpu driver only exposes a portion of system RAM to the GPU. If you try to load a 65+ GB model with --n-gpu-layers 999, allocation fails or falls back to CPU RAM.\nTo allocate a 100 GiB GPU aperture, llama_ROCm.yaml configures both amdgpu.gttsize (in MiB) and the Linux kernel Translation Table Maps (ttm) page limits (in 4 KiB pages):\n# Unified Memory Architecture (UMA) \u0026amp; TTM Tuning (128GB LPDDR5x on Ryzen AI MAX+ 395) amdgpu_gtt_size_mb: 102400 # 100 GB GTT allocation for RDNA 3.5 APU ttm_pages_limit: 26214400 # 100 GB in 4KB pages (critical for \u0026gt;60GB models) ttm_page_pool_size: 26214400 hsa_override_gfx: \u0026#34;11.5.0\u0026#34; # Strix Halo RDNA 3.5 (Radeon 8060S / gfx1150) ISA target After updating /etc/default/grub and rebooting, you can verify the active GTT pool and live GPU residency via sysfs:\nawk \u0026#39;{printf \u0026#34;GTT total: %.1f GiB\\n\u0026#34;, $1/1073741824}\u0026#39; /sys/class/drm/card*/device/mem_info_gtt_total awk \u0026#39;{printf \u0026#34;GTT used: %.1f GiB\\n\u0026#34;, $1/1073741824}\u0026#39; /sys/class/drm/card*/device/mem_info_gtt_used (Note: the DRM device index can flip between card0 and card1 across reboots, so always glob card* rather than hardcoding card0.)\nWorking Around Two ROCm 7.1 Gotchas on gfx1150 libhsa-runtime64 Segfault \u0026amp; libxml2.so.2 Shim: On Ubuntu 26.04 (kernel 7.0.x), the upstream ROCm /opt/rocm/lib/libhsa-runtime64.so can segfault during HIP initialization on gfx1150, whereas the distro-packaged libhsa-runtime64.so.1 runs cleanly. Additionally, ROCm\u0026rsquo;s LLVM toolchain expects libxml2.so.2 while newer Ubuntu releases ship libxml2.so.16. The playbook creates /usr/local/lib/rocm-compat with a libxml2.so.2 symlink shim and orders LD_LIBRARY_PATH so the distro HSA runtime loads first. Isolated llama.cpp Build with patchelf: llama_ROCm.yaml compiles llama.cpp specifically for AMDGPU_TARGETS=gfx1150 with -DGGML_HIP=ON -DGGML_HIPBLAS=ON -DLLAMA_OPENSSL=ON -DBUILD_SHARED_LIBS=ON, installs the shared libraries into /usr/local/lib/llamacpp/bin, and sets the binary RUNPATH via patchelf so /usr/local/bin/llama-server never collides with system libraries. Stage 2: On-Demand Multi-Model Routing \u0026amp; Speculative Decoding Instead of running multiple llama-server processes that compete for memory, a single llama-server.service runs in router mode:\n/usr/local/bin/llama-server \\ --models-dir /opt/llama_models/dir \\ --models-max 1 \\ --ctx-size 262144 \\ --n-gpu-layers 999 \\ --cont-batching \\ --metrics \\ --api-key-file /etc/llama-server/api_key Each subdirectory in /opt/llama_models/dir/\u0026lt;ModelID\u0026gt;/ registers as a selectable model ID on /v1/chat/completions. With --models-max 1, llama-server loads the requested model into the 100 GiB GTT window on demand and evicts the previous one automatically.\nllama_router.yaml provisions four models using the Hugging Face CLI (hf download):\nQwen3.6-35B-Q8 (Qwen3.6-35B-A3B-Q8_0.gguf + mtp-Qwen3.6-35B-A3B-Q8_0.gguf): Placing the Multi-Token Prediction (mtp-*) draft file in the same directory enables automatic speculative decoding (~50% faster token generation). gpt-oss-120b (gpt-oss-120b-MXFP4.gguf): 120B parameters for heavy architectural reasoning and code review. Devstral-Small-2507 (Devstral-Small-2507-Q5_K_M.gguf): Fast agentic tool-calling (unsloth build with working Jinja tool templates). GLM-4.7-Flash (GLM-4.7-Flash-Q5_K_M.gguf): Low-latency completions and sub-agent queries. Using --api-key-file /etc/llama-server/api_key (root:goobey, mode 0640) instead of --api-key ensures the secret never appears in ps aux output.\nStages 3 \u0026amp; 4: HTTPS \u0026amp; OpenCode Bootstrap llama_https.yaml generates a local Root CA and SAN certificate (DNS:max, DNS:max.local, DNS:max.ocelot-company.ts.net, LAN IP, and Tailscale IP) so HTTPS clients can connect on :8443 while preserving local HTTP on :8080. Finally, opencode_bootstrap.yaml installs the OpenCode CLI, configures ~/.config/opencode/opencode.json against the local router, and deploys custom specialist agents (ansible-specialist) and 30 engineering skills.\n","permalink":"https://jlte.us/blog/amd-strix-halo-rocm-llama-cpp-ansible/","summary":"How I automated a 128 GB unified-memory AMD Ryzen AI MAX+ 395 (Strix Halo gfx1150) box to run 60+ GB GGUF models in GPU memory using Ansible, kernel TTM/GTT tuning, ROCm 7.1, and llama-server router mode.","title":"Provisioning an AMD Strix Halo Local LLM Node with Ansible, ROCm 7.1, and llama.cpp"},{"content":"Getting Splunk up and running on a fresh Ubuntu or Debian box takes about five minutes. There is one catch with Splunk 10.x that will silently burn you if you are running as root — more on that below.\nFind the Latest Version Splunk does not publish a generic \u0026ldquo;latest\u0026rdquo; redirect URL — the version is always baked into the link. You can try to pull it dynamically by scraping the download page:\nSPLUNK_URL=$(curl -s \u0026#39;https://www.splunk.com/en_us/download/splunk-enterprise.html\u0026#39; \\ | grep -oP \u0026#39;https://download\\.splunk\\.com/products/splunk/releases/[^\u0026#34;]+linux-amd64\\.deb\u0026#39; \\ | head -1) echo \u0026#34;$SPLUNK_URL\u0026#34; Fair warning: the download page is JavaScript-rendered, so this grep often comes back empty. If $SPLUNK_URL is blank, just head to the Splunk Enterprise download page directly, grab the .deb link, and hard-code it. At the time of writing the current release is 10.2.1.\nDownload and Install If the dynamic URL resolved above, use it directly. Otherwise substitute the pinned URL:\n# Use dynamic URL if it resolved, otherwise fall back to pinned version SPLUNK_URL=${SPLUNK_URL:-\u0026#34;https://download.splunk.com/products/splunk/releases/10.2.1/linux/splunk-10.2.1-c892b66d163d-linux-amd64.deb\u0026#34;} wget -O /tmp/splunk.deb \u0026#34;$SPLUNK_URL\u0026#34; dpkg -i /tmp/splunk.deb rm /tmp/splunk.deb Start Splunk If you are running as root — which is common on a homelab or lab VM — you must pass --run-as-root. Without it, Splunk 10.x silently exits and gives you no useful error message.\n/opt/splunk/bin/splunk start \\ --accept-license --answer-yes --no-prompt \\ --run-as-root --seed-passwd changeme Replace changeme with whatever password you want for the admin account.\nEnable Boot-Start So Splunk comes back up automatically after a reboot:\n/opt/splunk/bin/splunk enable boot-start -user root \\ --accept-license --answer-yes --no-prompt --run-as-root Verify /opt/splunk/bin/splunk status ss -tlnp | grep 8000 The web UI will be at http://\u0026lt;your-server-ip\u0026gt;:8000. Log in with admin and the password you set above.\nStaying Current Since there is no rolling \u0026ldquo;latest\u0026rdquo; URL, the easiest way to stay up to date is to check the Splunk release notes periodically and update the version string in your install script when a new release drops.\n","permalink":"https://jlte.us/blog/splunk-install/","summary":"A quick guide to downloading and installing the latest Splunk Enterprise on any Ubuntu or Debian system, including the \u0026ndash;run-as-root gotcha on Splunk 10.x.","title":"Installing Splunk Enterprise on Ubuntu or Debian"},{"content":"Welcome to the blog. Posts here cover enterprise infrastructure, Linux, Ansible automation, local AI hardware, security, and anything else worth documenting.\n","permalink":"https://jlte.us/blog/hello-world/","summary":"First post — notes on infrastructure, security, and things worth writing down.","title":"Hello World"},{"content":"Open-source utilities I\u0026rsquo;ve built in Rust and Go to solve everyday operational and workflow headaches.\n1. nettui — Native macOS Network TUI in Rust GitHub: jdansak/OSX_network_TUI · Read the full project article →\nA native, compiled Rust terminal user interface (TUI) for inspecting, controlling, and configuring macOS network settings—wired interfaces, Wi-Fi connections, VPN tunnels, and 802.1Q VLANs—without clicking through nested System Settings panes.\nBuilt with ratatui, Apple\u0026rsquo;s CoreWLAN and CoreLocation frameworks (via objc2), getifaddrs(3), /usr/sbin/networksetup, and /usr/sbin/scutil. Zero Python or shell scripts.\nKey Capabilities Wired \u0026amp; Wireless Services (1 Interfaces): Live link state, BSD device names (en0, en7, vlan0, utun*), interactive IPv4 modal (DHCP, Manual, Manual with DHCP Router), custom DNS \u0026amp; search domains, DHCP lease renewal, IPv6 toggle, and MTU tuning. VPN Tunnel Control (1 Interfaces): Integrates with scutil --nc to inspect and toggle Tailscale, WireGuard, IKEv2, L2TP, and IPSec tunnels directly from the keyboard (c). 802.1Q VLAN Management (1 Interfaces): Create (v), re-tag (g), and delete (x) virtual 802.1Q VLAN interfaces (1–4094) on any physical parent adapter. Wi-Fi Telemetry \u0026amp; Scanner (2 Wi-Fi): Live RSSI, noise floor, SNR, PHY mode (802.11ax), channel width/band, nearby AP scanner (s), and macOS Keychain network joining—using an embedded Mach-O __TEXT,__info_plist section so macOS Sonoma/Sequoia grants CoreLocation access to unredacted SSIDs. git clone https://github.com/jdansak/OSX_network_TUI.git cd OSX_network_TUI cargo build --release ./target/release/nettui 2. canvas-scraper — Canvas LMS Gradebook CLI \u0026amp; Web Dashboard (Go) GitHub: jdansak/Canvas-Scraper · Latest Releases (macOS / Linux / Windows) · Read the full project article →\nA standalone Go tool that extracts courses, overall grades, category weights (Tests 55%, Quizzes 30%, Homework 15%), and individual assignment submissions from any Canvas LMS instance (https://\u0026lt;school\u0026gt;.instructure.com). Built with first-class support for Parent / Observer accounts observing multiple students.\nKey Capabilities Two-Tier Session \u0026amp; Data Caching: Authenticates once via headless Chrome (chromedp—or --headed for MFA/2FA), saves Playwright-compatible cookies to storage_state.json, and executes subsequent fetches directly over Go\u0026rsquo;s native HTTP client with local CACHE_TIMER persistence (.cache_raw.json). Interactive Single-File HTML Dashboard (--web): Generates a dependency-free HTML5 gradebook dashboard with automatic Light/Dark mode, grade badges, and per-course tables. AI-Ready Markdown (--markdown), CSV \u0026amp; JSON Exports: Produces canvas_grades.md with pre-extracted missing/late action items alongside courses_summary.csv, assignment_weights.csv, assignments_detail.csv, and canvas_grades.json. Instant Filtering \u0026amp; Sorting: Filter cached results in milliseconds by --student, --course, --due-within 7d, --missing-days 2, --below 75, --min-weight 30, or --status missing,late, and sort by status, score, grade, date, or weight. # Run with status urgency sorting and open the interactive web dashboard ./canvas-scraper --sort status --web # Show only missing/overdue assignments or graded items below 75% ./canvas-scraper --missing-days 1 ./canvas-scraper --below 75 --min-weight 30 ","permalink":"https://jlte.us/projects/","summary":"Open-source tools built in Rust and Go: nettui (macOS Network TUI) and canvas-scraper (Canvas LMS Gradebook CLI \u0026amp; Web Dashboard).","title":"Projects"},{"content":" John Dansak\nSenior Site Reliability Engineer · Marietta / Atlanta, GA\njohn.dansak@outlook.com Background I\u0026rsquo;ve spent most of my career at Bank of America, working through just about every layer of the infrastructure stack. Early on that meant keeping 83,000 workstations and 6,500 servers running across 6,000+ retail branches. Later it was VMware at scale across 20,000+ data center servers, then enterprise architecture work, and now I\u0026rsquo;m back in hands-on SRE — diagnosing production incidents, building tooling, and finding the things that are quietly about to break.\nOutside of work I run a homelab on Proxmox, automate Linux and macOS setups with Ansible, build local AI inference systems on AMD Strix Halo hardware, and work toward certifications as an excuse to go deeper on topics I find interesting.\nExperience Senior Site Reliability Engineer Bank of America — CFPT Production Readiness · Mar 2026 – Present\nRoot cause analysis for production incidents involving TLS failures, WebSocket disruptions, and load balancer issues using Wireshark, Splunk, and Dynatrace APM. Build custom scripts to automate proxy log parsing and packet capture (pcap) analysis, reducing time-to-resolution for connectivity incidents. Manage LXC container infrastructure on Proxmox for service isolation across VLAN-segmented environments. Partner with development and infrastructure teams to close monitoring and alerting gaps before they become incidents. Architect — APS\u0026amp;E Bank of America · Feb 2021 – May 2025\nOwned architecture definitions from initial design through implementation, keeping designs adaptable as requirements changed. Ran design and code reviews to validate that engineering solutions matched operational and business requirements. Worked with product management to keep the roadmap connected to what engineering could realistically deliver and cleared blockers across teams. Systems Engineer — FCE Refresh \u0026amp; Security Bank of America · Mar 2017 – Apr 2022\nLed the hardware and OS refresh of 6,000+ servers and 83,000+ workstations across the retail branch footprint, delivering a 3-year project on time with a 5-engineer team. Owned all security audit findings for the retail space; partnered with audit teams to identify and remediate vulnerabilities. Led the migration of a legacy Perl automation codebase to PowerShell. Systems Engineer — Virtual / Windows Bank of America · May 2013 – Mar 2017\nAdministered VMware vCenter (4.x/5.x) across cloud and legacy environments; managed DRS, Storage DRS, HA, and Resource Pools across all lines of business. Provided remote Break/Fix support for 20,000+ servers (IBM, HP, Dell) running Windows Server 2003–2012 and ESXi 3.5–5.5 across all Bank of America and Merrill Lynch data centers. Automated deployments and QA checks across Windows and ESX environments and collected compliance artifacts for audit. Systems Engineer — Retail Operations Bank of America · 2003 – 2013\nDay-to-day operations for 6,000+ retail financial centers: 6,500 servers, 83,000 workstations, and 15,000 printers. Built and maintained mobile financial centers for disaster recovery, executing rapid field deployments nationwide after natural disasters. Senior Network Consultant Creative Solutions · 2001 – 2003\nAssessed client networks, uncovered latent routing and infrastructure issues, and implemented permanent fixes. Provided day-to-day support and hands-on systems engineering for small and mid-size businesses. Skills \u0026amp; Technologies Domain Technologies \u0026amp; Practices SRE \u0026amp; Reliability SLO / SLI / SLA, Incident Management, Observability, Post-mortems, Change Management Cloud \u0026amp; Infrastructure Red Hat Enterprise Linux, Ubuntu / Debian, Proxmox VE, LXC Containers, Docker, VMware vSphere / ESXi, Microsoft Azure, Windows Server Networking TCP/IPv4, TLS Inspection, Wireshark / pcap, DNS / DHCP, VPN / Tailscale, Cisco Routing \u0026amp; Switching Automation \u0026amp; Monitoring Ansible, Python, PowerShell, Bash, Splunk Enterprise, Dynatrace APM, ITIL / ITSM Certifications Red Hat Certified System Administrator (RHCSA) CompTIA Security+ eJPT (eLearnSecurity Junior Penetration Tester) Microsoft Certified: Azure Administrator (AZ-104) Microsoft Certified: Azure Fundamentals (AZ-900) Cisco CCNA Route/Switch VMware VCP 5.5 Windows NT/2000/2003 MCSE Education Southern College of Technology (Marietta, GA · 1997–1998)\n","permalink":"https://jlte.us/resume/","summary":"20+ years in enterprise infrastructure, virtualization, architecture, and Site Reliability Engineering.","title":"Resume \u0026 Background"}]