# framna-dk/tartelet architecture

> Architecture diagram of the GitHub repository framna-dk/tartelet, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-06-24). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/framna-dk/tartelet
- Repository: https://github.com/framna-dk/tartelet

## Overview

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: [`Tartelet/Sources/Composers.swift`](https://github.com/framna-dk/tartelet/blob/main/Tartelet/Sources/Composers.swift) (Composers), [`Packages/VirtualMachine/Sources/VirtualMachineDomain/VirtualMachineFleet.swift`](https://github.com/framna-dk/tartelet/blob/main/Packages/VirtualMachine/Sources/VirtualMachineDomain/VirtualMachineFleet.swift) (VM domain), [`Packages/VirtualMachine/Sources/VirtualMachineDomain/GitHubActionsRunner/GitHubActionsRunnerSSHConnectionHandler.swift`](https://github.com/framna-dk/tartelet/blob/main/Packages/VirtualMachine/Sources/VirtualMachineDomain/GitHubActionsRunner/GitHubActionsRunnerSSHConnectionHandler.swift) (Runner setup), [`Packages/VirtualMachine/Sources/VirtualMachineData/TartVirtualMachine.swift`](https://github.com/framna-dk/tartelet/blob/main/Packages/VirtualMachine/Sources/VirtualMachineData/TartVirtualMachine.swift) (Tart backend)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_group_app["App target"]
  node_node_tartelet_app["Tartelet app<br/>composition root<br/>[TarteletApp.swift]"]
  node_node_composers["Composers<br/>wiring<br/>[Composers.swift]"]
  node_node_app_delegate["App delegate<br/>lifecycle<br/>[AppDelegate.swift]"]
end

subgraph group_group_presentation["User-facing"]
  node_node_settings_scene["Settings scene<br/>settings UI"]
  node_node_settings_domain["Settings domain<br/>settings model"]
  node_node_settings_data[("Settings store<br/>settings persistence")]
  node_node_menu_bar["Menu bar<br/>status UI<br/>[MenuBarItem.swift]"]
end

subgraph group_group_vm["Virtual machine"]
  node_node_vm_domain["VM domain<br/>fleet orchestration"]
  node_node_vm_ssh["VM SSH layer<br/>guest bridge"]
  node_node_vm_runner["Runner setup<br/>runner flow"]
  node_node_vm_data["Tart backend<br/>vm runtime"]
end

subgraph group_group_integrations["Integrations"]
  node_node_ssh_package["SSH package<br/>ssh abstraction<br/>[SSHClient.swift]"]
  node_node_github_domain["GitHub domain<br/>github auth<br/>[GitHubClient.swift]"]
  node_node_github_data["GitHub client<br/>github api"]
  node_node_keychain[("Keychain<br/>secret store<br/>[Keychain.swift]")]
  node_node_networking["Networking<br/>http layer"]
  node_node_logging["Logging<br/>file diagnostics<br/>[Logger.swift]"]
  node_node_shell["Shell<br/>process exec<br/>[Shell.swift]"]
  node_node_filesystem["File system<br/>disk I/O<br/>[FileSystem.swift]"]
end

node_node_tartelet_app -->|"wires"| node_node_composers
node_node_app_delegate -->|"drives"| node_node_tartelet_app
node_node_composers -->|"instantiates"| node_node_settings_scene
node_node_composers -->|"instantiates"| node_node_menu_bar
node_node_composers -->|"connects"| node_node_vm_domain
node_node_composers -->|"connects"| node_node_logging
node_node_composers -->|"connects"| node_node_keychain
node_node_settings_scene -->|"uses"| node_node_settings_domain
node_node_settings_domain -->|"persists via"| node_node_settings_data
node_node_menu_bar -->|"reflects"| node_node_vm_domain
node_node_vm_domain -->|"implements via"| node_node_vm_data
node_node_vm_domain -->|"boots via"| node_node_vm_ssh
node_node_vm_domain -->|"registers"| node_node_vm_runner
node_node_vm_ssh -->|"builds on"| node_node_ssh_package
node_node_vm_runner -->|"calls"| node_node_github_domain
node_node_vm_runner -->|"executes"| node_node_shell
node_node_vm_runner -->|"reports"| node_node_logging
node_node_github_domain -->|"implemented by"| node_node_github_data
node_node_github_data -->|"uses"| node_node_networking
node_node_github_data -->|"stores in"| node_node_keychain
node_node_logging -->|"writes via"| node_node_filesystem
node_node_vm_data -->|"invokes"| node_node_shell
node_node_vm_data -->|"reads/writes"| node_node_filesystem

click node_node_tartelet_app "https://github.com/framna-dk/tartelet/blob/main/Tartelet/Sources/TarteletApp.swift"
click node_node_composers "https://github.com/framna-dk/tartelet/blob/main/Tartelet/Sources/Composers.swift"
click node_node_app_delegate "https://github.com/framna-dk/tartelet/blob/main/Tartelet/Sources/AppDelegate.swift"
click node_node_settings_scene "https://github.com/framna-dk/tartelet/blob/main/Packages/Settings/Sources/SettingsUI/SettingsScene.swift"
click node_node_settings_domain "https://github.com/framna-dk/tartelet/blob/main/Packages/Settings/Sources/SettingsDomain/ConfigurationState.swift"
click node_node_settings_data "https://github.com/framna-dk/tartelet/blob/main/Packages/Settings/Sources/SettingsData/AppStorageSettingsStore.swift"
click node_node_menu_bar "https://github.com/framna-dk/tartelet/blob/main/Packages/MenuBar/Sources/MenuBar/MenuBarItem.swift"
click node_node_vm_domain "https://github.com/framna-dk/tartelet/blob/main/Packages/VirtualMachine/Sources/VirtualMachineDomain/VirtualMachineFleet.swift"
click node_node_vm_ssh "https://github.com/framna-dk/tartelet/blob/main/Packages/VirtualMachine/Sources/VirtualMachineDomain/SSH/VirtualMachineSSHClient.swift"
click node_node_vm_runner "https://github.com/framna-dk/tartelet/blob/main/Packages/VirtualMachine/Sources/VirtualMachineDomain/GitHubActionsRunner/GitHubActionsRunnerSSHConnectionHandler.swift"
click node_node_vm_data "https://github.com/framna-dk/tartelet/blob/main/Packages/VirtualMachine/Sources/VirtualMachineData/TartVirtualMachine.swift"
click node_node_ssh_package "https://github.com/framna-dk/tartelet/blob/main/Packages/SSH/Sources/SSHDomain/SSHClient.swift"
click node_node_github_domain "https://github.com/framna-dk/tartelet/blob/main/Packages/GitHub/Sources/GitHubDomain/GitHubClient.swift"
click node_node_github_data "https://github.com/framna-dk/tartelet/blob/main/Packages/GitHub/Sources/GitHubData/NetworkingGitHubClient.swift"
click node_node_keychain "https://github.com/framna-dk/tartelet/blob/main/Packages/Keychain/Sources/Keychain/Keychain.swift"
click node_node_networking "https://github.com/framna-dk/tartelet/blob/main/Packages/Networking/Sources/NetworkingDomain/NetworkingService.swift"
click node_node_logging "https://github.com/framna-dk/tartelet/blob/main/Packages/Logging/Sources/LoggingDomain/Logger.swift"
click node_node_shell "https://github.com/framna-dk/tartelet/blob/main/Packages/Shell/Sources/ShellDomain/Shell.swift"
click node_node_filesystem "https://github.com/framna-dk/tartelet/blob/main/Packages/FileSystem/Sources/FileSystemDomain/FileSystem.swift"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_node_tartelet_app,node_node_composers,node_node_app_delegate toneBlue
class node_node_settings_scene,node_node_settings_domain,node_node_settings_data,node_node_menu_bar toneAmber
class node_node_vm_domain,node_node_vm_ssh,node_node_vm_runner,node_node_vm_data toneMint
class node_node_ssh_package,node_node_github_domain,node_node_github_data,node_node_keychain,node_node_networking,node_node_logging,node_node_shell,node_node_filesystem toneRose
```

## Components

### App target

macOS composition root and app wiring.

- [Tartelet app](https://github.com/framna-dk/tartelet/blob/main/Tartelet/Sources/TarteletApp.swift): composition root · Thin macOS app target that wires the packages together. · `Tartelet/Sources/TarteletApp.swift`
- [Composers](https://github.com/framna-dk/tartelet/blob/main/Tartelet/Sources/Composers.swift): wiring · Assembles feature packages and platform services. · `Tartelet/Sources/Composers.swift`
- [App delegate](https://github.com/framna-dk/tartelet/blob/main/Tartelet/Sources/AppDelegate.swift): lifecycle · Hosts application lifecycle hooks for the app target. · `Tartelet/Sources/AppDelegate.swift`

### User-facing

Settings and menu bar presentation layers.

- [Settings scene](https://github.com/framna-dk/tartelet/blob/main/Packages/Settings/Sources/SettingsUI/SettingsScene.swift): settings UI · Top-level settings entry point and scenes. · `Packages/Settings/Sources/SettingsUI/SettingsScene.swift`
- [Settings domain](https://github.com/framna-dk/tartelet/blob/main/Packages/Settings/Sources/SettingsDomain/ConfigurationState.swift): settings model · Configuration state, VM selection, and app UI mode. · `Packages/Settings/Sources/SettingsDomain/ConfigurationState.swift`
- [Settings store](https://github.com/framna-dk/tartelet/blob/main/Packages/Settings/Sources/SettingsData/AppStorageSettingsStore.swift): settings persistence · Persists app settings through the app storage-backed store. · `Packages/Settings/Sources/SettingsData/AppStorageSettingsStore.swift`
- [Menu bar](https://github.com/framna-dk/tartelet/blob/main/Packages/MenuBar/Sources/MenuBar/MenuBarItem.swift): status UI · 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](https://github.com/framna-dk/tartelet/blob/main/Packages/VirtualMachine/Sources/VirtualMachineDomain/VirtualMachineFleet.swift): fleet orchestration · Fleet model, state machine, editor, and runner coordination contracts. · `Packages/VirtualMachine/Sources/VirtualMachineDomain/VirtualMachineFleet.swift`
- [VM SSH layer](https://github.com/framna-dk/tartelet/blob/main/Packages/VirtualMachine/Sources/VirtualMachineDomain/SSH/VirtualMachineSSHClient.swift): guest bridge · SSH-based VM access, waiters, credential store, and connection handlers. · `Packages/VirtualMachine/Sources/VirtualMachineDomain/SSH/VirtualMachineSSHClient.swift`
- [Runner setup](https://github.com/framna-dk/tartelet/blob/main/Packages/VirtualMachine/Sources/VirtualMachineDomain/GitHubActionsRunner/GitHubActionsRunnerSSHConnectionHandler.swift): runner flow · Composes post-boot scripting with GitHub Actions runner registration. · `Packages/VirtualMachine/Sources/VirtualMachineDomain/GitHubActionsRunner/GitHubActionsRunnerSSHConnectionHandler.swift`
- [Tart backend](https://github.com/framna-dk/tartelet/blob/main/Packages/VirtualMachine/Sources/VirtualMachineData/TartVirtualMachine.swift): vm runtime · Concrete Tart-backed VM implementation and boot/runtime helpers. · `Packages/VirtualMachine/Sources/VirtualMachineData/TartVirtualMachine.swift`

### Integrations

External service and local infrastructure boundaries.

- [SSH package](https://github.com/framna-dk/tartelet/blob/main/Packages/SSH/Sources/SSHDomain/SSHClient.swift): ssh abstraction · Generic SSH client and connection abstractions. · `Packages/SSH/Sources/SSHDomain/SSHClient.swift`
- [GitHub domain](https://github.com/framna-dk/tartelet/blob/main/Packages/GitHub/Sources/GitHubDomain/GitHubClient.swift): github auth · Tokens, credentials store, client, and registration abstractions. · `Packages/GitHub/Sources/GitHubDomain/GitHubClient.swift`
- [GitHub client](https://github.com/framna-dk/tartelet/blob/main/Packages/GitHub/Sources/GitHubData/NetworkingGitHubClient.swift): github api · Networking-backed GitHub API client and keychain credential storage. · `Packages/GitHub/Sources/GitHubData/NetworkingGitHubClient.swift`
- [Keychain](https://github.com/framna-dk/tartelet/blob/main/Packages/Keychain/Sources/Keychain/Keychain.swift): secret store · Private key and password storage for sensitive app material. · `Packages/Keychain/Sources/Keychain/Keychain.swift`
- [Networking](https://github.com/framna-dk/tartelet/blob/main/Packages/Networking/Sources/NetworkingDomain/NetworkingService.swift): http layer · URLSession-based HTTP service and response handling. · `Packages/Networking/Sources/NetworkingDomain/NetworkingService.swift`
- [Logging](https://github.com/framna-dk/tartelet/blob/main/Packages/Logging/Sources/LoggingDomain/Logger.swift): file diagnostics · File-based logger and log exporter for runtime diagnostics. · `Packages/Logging/Sources/LoggingDomain/Logger.swift`
- [Shell](https://github.com/framna-dk/tartelet/blob/main/Packages/Shell/Sources/ShellDomain/Shell.swift): process exec · Local process execution abstraction used for host tools. · `Packages/Shell/Sources/ShellDomain/Shell.swift`
- [File system](https://github.com/framna-dk/tartelet/blob/main/Packages/FileSystem/Sources/FileSystemDomain/FileSystem.swift): disk I/O · Abstracts disk access and file coordination. · `Packages/FileSystem/Sources/FileSystemDomain/FileSystem.swift`

## Connections

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