pragma Singleton // SSH keys, the agent holding them, and the hosts this machine has met. // // Nothing here ever sees a private key or a passphrase. Adding an encrypted key // makes ssh-add prompt through the system's own askpass, which is where a // passphrase belongs -- a settings page collecting one and passing it along // would be a worse place for it to live. import Quickshell import Quickshell.Io import QtQuick Singleton { id: root readonly property string helperPath: Quickshell.shellDir + "/scripts/panama-ssh-keys" property bool available: false property string directory: "" property var agent: ({}) property var keys: [] property var hosts: [] property bool scanned: false property string lastError: "" readonly property bool busy: query.running || mutation.running readonly property int loadedCount: root.keys.filter(key => key.loaded === true).length // Keys readable by anyone but their owner. ssh refuses to use these, so a // page that stayed quiet about it would leave someone wondering why a key // that plainly exists is never offered. readonly property var overexposed: root.keys.filter(key => key.mode !== "" && key.mode !== "600" && key.mode !== "400") function refresh(): void { if (query.running) return; query.command = [root.helperPath, "snapshot"]; query.running = true; } function absorb(text: string): void { try { const parsed = JSON.parse(text); root.available = parsed.available === true; root.directory = String(parsed.directory ?? ""); root.agent = parsed.agent ?? ({}); root.keys = Array.isArray(parsed.keys) ? parsed.keys : []; root.hosts = Array.isArray(parsed.hosts) ? parsed.hosts : []; root.lastError = String(parsed.error ?? ""); } catch (error) { root.lastError = "Could not read the SSH configuration."; console.warn("SshKeys: could not parse helper output:", error); } root.scanned = true; } function run(arguments: var): void { if (mutation.running) return; root.lastError = ""; mutation.command = [root.helperPath].concat(arguments); mutation.running = true; } // Adding an encrypted key prompts, and the prompt is the system's, so this // is allowed a long time before it is considered stuck. function addToAgent(path: string): void { root.run(["agent-add", path]); } // The public half, onto the clipboard. Safe to copy by definition -- it is // the thing you paste into a server. The path arrives as $1 rather than // being spliced into shell source, so a name with a space or a quote in it // cannot become part of the command. function copyPublicKey(publicPath: string): void { if (publicPath === "" || copier.running) return; copier.command = ["sh", "-c", 'exec wl-copy < "$1"', "qs-ssh-keys", publicPath]; copier.running = true; } function forgetHost(host: string): void { root.run(["forget-host", host]); } Component.onCompleted: root.refresh() Process { id: copier } Process { id: query stdout: StdioCollector { onStreamFinished: root.absorb(this.text) } stderr: StdioCollector { onStreamFinished: if (this.text.trim() !== "") root.lastError = this.text.trim() } } Process { id: mutation stdout: StdioCollector { onStreamFinished: root.absorb(this.text) } stderr: StdioCollector { onStreamFinished: if (this.text.trim() !== "") root.lastError = this.text.trim() } } }