Skip to content

Dispatches

I'll Be Damned

Reflection

Some roads don't take you anywhere new; the ones that drag you back to what you can't forget.

For me, that’s the haunted stretch of State Road 39 that pulls away just north of the Miller County line, past my Grandaddy’s broken fences and the hard-baked clay ruts. It's where the world closes in around the pines and pastures, and you're just waiting to see what the afternoon is going to break next.

I never had the luxury of romanticizing this place. I was raised to be useful to a land that looks peaceful on a postcard but cuts deep when you’re trapped inside it. Where the stench of diesel and manure hangs heavy, and hardship settles onto your shoulders the same way the red dirt stains your boot seams.

These songs are how I drag those memories into the light.

They're written for the folks who carry the weight in total silence, only to get up tomorrow and do it all over again.

Lyrics

I'll Be Damned

Morning breaks like bad news
Over land that never speaks
Wind moves through the dead rows
Like it’s lookin’ for relief
Daddy’s boots by the back door
Dirt already on my hands
I was barely old enough
To know the weight of this damn land

     It’ll feed you, it’ll fight you
     Make you curse and make you pray
     And every year I tell myself
     This’ll be the year I walk away

     This is where the strong stay silent
     And the weak don’t last too long
     Where hope is just a habit
     And quittin’ feels too wrong
     Yeah, the roots go deep and the debt is steep
     And the devil holds my hand
     If I’m leavin’, I’ll be bleedin’
     If I’m stayin’, I’ll be damned

Iron breaks in the July heat
Right when the rain is runnin’ late
Banker’s callin’, diesel’s climbin’
Staring down an open gate
Old men nod across the fence line
Knowin’ what I just can’t say
You don’t choose this kind of livin’
It just bleeds into your veins

     Repeat Pre-Chorus

     Repeat Chorus

Red dust settled on the dash
Sweat dryin’ on my shirt
Yeah, every road around here
Starts and ends in dirt
You can tell yourself a story
But the ground don’t care at all
It just holds what we leave it
When our names quit gettin’ called

     Repeat Chorus

Writer: J. Ryan Johnson (BMI)
Copyright: © 2026 J. Ryan Johnson. All rights reserved.
Phone: +1 (407) 902-5419
Email: hello [at] tenthirtyam [dot] org

Audio Disclaimer

Lyrics: Original | Audio: AI-Generated

I am a songwriter and a musician, but I am not the voice meant to inhabit these verses.

I've used AI to bridge the gap for the concept demos, crafted to serve as blueprints that capture the genre, tone, and weary soul I hear for each song.

They exist as an invitation, offered in the hope that these lyrics will eventually reach the hands of an artist and storyteller who can bring them fully into the light.

Until then, they remain as they were born: quiet reflections on the grit and grace found just north of the county line.

Generating a Python SDK from an OpenAPI Specification

If your API ships an OpenAPI spec, OpenAPI Generator can build the Python client from it. You don't have to hand-write models and endpoint wrappers.

Most tutorials stop at generate. After that you're still choosing generator options, picking a spec with enough surface area to matter, locking dependencies, running smoke tests, and keeping the repo sane when the spec changes again.

This walkthrough uses a compact OpenAPI 3.1 example. I use Astral uv for lock, sync, test, and build because that's what I run locally. The generator config and generate steps are the same with pip or Poetry.

Prerequisites

You need three things:

Start with a Useful Specification

Start with an API specification that'll actually exercise the generator: tags, pagination, request bodies, enums, timestamps, and a reusable error schema, not one of the generic "Pet Store" examples.

Here is a compact OpenAPI 3.1 example:

openapi: 3.1.0
info:
  title: Dispatch API
  version: 1.0.0
  summary: A compact task-tracking API for SDK generation examples.
  description: |
    This example stays small enough for a blog post while still looking like a real API.
    It includes pagination, reusable schemas, enums, timestamps, bearer auth, and a
    structured error response.
jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema
servers:
  - url: https://api.example.com/v1
    description: Production
tags:
  - name: Tasks
    description: Manage tracked work items.
