mirror of
https://github.com/trycua/lume.git
synced 2026-02-15 02:39:32 -06:00
fix(lume): disable clipboard sync by default, add --clipboard opt-in
Clipboard sync is experimental and creates noisy SSH error logs when the VM's SSH is unavailable. Disable it by default and add a --clipboard flag to opt in. - Add --clipboard flag to `lume run` (default: off) - Add `clipboard` field to the HTTP API RunVMRequest - Add `clipboard` param to the MCP server run handler - Thread the flag through LumeController → VM.run - Update API docs Closes #1054
This commit is contained in:
@@ -56,6 +56,9 @@ struct Run: AsyncParsableCommand {
|
||||
help: "Optional network override: 'nat', 'bridged', or 'bridged:<interface>' (e.g. 'bridged:en0'). Defaults to the VM's configured mode.")
|
||||
var network: String?
|
||||
|
||||
@Flag(name: .customLong("clipboard"), help: "Enable bidirectional clipboard sync with the VM via SSH (experimental)")
|
||||
var clipboard: Bool = false
|
||||
|
||||
private var parsedNetworkMode: NetworkMode? {
|
||||
get throws {
|
||||
guard let network else {
|
||||
@@ -133,7 +136,8 @@ struct Run: AsyncParsableCommand {
|
||||
recoveryMode: recoveryMode,
|
||||
storage: storage,
|
||||
usbMassStoragePaths: parsedUSBStorageDevices.isEmpty ? nil : parsedUSBStorageDevices,
|
||||
networkMode: parsedNetworkMode
|
||||
networkMode: parsedNetworkMode,
|
||||
clipboard: clipboard
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -900,7 +900,8 @@ final class LumeController {
|
||||
recoveryMode: Bool = false,
|
||||
storage: String? = nil,
|
||||
usbMassStoragePaths: [Path]? = nil,
|
||||
networkMode: NetworkMode? = nil
|
||||
networkMode: NetworkMode? = nil,
|
||||
clipboard: Bool = false
|
||||
) async throws {
|
||||
let normalizedName = normalizeVMName(name: name)
|
||||
Logger.info(
|
||||
@@ -998,7 +999,8 @@ final class LumeController {
|
||||
vncPort: vncPort,
|
||||
recoveryMode: recoveryMode,
|
||||
usbMassStoragePaths: usbMassStoragePaths,
|
||||
networkMode: networkMode)
|
||||
networkMode: networkMode,
|
||||
clipboard: clipboard)
|
||||
Logger.info("VM started successfully", metadata: ["name": normalizedName])
|
||||
} catch {
|
||||
SharedVM.shared.removeVM(name: normalizedName)
|
||||
|
||||
@@ -306,7 +306,8 @@ enum APIDocExtractor {
|
||||
APIFieldDoc(name: "noDisplay", type: "boolean", required: false, description: "Run without VNC display", defaultValue: "false"),
|
||||
APIFieldDoc(name: "sharedDirectories", type: "array", required: false, description: "Directories to share with the VM", defaultValue: nil),
|
||||
APIFieldDoc(name: "recoveryMode", type: "boolean", required: false, description: "Boot macOS VM in recovery mode", defaultValue: "false"),
|
||||
APIFieldDoc(name: "storage", type: "string", required: false, description: "VM storage location", defaultValue: nil)
|
||||
APIFieldDoc(name: "storage", type: "string", required: false, description: "VM storage location", defaultValue: nil),
|
||||
APIFieldDoc(name: "clipboard", type: "boolean", required: false, description: "Enable bidirectional clipboard sync via SSH (experimental)", defaultValue: "false")
|
||||
]
|
||||
),
|
||||
responseBody: APIResponseDoc(
|
||||
|
||||
@@ -340,7 +340,7 @@ extension Server {
|
||||
body.flatMap { try? JSONDecoder().decode(RunVMRequest.self, from: $0) }
|
||||
?? RunVMRequest(
|
||||
noDisplay: nil, sharedDirectories: nil, recoveryMode: nil, storage: nil,
|
||||
network: nil)
|
||||
network: nil, clipboard: nil)
|
||||
|
||||
// Record telemetry
|
||||
TelemetryClient.shared.record(event: TelemetryEvent.apiVMRun, properties: [
|
||||
@@ -372,7 +372,8 @@ extension Server {
|
||||
sharedDirectories: dirs,
|
||||
recoveryMode: request.recoveryMode ?? false,
|
||||
storage: request.storage,
|
||||
networkMode: networkMode
|
||||
networkMode: networkMode,
|
||||
clipboard: request.clipboard ?? false
|
||||
)
|
||||
Logger.info("VM start initiated in background", metadata: ["name": name])
|
||||
|
||||
@@ -825,7 +826,8 @@ extension Server {
|
||||
sharedDirectories: [SharedDirectory] = [],
|
||||
recoveryMode: Bool = false,
|
||||
storage: String? = nil,
|
||||
networkMode: NetworkMode? = nil
|
||||
networkMode: NetworkMode? = nil,
|
||||
clipboard: Bool = false
|
||||
) {
|
||||
Logger.info(
|
||||
"Starting VM in detached task",
|
||||
@@ -855,7 +857,8 @@ extension Server {
|
||||
sharedDirectories: sharedDirectories,
|
||||
recoveryMode: recoveryMode,
|
||||
storage: storage,
|
||||
networkMode: networkMode
|
||||
networkMode: networkMode,
|
||||
clipboard: clipboard
|
||||
)
|
||||
Logger.info("VM started successfully in background task", metadata: ["name": name])
|
||||
} catch {
|
||||
|
||||
@@ -565,6 +565,7 @@ final class LumeMCPServer {
|
||||
|
||||
let storage = args?["storage"]?.stringValue
|
||||
let noDisplay = args?["no_display"]?.boolValue ?? true
|
||||
let clipboard = args?["clipboard"]?.boolValue ?? false
|
||||
|
||||
var sharedDirectories: [SharedDirectory] = []
|
||||
if let sharedDir = args?["shared_dir"]?.stringValue {
|
||||
@@ -581,7 +582,8 @@ final class LumeMCPServer {
|
||||
name: name,
|
||||
noDisplay: noDisplay,
|
||||
sharedDirectories: sharedDirectories,
|
||||
storage: storage
|
||||
storage: storage,
|
||||
clipboard: clipboard
|
||||
)
|
||||
} catch {
|
||||
Logger.error(
|
||||
|
||||
@@ -17,6 +17,7 @@ struct RunVMRequest: Codable {
|
||||
let recoveryMode: Bool?
|
||||
let storage: String?
|
||||
let network: String?
|
||||
let clipboard: Bool?
|
||||
|
||||
struct SharedDirectoryRequest: Codable {
|
||||
let hostPath: String
|
||||
|
||||
@@ -136,7 +136,7 @@ class VM {
|
||||
func run(
|
||||
noDisplay: Bool, sharedDirectories: [SharedDirectory], mount: Path?, vncPort: Int = 0,
|
||||
recoveryMode: Bool = false, usbMassStoragePaths: [Path]? = nil,
|
||||
networkMode: NetworkMode? = nil
|
||||
networkMode: NetworkMode? = nil, clipboard: Bool = false
|
||||
) async throws {
|
||||
Logger.info(
|
||||
"VM.run method called",
|
||||
@@ -261,8 +261,10 @@ class VM {
|
||||
|
||||
// Start clipboard watcher for automatic host-to-VM clipboard sync
|
||||
// Requires SSH/Remote Login to be enabled on the VM
|
||||
clipboardWatcher = ClipboardWatcher(vmName: vmDirContext.name, storage: vmDirContext.storage)
|
||||
await clipboardWatcher?.start()
|
||||
if clipboard {
|
||||
clipboardWatcher = ClipboardWatcher(vmName: vmDirContext.name, storage: vmDirContext.storage)
|
||||
await clipboardWatcher?.start()
|
||||
}
|
||||
|
||||
while true {
|
||||
try await Task.sleep(nanoseconds: UInt64(1e9))
|
||||
|
||||
Reference in New Issue
Block a user