How framna-dk/tartelet works
1. What this repo is
Tartelet is a macOS app that manages up to two ephemeral GitHub Actions runners inside Tart virtual machines. The core architectural loop is: clone VM, boot VM, SSH in, register GitHub runner, wait for job, deregister, shut down, delete VM, repeat.
2. Top-level app structure
The Tartelet app target is a thin composition root. AppDelegate.swift, TarteletApp.swift, and Composers.swift wire together the feature packages and platform services. Most behavior lives in Swift packages rather than in the app target itself.
3. Layering pattern across packages
Most subsystems follow a consistent split: *Domain defines protocols, models, and orchestration logic; *Data provides concrete implementations; *UI exists where user-facing configuration is needed. This keeps core behavior testable and lets the app swap implementations at the boundary.
4. Virtual machine orchestration is the centerpiece
Packages/VirtualMachine owns the lifecycle of runner machines. Its domain types (VirtualMachine, VirtualMachineFleet, VirtualMachineState, VirtualMachineEditor) model the fleet and its state transitions. The data layer provides Tart-backed implementations such as TartVirtualMachine, Tart, TartLocator, and TartHomeProvider.
5. How a VM becomes a runner
The VM flow is coordinated through SSH-related abstractions in VirtualMachineDomain/SSH plus runner-specific handlers in GitHubActionsRunner. After boot, the app waits for network/SSH readiness, then runs post-boot setup and GitHub runner setup inside the VM. The handlers are composed so the VM can run generic setup scripts and the GitHub Actions registration flow in sequence.
6. SSH is the execution bridge into the VM
Packages/SSH wraps remote command execution behind SSHClient and SSHConnection abstractions. Packages/VirtualMachine builds on that with VirtualMachineSSHClient, SSHConnectionWaiter, RetryingVirtualMachineIPAddressReader, and TaskSSHConnectionWaiter to handle boot-time uncertainty and delayed availability of the guest.
7. GitHub integration is isolated behind its own package
Packages/GitHub owns authentication and runner registration with GitHub. The domain layer defines GitHubClient, GitHubCredentialsStore, GitHubJWTTokenFactory, GitHubAppAccessToken, and GitHubRunnerRegistrationToken. The data layer uses NetworkingGitHubClient and KeychainGitHubCredentialsStore to talk to GitHub’s API and persist credentials securely.
8. Secrets and key material live in the keychain
Runner registration depends on private keys and GitHub app credentials, so those are stored via Packages/Keychain. The package provides low-level key/password queries plus RSAPrivateKey handling. This is a hard boundary: sensitive GitHub app material is not treated as ordinary app settings.
9. Settings are both persisted and user-configurable
Packages/Settings contains the app’s settings model, storage, and SwiftUI screens. SettingsDomain defines the configuration state, virtual machine selection, GitHub runner settings, and application UI mode; SettingsData persists settings via AppStorageSettingsStore; SettingsUI provides the actual settings scenes and pickers.
10. Menu bar is the primary user surface
Packages/MenuBar supplies the status/menu bar UI. It contains menu bar items and content that likely reflect the current fleet state and expose quick actions. This package is separate from settings, so ongoing fleet control and configuration are decoupled.
11. Logging and diagnostics are file-based
Packages/Logging defines a generic logging interface and a file exporter. FileLogger and FileLogExporter write logs to disk using LogsDirectory and a date provider. This is useful for diagnosing VM boot, SSH, and GitHub registration failures outside the UI.
12. Supporting infrastructure packages
Packages/Shell abstracts local process execution, which is likely used for calling Tart and other command-line tools. Packages/FileSystem abstracts disk access and coordination. Packages/Networking wraps URLSession and response handling for HTTP interactions, primarily serving the GitHub client.
13. Build and repo tooling
The project is organized as an XcodeGen-generated macOS app with multiple Swift packages. SwiftGen generates resource and localization constants, and SwiftLint enforces style in CI. These tools shape the repo, but they do not materially change the runtime architecture.
14. Key architectural boundaries to show in a diagram
The important diagram boundaries are: UI/composition root in the app target; settings and menu bar presentation; VM orchestration; SSH execution into guests; GitHub API/auth; local persistence in keychain and file system; and external dependencies on Tart, GitHub Actions, and the host macOS runtime.
Main entry points: Composers.swift Composers · VirtualMachineFleet.swift VM domain · GitHubActionsRunnerSSHConnectionHandler.swift Runner setup · TartVirtualMachine.swift Tart backend
Components 19
App target
macOS composition root and app wiring.
- Tartelet app · Thin macOS app target that wires the packages together.
Tartelet/Sources/TarteletApp.swift - Composers · Assembles feature packages and platform services.
Tartelet/Sources/Composers.swift - App delegate · Hosts application lifecycle hooks for the app target.
Tartelet/Sources/AppDelegate.swift
User-facing
Settings and menu bar presentation layers.
- Settings scene · Top-level settings entry point and scenes.
Packages/Settings/Sources/SettingsUI/SettingsScene.swift - Settings domain · Configuration state, VM selection, and app UI mode.
Packages/Settings/Sources/SettingsDomain/ConfigurationState.swift - Settings store · Persists app settings through the app storage-backed store.
Packages/Settings/Sources/SettingsData/AppStorageSettingsStore.swift - Menu bar · Menu bar items and content reflecting fleet state and actions.
Packages/MenuBar/Sources/MenuBar/MenuBarItem.swift
Virtual machine
Fleet lifecycle, SSH bootstrapping, and runner setup.
- VM domain · Fleet model, state machine, editor, and runner coordination contracts.
Packages/VirtualMachine/Sources/VirtualMachineDomain/VirtualMachineFleet.swift - VM SSH layer · SSH-based VM access, waiters, credential store, and connection handlers.
Packages/VirtualMachine/Sources/VirtualMachineDomain/SSH/VirtualMachineSSHClient.swift - Runner setup · Composes post-boot scripting with GitHub Actions runner registration.
Packages/VirtualMachine/Sources/VirtualMachineDomain/GitHubActionsRunner/GitHubActionsRunnerSSHConnectionHandler.swift - Tart backend · Concrete Tart-backed VM implementation and boot/runtime helpers.
Packages/VirtualMachine/Sources/VirtualMachineData/TartVirtualMachine.swift
Integrations
External service and local infrastructure boundaries.
- SSH package · Generic SSH client and connection abstractions.
Packages/SSH/Sources/SSHDomain/SSHClient.swift - GitHub domain · Tokens, credentials store, client, and registration abstractions.
Packages/GitHub/Sources/GitHubDomain/GitHubClient.swift - GitHub client · Networking-backed GitHub API client and keychain credential storage.
Packages/GitHub/Sources/GitHubData/NetworkingGitHubClient.swift - Keychain · Private key and password storage for sensitive app material.
Packages/Keychain/Sources/Keychain/Keychain.swift - Networking · URLSession-based HTTP service and response handling.
Packages/Networking/Sources/NetworkingDomain/NetworkingService.swift - Logging · File-based logger and log exporter for runtime diagnostics.
Packages/Logging/Sources/LoggingDomain/Logger.swift - Shell · Local process execution abstraction used for host tools.
Packages/Shell/Sources/ShellDomain/Shell.swift - File system · Abstracts disk access and file coordination.
Packages/FileSystem/Sources/FileSystemDomain/FileSystem.swift
Connections 23
- Tartelet app → Composers: wires · Composition root delegates dependency assembly.
- App delegate → Tartelet app: drives · App lifecycle activates the composed application.
- Composers → Settings scene: instantiates · Builds the settings UI entry points.
- Composers → Menu bar: instantiates · Builds the primary status/menu bar surface.
- Composers → VM domain: connects · Supplies VM orchestration services to the app.
- Composers → Logging: connects · Injects logging infrastructure.
- Composers → Keychain: connects · Injects secret storage.
- Settings scene → Settings domain: uses · Settings screens bind to configuration state.
- Settings domain → Settings store: persists via · Configuration is stored through the settings store.
- Menu bar → VM domain: reflects · Menu bar mirrors fleet state and quick actions.
- VM domain → Tart backend: implements via · Fleet lifecycle is backed by Tart VM operations.
- VM domain → VM SSH layer: boots via · VM state transitions depend on SSH readiness and connection handling.
- VM domain → Runner setup: registers · After boot, runner setup is composed into the VM flow.
- VM SSH layer → SSH package: builds on · Virtual machine SSH abstractions sit on the generic SSH package.
- Runner setup → GitHub domain: calls · Runner registration uses GitHub auth and token abstractions.
- Runner setup → Shell: executes · Post-boot and setup work can invoke local shell commands.
- Runner setup → Logging: reports · Boot and registration issues are logged to file.
- GitHub domain → GitHub client: implemented by · Domain contracts are backed by networking and keychain implementations.
- GitHub client → Networking: uses · GitHub API calls flow through the networking layer.
- GitHub client → Keychain: stores in · Credentials are persisted securely in the keychain.
- Logging → File system: writes via · Log export depends on file system access.
- Tart backend → Shell: invokes · Tart and host tooling are executed through shell abstractions.
- Tart backend → File system: reads/writes · VM helpers depend on local file system access.