paths:
  /tasks:
    get:
      tags: [Tasks]
      operationId: listTasks
      summary: List tasks
      parameters:
        - name: status
          in: query
          required: false
          description: Filter tasks by workflow state.
          schema:
            $ref: "#/components/schemas/TaskStatus"
        - name: limit
          in: query
          required: false
          description: Maximum number of tasks to return.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
        - name: cursor
          in: query
          required: false
          description: Opaque pagination cursor from a previous response.
          schema:
            type: string
      responses:
        "200":
          description: A page of tasks.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TaskListResponse"
        "401":
          description: Authentication failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      tags: [Tasks]
      operationId: createTask
      summary: Create a task
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateTaskRequest"
      responses:
        "201":
          description: Task created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Task"
        "400":
          description: Invalid request body.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /tasks/{taskId}:
    get:
      tags: [Tasks]
      operationId: getTask
      summary: Get a task by ID
      parameters:
        - $ref: "#/components/parameters/TaskId"
      responses:
        "200":
          description: The requested task.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Task"
        "404":
          description: Task not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    patch:
      tags: [Tasks]
      operationId: updateTask
      summary: Update a task
      parameters:
        - $ref: "#/components/parameters/TaskId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateTaskRequest"
      responses:
        "200":
          description: Task updated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Task"
        "404":
          description: Task not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  parameters:
    TaskId:
      name: taskId
      in: path
      required: true
      description: Stable task identifier.
      schema:
        type: string
        format: uuid
  schemas:
    TaskStatus:
      type: string
      enum:
        - queued
        - running
        - completed
        - failed
    Task:
      type: object
      required:
        - id
        - title
        - status
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
          minLength: 1
          maxLength: 120
        description:
          type:
            - string
            - "null"
        status:
          $ref: "#/components/schemas/TaskStatus"
        labels:
          type: array
          items:
            type: string
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    CreateTaskRequest:
      type: object
      required:
        - title
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 120
        description:
          type:
            - string
            - "null"
        labels:
          type: array
          items:
            type: string
    UpdateTaskRequest:
      type: object
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 120
        description:
          type:
            - string
            - "null"
        status:
          $ref: "#/components/schemas/TaskStatus"
        labels:
          type: array
          items:
            type: string
    TaskListResponse:
      type: object
      required:
        - items
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/Task"
        nextCursor:
          type:
            - string
            - "null"
    Error:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: string
        message:
          type: string
        requestId:
          type:
            - string
            - "null"
security:
  - bearerAuth: []

This example is small enough to walk through without pasting pages of YAML.

Configure the Python Generator

Create a generator configuration file and make the build system choice explicit:

{
  "packageName": "python_dispatch_sdk",
  "projectName": "pythn-dispatch-sdk",
  "packageVersion": "1.0.0",
  "packageUrl": "https://example.com/dispatch-sdk",
  "description": "Python SDK for the Dispatch API.",
  "author": "The Authors",
  "authorEmail": "[email protected]",
  "license": "MIT",
  "library": "httpx",
  "buildSystem": "hatchling",
  "hideGenerationTimestamp": true
}

In that configuration, these fields matter most:

  • packageName: the import name, so use snake_case
  • projectName: the distribution name, so use hyphens
  • library: httpx: the default I'd pick today unless you need urllib3 (see Requests vs. HTTPX)
  • buildSystem: hatchling: keeps the generated pyproject.toml aligned with current Python packaging practice
  • hideGenerationTimestamp: true: removes a useless source of churn from regenerated files

Don't flip disallowAdditionalPropertiesIfNotPresent without reading the docs first. OpenAPI Generator's Python generator docs call true the old behavior and false the spec-compliant one. It's a compatibility switch, not a free hardening flag.

Generate the SDK

With the spec and config in place, generate the client:

openapi-generator-cli generate \
  -i snippets/example-openapi-3.1-dispatch-api.yaml \
  -g python \
  -o ./client \
  -c snippets/openapi-generator-config-example.json

That produces a Python package, API classes, models, tests, and a pyproject.toml. The point is to treat that output as generated source, not something you hand-edit line by line.

When the upstream specification changes, rerun the same command and review the diff. If you're hand-editing generated files, you'll fight every regeneration.

Manage the Generated Package with uv

The generator hands you a pyproject.toml. Below I use uv to compile a lockfile, sync the environment, run tests, and build artifacts. Swap in pip or Poetry if that's what you already use.

Lock the generated dependencies:

cd client
uv pip compile pyproject.toml -o requirements.txt --upgrade

Create a local environment and sync it exactly to the lockfile:

uv venv
source .venv/bin/activate
uv pip sync requirements.txt

On Windows, activate with .venv\Scripts\activate instead.

Install the generated package plus test tools:

uv pip install -e . pytest pytest-cov
python -m pytest test -v

uv pip compile writes a lock you can review in requirements.txt. uv pip sync installs exactly that lock instead of whatever was already on the machine.

Build the Distribution

Once the smoke tests pass, build the package artifacts:

uv build --no-sources

That writes the source distribution and wheel into dist/.

Keep Your Repository Clean

Generated SDK repositories can collect a lot of junk unless you make the boundaries explicit.

At a minimum, ignore local environments, caches, build output, and generator metadata:

.venv/
__pycache__/
*.py[cod]
dist/
build/
.pytest_cache/
.coverage
htmlcov/
.openapi-generator/
git_push.sh

I'd also rewrite the generated README.md before publishing. The scaffold is fine for a first pass, but it's not documentation you should put your name on.

Optional: Template Overrides

If you need custom copyright or SPDX headers, use template overrides instead of editing generated files after the fact:

openapi-generator-cli author template -g python -o ./templates

From there, edit the copied Mustache templates and regenerate with -t ./templates. Keep the override directory small. The more templates you fork, the more work you'll inherit on generator upgrades.

Here is the generated workflow in one pass:

OpenAPI Python SDK Workflow Demo

Let the generator handle the scaffolding.

The specification quality, package boundaries, and how you ship releases are where the maintenance cost actually lives.

References

Making Sense of the Circle of Fifths

If the Circle of Fifths has always looked like one of those theory diagrams everyone swears is important but no one actually explains, you're not alone. The good news is that it's not really a chart about memorizing symbols. It's a map of how keys, chords, and musical motion relate to each other, and once you hear that on guitar, it starts feeling useful fast.

For a lot of players, the Circle of Fifths feels abstract because it gets introduced on paper instead of on the fretboard.

