External macOS worker for Tangled Spindle. nnabeyang.tngl.sh/spindle-agent
6.2 kB
Markdown
at main

Operating the managed macOS stack #

Run Swift Spindle, native and virtual-machine workers, and Cloudflare Tunnel as one managed stack owned by a dedicated non-administrator macOS account.

Overview #

The supported stack has four LaunchAgents:

  • sh.tangled.swift-spindle
  • sh.tangled.spindle-agent.native
  • sh.tangled.spindle-agent.vm
  • com.cloudflare.cloudflared.spindle

deploy/macos/install.sh copies swift-spindle and spa into one digest-addressed immutable release and atomically updates ~/Library/Application Support/spindle-stack/current. The server and workers therefore move forward and backward as one matched release.

The installer does not delete historical server data or releases. Files from an older deployment may remain for forensic recovery, but they are not loaded or accepted by the supported rollback command.

Prerequisites #

Use the same logged-in account that owns the worker's GUI and Keychain access. Install:

  • Swift and the pinned Xcode versions required by configured profiles
  • cloudflared
  • a source-built swift-spindle
  • spa, from source, Homebrew, or a release archive
  • a validated macOS VM base when the VM profile is enabled

Run the preflight without exposing credentials:

SPI_HOSTNAME=spindle.example.org \
SPI_DNS_ZONE=example.org \
./deploy/macos/preflight.sh

An already-running service on port 6555 is reported as information because an update is expected to replace it during the controlled restart.

Private configuration #

Create these mode 0600 files under ~/Library/Application Support/spindle-stack/config:

  • swift-spindle.json
  • spa-native.json
  • spa-vm.json
  • the worker credential file and token files referenced by those configs
  • cloudflare-token

Start from the examples in deploy/macos. The Swift Spindle configuration is the source of truth for the public hostname, owner DID, listen address, data directory, worker gateway, and executor profiles. The two spa configurations must use worker IDs macos-native and macos-vm.

Keep the Swift Spindle data directory outside every release, normally:

~/Library/Application Support/spindle-stack/state/swift-spindle

Configuration, tokens, state, and logs are never copied into an immutable release.

Install or update #

Build both executables from a clean checkout:

swift build -c release --product swift-spindle
swift build -c release --product spa
bin="$(swift build -c release --show-bin-path)"

SPI_SWIFT_SPINDLE_EXECUTABLE="$bin/swift-spindle" \
SPI_SPA_EXECUTABLE="$bin/spa" \
./deploy/macos/install.sh

The installer:

  1. validates the Swift Spindle and worker configurations;
  2. copies and signs both executables into one release;
  3. writes private LaunchAgent property lists;
  4. atomically switches current;
  5. waits for the previous Swift Spindle process to exit;
  6. starts Swift Spindle, workers, and Tunnel;
  7. requires an HTTP 200 or 201 owner response.

When the generated Cloudflare Tunnel property list is unchanged, the installer preserves an already loaded Tunnel without replacing its property list or restarting its process. An unloaded Tunnel is started from that same property list. A changed Tunnel property list requires a restart, so run that update from a local session, screen sharing, or another connection that does not depend on the managed Tunnel.

If startup or the owner probe fails, the previous current target and Swift Spindle property list are restored and the previous workers are restarted. The failed release remains available for diagnosis.

On the first Swift-only installation, an inactive sh.tangled.spindle-server.plist is moved under the private retired directory. No supported service or rollback path loads it.

Inspect status #

./deploy/macos/status.sh

Status reports the four supported services, the local and public owner endpoints, managed workspaces, and booted Simulators. It obtains the listen address, public instance, and execution mode from the redacted swift-spindle check report.

Inspect service logs under ~/Library/Logs/spindle-stack. Never publish configuration, token files, complete environment dumps, or unredacted job output while diagnosing a public service.

Back up and restore #

Back up Swift Spindle's configured data directory to an absolute private destination:

./deploy/macos/backup.sh /absolute/private/backup-root

The command briefly stops Swift Spindle, copies only its persistent data plus the active release revisions, restores the service, and prints the new backup directory. Worker tokens and configuration are deliberately excluded.

Restore is explicit and replaces the configured data directory:

./deploy/macos/restore.sh --yes /absolute/private/backup-directory

Restore preserves the previous data directory until Swift Spindle restarts and the owner endpoint succeeds. A failed probe automatically restores the previous data.

Roll back executables #

List release directory names after inspecting the active current link:

readlink "$HOME/Library/Application Support/spindle-stack/current"
ls "$HOME/Library/Application Support/spindle-stack/releases"

Select a release containing both bin/swift-spindle and bin/spa, then:

./deploy/macos/rollback.sh 0123456789ab-fedcba987654

Rollback validates the selected Swift Spindle and spa configurations, switches both executables together, restarts the applicable workers, and requires the owner endpoint to succeed. It restores the previously active release if that probe fails. A server-only or spa-only historical release is rejected.

VM bundles are not release contents. Their guest protocol version and selected OS, Swift, Xcode, and tool capabilities remain the compatibility boundary. The validated spa version is retained as provenance, but upgrading spa does not require reprovisioning an otherwise compatible version 1 VM.

Uninstall services #

./deploy/macos/uninstall.sh

Uninstall stops and removes the supported LaunchAgents. It retains state, configuration, tokens, releases, logs, backups, and retired files so removal does not destroy operator data.