If my Decoding the Major and Minor Pentatonic Scales post was about understanding what's happening inside the pentatonic scale shapes, this one is about understanding why chords and keys pull the way they do around those scale shapes.

My goal here is to help you:

  • understand what the circle is actually showing you,
  • learn why moving by fifths sounds so strong in music,
  • connect nearby keys to nearby chord families, and
  • use the circle for progressions, songwriting, and practice.

Tip

The Circle of Fifths isn't mainly about memorizing key signatures. It's about seeing which keys are closely related, which chords want to lead to each other, and why certain progressions feel so natural.

What Is the Circle of Fifths?

At its core, the Circle of Fifths is just an ordered list of notes.

Start on C. Move up a perfect fifth and you get G. Move up another fifth and you get D.

Keep going and you get:

  • C
  • G
  • D
  • A
  • E
  • B
  • F#
  • Db
  • Ab
  • Eb
  • Bb
  • F
  • back to C

That's the circle.

If you move the other direction, you're moving by fourths, which lands on the same set of keys in reverse order.

"Why does that matter?"

Because music built from neighboring keys tends to share a lot of notes and chords. The closer two keys are on the circle, the more naturally they tend to connect.

Circle of Fifths Circle of Fifths

Guitar Translation

Think of the circle as a map of musical neighborhoods. Keys that sit next to each other are closely related. Keys across the circle feel more distant.

Why Fifths Sound So Strong

The interval of a fifth is one of the most stable and powerful sounds in music.

Guitar players already know this sound, even if they don't call it by name:

  • A basic power chord is built around the Root Note and the 5.
  • Power chords put the sound of the fifth under your fingers all over the neck.
  • A lot of strong chord movement in rock, blues, country, and pop follows fifth relationships!

The motion around the circle lines up with one of the most fundamental relationships in the instrument and in tonal music itself.

Tip

When you hear progressions like D - G - C or Em - Am - D - G, you're hearing the motion that lines up with the circle. And your ear recognizes that pull immediately.

Clockwise and Counterclockwise

One reason the circle matters is that it helps organize keys.

Moving clockwise:

  • goes up by fifths,
  • adds sharps, and
  • often feels like increasing harmonic brightness or tension

Moving counterclockwise:

  • goes up by fourths,
  • adds flats, and
  • often feels like a move toward warmer, flatter key areas

Here's a practical shorthand:

  • C has no sharps and no flats
  • one step clockwise, G, has 1 sharp
  • one more step, D, has 2 sharps
  • one step counterclockwise, F, has 1 flat
  • one more step, Bb, has 2 flats

Stop! You Don't Need to Memorize All of This at Once!

Start with just C, G, D, A on the sharp side and C, F, Bb on the flat side. That small map already explains a huge amount of real guitar music.

Relative Minor

The Circle of Fifths isn't only about major keys. Each major key has a Relative Minor that uses the same notes.

Examples:

  • C major and A minor
  • G major and E minor
  • D major and B minor

If that sounds familiar, it should. It's the same relationship we used in Decoding the Major and Minor Pentatonic Scales.

The circle helps you see those pairs as families:

  • major key on the outside
  • relative minor connected to the same note pool

That's useful when:

  • understanding songs that shift between major and minor moods,
  • finding related scales quickly, and
  • writing chord progressions that feel connected without feeling repetitive

vmcli: A Practical Guide for the VMware Fusion and Workstation Command Line

The command line is still the fastest way to script power state, disks, snapshots, and guest operations on with VMware Fusion (macOS) and VMware Workstation (Windows and Linux).

vmcli ships with both desktop hypervisors so you can drive the same lifecycle steps without opening the GUI.. Same lifecycle steps, no GUI.

This post covers the main modules with copy-and-paste examples.

Always confirm flags on your build with vmcli <module> --help and vmcli <vmx> <module> <command> --help, since Fusion and Workstation revisions do not always stay in lockstep.

Basic Syntax

vmcli is the cross-platform CLI for Fusion and Workstation. Each functional area (power, snapshots, storage, networking, guest operations) is a module with its own sub-commands. The usual shape is:

vmcli <vmx location> <module> <command> [args]

Important

The <vmx location> is required for commands working with VMs, and can be the first or last argument on the command line.

Global Options

Flag Description
-v, --version Display the version information.
--verbose Enable verbose logging.
-h, --help Display the help information.

Default Install Locations

CI runners and daemons often lack the same PATH as an interactive shell. Point scripts at the real binary when vmcli is not on PATH:

Platform Typical path
macOS (Fusion) /Applications/VMware Fusion.app/Contents/Public/vmcli
Windows (Workstation) C:\Program Files\VMware\VMware Workstation\vmcli.exe (or the x86 variant on some installs)

On Linux hosts, resolve the Workstation package path for your distro, or install location, then call that absolute path from automation.

If your host responds with vmcli: command not found, the binary is probably fine, but your PATH needs updating.

JSON Output for Scripting

Several query style commands accept -f json so you can parse output with jq or your language's JSON decoder instead of scraping plain text. Sub-command spelling is case sensitive (query vs Query), so match vmcli <module> --help on your build.

Example Purpose
vmcli <vmx> Power query -f json Structured power state.
vmcli <vmx> Snapshot query -f json Snapshot tree, UIDs, and current marker when the build supports it.
vmcli <vmx> Guest query -f json Guest and IP-style fields only while the VM is powered on with VMware Tools running (offline returns an error).
vmcli <vmx> Tools Query -f json Tools install and version status in JSON form.
vmcli <vmx> USB Query -f json Only on builds that list USB under vmcli --help; many Fusion vmcli trees omit USB entirely.

If -f json is missing on a sub-command, fall back to the default text layout or upgrade the desktop hypervisor build.

Core Modules and Commands

1. Power Operations (Power module)

Stop, Suspend, and Reset need -o <opType>; other rows below do not.

Command Required Args Description
vmcli <vmx> Power Start N/A Start the virtual machine.
vmcli <vmx> Power Stop -o <opType> Stop the virtual machine using the specified operation type.
vmcli <vmx> Power Pause N/A Pause the virtual machine execution.
vmcli <vmx> Power Unpause N/A Resume a paused virtual machine.
vmcli <vmx> Power Suspend -o <opType> Suspend the virtual machine using the specified operation type.
vmcli <vmx> Power Reset -o <opType> Reset the virtual machine using the specified operation type.
vmcli <vmx> Power Query N/A Query the current power state of the VM.

Power Start optional flags:

Flag Description
-p, --paused Power on in paused mode.
-s, --soft Power on in soft mode.

Valid -o opType values for Stop, Suspend, and Reset:

Value Description
hard Immediately cut power, the same as pulling the plug.
trySoft Attempt a graceful OS shutdown; fall back to hard if the guest doesn't respond.
soft Send an ACPI shutdown signal and wait for the guest to respond.
configDefault Use the operation type defined in the VM's configuration.

2. VM Management (VM & VMTemplate modules)

To create a new VM, use the VM module. Note that -g accepts a numeric preset and -c accepts any custom guest OS string.

Flag Required Description
-n, --name Yes Virtual machine name.
-d, --dirpath Yes Directory path where the VM will be created.
-g, --guesttype No Guest OS preset (numeric enum: 1–10).
-c, --custom-guesttype No Any custom guest OS type string (e.g., arm-ubuntu-64, ubuntu-64).

Example: Custom Guest OS Type

Use -c for named guest OS types such as arm-ubuntu-64 or ubuntu-64:

vmcli VM Create -n myVM -d ~/Desktop/ -c arm-ubuntu-64

The VMTemplate module is used for creating and deploying VM templates natively from the command line.

3. Snapshot Management (Snapshot module)

Snapshots give you quick checkpoints for testing and rollbacks. Commands that target an existing snapshot take a <uid> integer. Run Snapshot query first to list UIDs.

Command Required Args Description
vmcli <vmx> Snapshot Take <name> <name> Creates a snapshot. Use -d for a description and -m to include memory state.
vmcli <vmx> Snapshot Revert <uid> <uid> Revert the VM to the snapshot with the given UID.
vmcli <vmx> Snapshot Clone <uid> <filePath> <name> <uid>, <filePath>, <name> Clone a snapshot to a new .vmx file. Use -l for a linked clone.
vmcli <vmx> Snapshot Delete <uid> <uid> Removes the snapshot with the given UID. Use -d to also delete child snapshots.
vmcli <vmx> Snapshot query N/A Lists all snapshots and their UIDs.

!!! warning "Deleting and Reverting Snapshots" The VM must be powered off or suspended before using Snapshot Delete. Always run Snapshot query first to confirm the correct UID.

4. Hardware and Storage (Disk, Ethernet, Nvme, Sata modules)

Disk module. Create, extend, query, and configure virtual disks:

Sub-command Description
Disk Create -f <path> -a <adapter> -s <size> -t <type> Create a new virtual disk file.
Disk Extend <diskLabel> <newNumSectors> Extend an attached disk to a new size (in sectors).
Disk query Query the state of all disks attached to the VM.
Disk ConnectionControl Connect or disconnect a disk device.
Disk SetMode Set the persistence mode of the disk.
Disk SetReadOnly Mark a disk as read-only.
Disk SetBandwidthCap Set a bandwidth cap on the disk (bytes per second).
Disk Branch Create a new disk branch from the current VM state.

Valid -a adapter types for Disk Create: ide, buslogic, lsilogic (use lsilogic for all other types).

Valid -t disk types for Disk Create:

Type Description
0 Single growable virtual disk.
1 Growable virtual disk split into multiple files.
2 Pre-allocated virtual disk (single file).
3 Pre-allocated virtual disk split into multiple files.

Ethernet module. Configure virtual network adapters:

Sub-command Description
Ethernet query Query the current ethernet configuration.
Ethernet SetNetworkName <deviceLabel> <networkName> Connect an adapter to a named virtual network.
Ethernet SetConnectionType <deviceLabel> <connectionType> Set the connection type (e.g., nat, bridged, hostonly).
Ethernet SetVirtualDevice <deviceLabel> <deviceType> Set the virtual NIC type (e.g., vmxnet3, e1000e).
Ethernet SetPresent <deviceLabel> <bool> Add or remove a network adapter.

Other Hardware Modules:

Module Typical starting point
Nvme & Sata Controller and attachment sub-commands vary by build; see --help.
Serial Port presence and backing (file, pipe, network) via --help.

5. Configuration (ConfigParams module)

Read and write raw .vmx keys (RAM, CPU, nested virt, display name, and many hardware toggles).

Sub-command Description
ConfigParams query Print all current configuration entries for the VM.
ConfigParams SetEntry <name> <value> Write or overwrite a configuration entry.

Set bios.forceSetupOnce to TRUE for a one-time visit to firmware or EFI setup on the next boot; the hypervisor clears it after that cycle.

6. Shared Folders (HGFS module)

The Host Guest File System (HGFS) module manages the Shared Folders feature between host and guest. VMware Tools must be installed and running in the guest.

Sub-command Description
HGFS query Query the current shared folder configuration.
HGFS SetPresent <shareLabel> <bool> Add or remove a shared folder slot (for example sharedFolder0).
HGFS SetHostPath <shareLabel> <hostPath> Set the host path for that slot.
HGFS SetGuestName <shareLabel> <guestName> Name the guest sees (for example projects).
HGFS SetEnabled <shareLabel> <bool> Enable or disable a share without removing it.
HGFS SetReadAccess <shareLabel> <bool> Control read access on the share.
HGFS SetWriteAccess <shareLabel> <bool> Control write access on the share.

On many Fusion vmcli builds, HGFS sub-commands use the .vmx slot label (sharedFolder0, sharedFolder1, …), not an arbitrary string. Create the slot with SetPresent sharedFolder0 true (or matching ConfigParams entries), then set host path, guest name, and ACLs. Arbitrary labels such as projects alone are rejected until that slot exists.

7. Guest Operations (Guest module)

The Guest module executes operations directly inside a running VM. VMware Tools must be running for guest-side calls. Commands that start programs or touch the filesystem need -u <username> and -p <password>. Guest query omits -u / -p on supported builds, but the VM must still be powered on (offline returns an error). Confirm with vmcli <vmx> Guest query --help on your SKU.

Sub-command Description
Guest query Read guest state (for example OS string and networking) while the VM is on; combine with -f json for parsers.
Guest run Start a program inside the guest OS.
Guest ps List running processes in the guest.
Guest kill Terminate a process in the guest by PID.
Guest copyTo Copy a file from the host into the guest.
Guest copyFrom Copy a file from the guest back to the host.
Guest ls List directory contents inside the guest.
Guest mkdir Create a directory inside the guest.
Guest rm / rmdir Remove a file or directory inside the guest.
Guest mv / mvdir Move a file or directory inside the guest.
Guest env Show the guest's environment variables.
Guest createTempFile Create a temporary file in the guest.
Guest createTempDir Create a temporary directory in the guest.
Guest toolsproperties Show VMware Tools properties.

8. VMware Tools (Tools module)

Sub-command Description
Tools Query Query the state and version of VMware Tools in the guest.
Tools Install Mount the VMware Tools installer in the guest.
Tools Upgrade Upgrade VMware Tools to the latest available version.

9. Other Modules (MKS, Chipset, VProbes)

Module Description
MKS Mouse, keyboard, and screen (MKS) operations, including MKS captureScreenshot <output.png> while the VM displays video.
Chipset Configure low-level virtual chipset options.
VProbes Setup instrumentation probes inside the guest for performance analysis.

10. USB Devices (USB module)

Some Workstation (and occasional Fusion) builds expose a USB module. vmcli --help must list USB under Available modules; otherwise every vmcli … USB … invocation fails with Invalid/unrecognized argument "USB"`.

When the module exists:

Sub-command Description
USB Query Inspect USB devices visible to the VM; -f json when supported.
USB Connect <deviceId> Attach a host USB device to the guest.
USB Disconnect <deviceId> Release a device from the guest.

Device identifiers come from USB Query output; treat them as opaque strings.

11. Full VM clones (Clone module)

Separate from Snapshot Clone, some Workstation builds expose a top-level Clone module. Confirm with vmcli --help: many Fusion vmcli builds do not list Clone, so vmcli <vmx> Clone … fails the same way as a missing USB module.

Where Clone exists, sub-command names and flags have shifted across releases (CreateLinked, Create -t linked, and similar). Run vmcli Clone --help on the machine that will execute the job, then pin your automation to that text.

Practical Examples

Examples use VMX for the .vmx path and dirname "$VMX" for sibling files (extra disk, screenshots, logs). Use zsh or bash. On Windows, set VMX in the environment your script uses before each vmcli invocation.

!!! note "Where the .vmx path is" VM Create layout differs by product (flat folder vs *.vmwarevm bundle). Set VMX to the real .vmx after creation.

1. Create, tune, power on, optional firmware boot

vmcli VM Create -n test_vm -d ~/Desktop/ -c ubuntu-64
export VMX="$HOME/Desktop/test_vm.vmx"

vmcli "$VMX" ConfigParams SetEntry memsize 8192
vmcli "$VMX" ConfigParams SetEntry numvcpus 4
vmcli "$VMX" ConfigParams SetEntry vhv.enable TRUE
vmcli "$VMX" ConfigParams query | grep -E "memsize|numvcpus|vhv"

vmcli "$VMX" Power Start

# Optional: firmware or EFI setup on the *next* boot only (cleared after that boot)
# vmcli "$VMX" ConfigParams SetEntry bios.forceSetupOnce TRUE
# vmcli "$VMX" Power Start

2. Extra disk, JSON checks, screenshot

vmcli "$VMX" Disk Create \
  -f "$(dirname "$VMX")/test_vm-data.vmdk" \
  -a lsilogic \
  -s 50GB \
  -t 0
vmcli "$VMX" Disk query

# Same `-f json` calls as the table under "JSON output for scripting"
vmcli "$VMX" Power query -f json
vmcli "$VMX" Snapshot query -f json
vmcli "$VMX" Guest query -f json
vmcli "$VMX" Tools Query -f json
vmcli "$VMX" MKS captureScreenshot "$(dirname "$VMX")/test_vm-console.png"

3. Network (NAT to Bridged)

vmcli "$VMX" Ethernet query
vmcli "$VMX" Ethernet SetConnectionType ethernet0 bridged
vmcli "$VMX" Ethernet SetNetworkName ethernet0 "VMnet0"

4. Shared folders (HGFS)

shareLabel is the sharedFolderN slot (see the HGFS module table above).

vmcli "$VMX" HGFS SetPresent sharedFolder0 true
vmcli "$VMX" HGFS SetHostPath sharedFolder0 ~/Projects
vmcli "$VMX" HGFS SetGuestName sharedFolder0 projects
vmcli "$VMX" HGFS SetEnabled sharedFolder0 true
vmcli "$VMX" HGFS SetReadAccess sharedFolder0 true
vmcli "$VMX" HGFS SetWriteAccess sharedFolder0 true

5. VMware Tools

vmcli "$VMX" Tools Query
vmcli "$VMX" Tools Install
vmcli "$VMX" Tools Upgrade

6. Snapshots

vmcli "$VMX" Snapshot Take pre-upgrade -d "State before upgrading kernel"
vmcli "$VMX" Snapshot query
vmcli "$VMX" Power Stop -o trySoft
vmcli "$VMX" Snapshot Revert 1
# VM must be off or suspended to delete snapshots
vmcli "$VMX" Snapshot Delete 1
# When you need to drop child snapshots too, use -d before the UID (see `Snapshot Delete --help` on your build)
# vmcli "$VMX" Snapshot Delete -d 1

7. Guest Operations (Credentials)

Guest run, file copy, and ps need -u and -p (see the Guest module above). Export GUEST_PASS yourself; don't paste real passwords into shell history.

!!! warning "Credential Handling" Yes, -p on the command line is ugly. So is storing the password in a script file. For automation, use environment variables or a secrets manager and pass them at runtime instead of hard-coding them.

# Set GUEST_PASS in your environment before running these lines (never log real values).

vmcli "$VMX" Guest run -u root -p "$GUEST_PASS" \
  /bin/bash -c "apt-get update && apt-get upgrade -y"

vmcli "$VMX" Guest copyTo -u admin -p "$GUEST_PASS" \
  /path/on/host/nginx.conf /etc/nginx/nginx.conf

vmcli "$VMX" Guest copyFrom -u admin -p "$GUEST_PASS" \
  /var/log/syslog "$(dirname "$VMX")/guest-syslog.txt"

vmcli "$VMX" Guest ps -u admin -p "$GUEST_PASS"

vmcli "$VMX" Guest run -u admin -p "$GUEST_PASS" -nw \
  /usr/bin/python3 /opt/scripts/bootstrap.py

Demos

Two short screen recordings from a local Fusion Ubuntu guest (paths match a *.vmwarevm bundle on macOS). Keep "$VMCLI" quoted so VMware Fusion.app paths don't split at the space.

Snapshots, Tools, Power and Guest Query

Snapshots, Tools, Power and Guest Query

Copy Host File to Guest, then List

opy Host File to Guest, then List


Treat vmcli as a thin wrapper over desktop hypervisor operations: set VMX once per script or session, call one module at a time, and lean on --help when a flag changes between Fusion and Workstation builds.

!!! tip "Quick Reference" Run vmcli <module> --help for module-level syntax, and vmcli <vmx> <module> <sub-command> --help for a specific operation.

Disclaimer

This is not an official VMware by Broadcom document. This is a personal blog post.

The information is provided as-is with no warranties and confers no rights.

Please, refer to official documentation for the most up-to-date information.

Requests vs. HTTPX: Choosing a Python HTTP Client

requests earned its reputation honestly. For a long time, if you needed to call an API, download a file, or glue two services together in Python, requests was usually the obvious answer.

Async web frameworks, high-concurrency services, and I/O-heavy workloads changed what many teams need from an HTTP client. That's where httpx starts to matter.

This post compares requests and httpx from the point of view of a Python developer who already knows requests, wants a clear explanation of what httpx adds, and doesn't want hand-wavy "modernization" advice.

The Champion: requests

requests became the standard because it removed friction. Small API. Sensible defaults. Session when you need reuse.

  • The API is small and easy to learn.
  • The defaults are simple enough for scripts and internal tools.
  • Session gives you connection reuse and shared configuration without much ceremony.
  • The ecosystem around it is huge: tutorials, examples, blog posts, Stack Overflow answers, and third-party integrations all assume you know requests.

For synchronous Python code, it's still a good library. A lot of production code doesn't need async support, doesn't need HTTP/2, and doesn't need a broader transport model. In those cases, requests remains a sensible choice.

That's why this comparison is not really about replacing a bad tool. It's about recognizing where the older tool stops fitting as cleanly.

The Challenger: httpx

httpx looks familiar on purpose. The synchronous API feels close enough to requests that most Python developers can read it immediately. That compatibility is one of its best features, because it lowers the cost of trying it.

Its two biggest advantages are:

  • Native async and await support through httpx.AsyncClient
  • Optional HTTP/2 support on both sync and async clients

Those aren't cosmetic differences.

Async support means httpx fits naturally into frameworks like FastAPI, Starlette, and any other async application that should not block the event loop on outbound network calls. HTTP/2 support means one client connection can handle multiplexed requests when the remote server supports it, which can matter for high-concurrency workloads.

You opt in with httpx.Client(http2=True) or httpx.AsyncClient(http2=True), and the server still has to support HTTP/2 for you to get it.

httpx also pushes users toward safer networking defaults. The biggest example is timeouts: requests doesn't time out unless you ask it to, while httpx enforces timeouts by default.

Decoding the Major and Minor Pentatonic Scales

If you've ever felt trapped inside the classic minor pentatonic box, you're not alone. The good news is that the box isn't the problem. The real breakthrough comes when you understand what the notes inside that shape are doing, and that's the moment the fretboard starts making sense.

Most intermediate players start in the same place: Minor Pentatonic Position 1, fingers parked at the 5th fret, trying to find fresh ideas out of the same old lick. While that scale shape is useful, but it can start to feel a bit clostrophobic.

My goal with this post is to help you:

  • Stop seeing pentatonic scales as random patterns.
  • Start seeing them as intervals, Root Notes, and musical colors.
  • Learn why major and minor pentatonic scales are deeply connected.
  • Use that connection to make better phrases anywhere on the neck of the guitar.

Tip

You don't need to memorize five more disconnected shapes to improve. You need to understand what your current shape is already showing you.

The word pentatonic just means five notes.

That's the whole idea:

  • A major scale has 7 notes.
  • A natural minor scale has 7 notes.
  • A pentatonic scale trims that down to 5.

Why remove two notes?

Because the notes that are left out are the ones that create the strongest half-step tension. Whilst half-steps aren't bad, they're the notes most likely to sound tense, crunchy, or like they want immediate resolution. When you remove them, the scale becomes smoother and more forgiving.

That's why pentatonic scales are awesome:

  • They're easy to hear.
  • They're easy to phrase.
  • They sit well over a range of chords.
  • They quickly sound musical, even before your theory knowledge is deep.

On the guitar, the pentatonic scales work so well because they remove some of the strongest tension notes from the full scale. You can still create emotion and movement, but you're less likely to land on a note that feels harsh or unresolved by accident.

Why Pentatonics Feel So Friendly

By removing some of the most tension-heavy notes from the full 7-note scale, pentatonics give you a leaner set of notes that is easier to phrase with confidence. That's why they're often the first scale family guitar players truly learn to hear.

Hay for the Heifers

Reflection

Farm work looks noble from a distance, viewed through the softening lens of years gone by.

But it was just sweat, salt, and the inescapable gravity of a bloodline leading ahead of us on a rusted tractor seat. It was the kind of grit that settled into our joints and became part of our posture, a permanent inheritance.

It was an education in endurance, watching our Grandad’s back silhouette against the suffocating heat haze while my brother and I moved in a wordless, desperate rhythm below, reading each other's every breath because there wasn't room for anything else.

We learned early on that love between us wasn’t spoken. It was thrown, caught, stacked, and suffered through together. Yet, there was a heavy, almost sacred grace found at the barn at dusk, when the Massey finally choked out, the dust began to sink, and the pine shadows stretched long enough to let us finally clear our lungs.

This song is a reminder of those long afternoons left behind, side-by-side.

Lyrics

HAY FOR THE HEIFERS

The sun’s a red eye peeking o’er the Georgia pines
Grandad’s on the seat, keeping everything in line
That Massey belching out the blackest of smoke
If this baler misses ties, boys, it ain't no joke
The peanut vines are cured, they’re gold and dry
We’re kicking up a grit-cloud reaching for the sky

     Baling square bales in the heat and the haze
     Chasing down the harvest in a dusty-red daze
     Stack ‘em on the trailer, keep the corners tight
     Building up a mountain ‘neath the afternoon sky
     It’s hay for the heifers, it’s gold for the herd
     Sweating ‘til we’re finished, Lord, you have my word

That trailer’s getting long and it’s sagging in the middle
The dry vines are humming like a low-tuned fiddle
I’m standing on the edge, catching the bails on the fly
Dust in my throat and a sting in my eye
Grandad shifts a gear, we feel a jerk and a sway
We’re heavy-loaded, headed for the barn today

Back at the barn, the shadows stretching long and lean
The sweetest smelling hay-house that you’ve ever did seen
Back that Massey up, let the iron engine roar
We’ll stack ‘em to the rafters, all the way from the floor
The cows are in the lot and they’re bellerin’ low
They know that candy’s got the winter-time glow

     Repeat Chorus

Yeah, stack ‘em high
Keep 'em square
Oh, that hay is everywhere
It’s hay for the heifers, gold for the herd

Writer: J. Ryan Johnson (BMI)
Copyright: © 2026 J. Ryan Johnson. All rights reserved.
Phone: +1 (407) 902-5419
Email: hello [at] tenthirtyam [dot] org

Audio Disclaimer

Lyrics: Original | Audio: AI-Generated

I am a songwriter and a musician, but I am not the voice meant to inhabit these verses.

I've used AI to bridge the gap for the concept demos, crafted to serve as blueprints that capture the genre, tone, and weary soul I hear for each song.

They exist as an invitation, offered in the hope that these lyrics will eventually reach the hands of an artist and storyteller who can bring them fully into the light.

Until then, they remain as they were born: quiet reflections on the grit and grace found just north of the county line.

Nginx: A Practical Deep Dive

Nginx ends up in front of a surprising amount of web traffic. You might not think much about it until you have to troubleshoot a redirect loop, a broken certificate renewal, or an application that only fails once it sits behind a proxy.

Static sites, application servers, APIs, internal tools, container platforms, and CDN origins often have Nginx somewhere out front. People keep using it because it's fast, it's predictable, and it makes you declare what the server should do.

This guide walks through the part that matters in practice: what Nginx is good at, how its config model works, how to install it, how to serve a site, how to terminate TLS, how to replace .htaccess style behavior the Nginx way, and how to use it as a reverse proxy and a basic load balancer.

Nginx is a web server, reverse proxy, and load balancer. In a modern stack it usually does one or more of these jobs:

  • Serves static files directly.
  • Terminates HTTPS connections.
  • Redirects HTTP to HTTPS.
  • Proxies requests to application servers.
  • Balances traffic across multiple backend services.
  • Adds request and response headers.
  • Buffers slow clients away from backend applications.
  • Enforces simple access control rules.
  • Caches upstream responses when configured to do so.

It's especially good at static assets, reverse proxying, and a lot of concurrent connections. It also works well as the boring edge layer in front of applications written in Node.js, Python, Go, Ruby, PHP, Java, or anything else that can listen on a port.

The simplest mental model:

graph TD
    A[Client Browser] -->|HTTPS Request| B[Nginx]
    B -->|HTTP Request to Local App| C[Backend Application]

Nginx does not need to understand your application. It just needs to accept the request, apply the rules you gave it, and either serve the response or pass the request upstream.

Nginx became popular because it handles several common infrastructure problems without much drama:

  • It handles many concurrent connections efficiently.
  • It serves static files quickly.
  • It's excellent as a reverse proxy.
  • It's good at shielding backend apps from awkward client behavior.
  • It centralizes TLS, redirects, headers, and access control.
  • It can load balance traffic without a separate appliance.
  • Its configuration is explicit and reviewable.

Nginx expects configuration in known files, not hidden per-directory overrides inside the document root. That makes behavior easier to inspect, version, test, and deploy.

VMware Workstation and Fusion 26H1 Release

VMware Workstation Pro and Fusion Pro 26H1 shipped recently and was a fairly quiet release.

The biggest change in Workstation Pro 26H1 is the move to a fully 64-bit architecture on Windows. Operating systems and hardware moved on from 32-bit limitations a long time ago, but parts of Workstation's underlying components were still dragging legacy dependencies forward.

The 26H1 release finally drops them. Installers, services, libraries, and application binaries now operate entirely in a 64-bit environment. It's a cleanup that eliminates years of technical debt and gets out of the way of heavy edge-case workloads on the Windows side.

In 26H1, you can finally see when a virtual machine was created and when it was last powered on.

If you manage a handful of virtual machines, you probably won't care. If you manage dozens, or maintain a sprawling cybersecurity lab, this is a massive quality-of-life fix. It's significantly easier to find dormant lab machines without having to boot them or guess based on filesystem modification dates.

The most impactful part of 26H1 isn't technical. Since Workstation Pro and Fusion Pro are now free for personal, educational, and commercial use, the economics of local virtualization are completely different. You don't have to justify a license request or fall back to an open-source alternative with fewer features. You can just download the premier desktop hypervisor and run your lab.

The 26H1 release includes a typical collection of bug fixes and security updates to improve platform stability. When you rely on virtual machines as part of your daily workflow, predictable execution matters more than new knobs to turn.

Disclaimer

This is not an official VMware by Broadcom document. This is a personal blog post.

The information is provided as-is with no warranties and confers no rights.

Please, refer to official documentation for the most up-to-date information.

How I'm Preparing for GH-600, GitHub's Agentic AI Developer Certification

GitHub Certification

When I published Branching Out: GitHub Certification Path, GitHub had five certifications. That list is already out of date, and so is my own exam roadmap.

GitHub has now added a sixth exam: GH-600, GitHub Certified: Agentic AI Developer (beta). As of June 6, 2026, this one is still in beta, and it fills a different role than the existing Copilot certification. The Copilot exam is about using GitHub's AI tooling well. GH-600 is about building, operating, constraining, and evaluating agents inside real software delivery workflows.

I'm treating this post as my preparation plan for GH-600. These are the official materials, docs, and hands-on exercises I'm using to get ready for it.

GitHub is treating agentic AI as an engineering discipline: tool access, MCP configuration, execution boundaries, memory, observability, evaluation, and guardrails. That's the right frame for the technology, and it's the reason this exam is worth taking seriously.