mirror of
https://github.com/dredx/prole.git
synced 2026-09-27 16:14:31 +00:00
- Replaced all IRCKit (0.16) usage with NozeIO `swift-nio-irc-client` (SwiftPM). - Enabled IRC window with new client: connects and joins `#prole` (plain TCP for now; SSL checkbox displays a notice). - Enabled RoyalVNC and removed all optional/flag-based handling. Integrated RoyalVNC via SwiftPM (`royalvnc` on `main`). - Updated VNC UI to current RoyalVNC API: `VNCConnection` + `VNCCAFramebufferView` with a strong delegate reference. - Simplified build to use SwiftPM for both IRC and RoyalVNC. - Fixed runtime embedding and loader issues: - Copy SwiftPM-built `.dylib` products (e.g., `libRoyalVNCKit.dylib`) into `Contents/Frameworks` and codesign them. - Added `@executable_path/../Frameworks` to app rpaths with `install_name_tool`. - Removed manual RoyalVNCKit `.xcframework` building/embedding and any IRCKit traces. #### Key changes - `prole-app/build.sh`: - Build app via SwiftPM; removed IRCKit and manual RoyalVNC build logic. - Embed SwiftPM `.dylib` outputs into `Contents/Frameworks` and set app rpath. - Cleaned usage text; dependencies now handled by SwiftPM. - `prole-app/Package.swift`: - Add `swift-nio-irc-client` (NozeIO) dependency. - Add RoyalVNC via SwiftPM: `https://github.com/royalapplications/royalvnc` on `main`. - `prole-app/Sources/IRCWindowController.swift`: - Migrate to NozeIO `IRC` package API; re-enable Connect; join `#prole`. - Import AppKit; provide convenience init for transcript view. - `prole-app/Sources/WorkstationWindowController.swift`: - Import `RoyalVNCKit`; use `VNCConnection` + `VNCCAFramebufferView`. - Implement `VNCConnectionDelegate` and keep a strong reference to the delegate. - `prole-app/debug.sh`: - Removed IRCKit logs section; kept RoyalVNC logs earlier; then removed manual RoyalVNC altogether. This build now launches successfully (no dyld errors), opens the Workstation window (RoyalVNC), and the IRC window connects via the new client. ### Suggested follow-ups - If TLS is required for IRC, add `NIOSSL` integration and wire SSL checkbox to TLS connection. - Optionally remove leftover Vendor references if any local cache remains.
141 lines
5.6 KiB
Swift
141 lines
5.6 KiB
Swift
import AppKit
|
|
import Foundation
|
|
|
|
// Simple .properties loader with bundle defaults and user override support
|
|
final class Config {
|
|
static let shared = Config()
|
|
|
|
private var props: [String: String] = [:]
|
|
|
|
private init() {
|
|
// Defaults
|
|
props = [
|
|
"svc.host": "svc.prole.org",
|
|
"svc.port": "443",
|
|
"k3s.retropie.host": "retropie.prole.org",
|
|
"k3s.retropie.port": "6443",
|
|
"k3s.pi.host": "pi.prole.org",
|
|
"k3s.pi.port": "6443",
|
|
"k3d.local.host": "localhost",
|
|
"k3d.local.port": "6443"
|
|
]
|
|
|
|
// Load bundled defaults if present
|
|
if let url = Bundle.main.url(forResource: "prole", withExtension: "properties") {
|
|
merge(loadProperties(url: url))
|
|
}
|
|
|
|
// Load user override from Application Support
|
|
let fm = FileManager.default
|
|
if let appSupport = try? fm.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: false) {
|
|
let dir = appSupport.appendingPathComponent("Prole", isDirectory: true)
|
|
let url = dir.appendingPathComponent("prole.properties")
|
|
if fm.fileExists(atPath: url.path) {
|
|
merge(loadProperties(url: url))
|
|
}
|
|
}
|
|
}
|
|
|
|
private func merge(_ other: [String: String]) { for (k, v) in other { props[k] = v } }
|
|
|
|
private func loadProperties(url: URL) -> [String: String] {
|
|
guard let data = try? Data(contentsOf: url), let text = String(data: data, encoding: .utf8) else { return [:] }
|
|
var result: [String: String] = [:]
|
|
for line in text.components(separatedBy: .newlines) {
|
|
let trimmed = line.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
if trimmed.isEmpty || trimmed.hasPrefix("#") { continue }
|
|
if let eq = trimmed.firstIndex(of: "=") {
|
|
let key = String(trimmed[..<eq]).trimmingCharacters(in: .whitespaces)
|
|
let value = String(trimmed[trimmed.index(after: eq)...]).trimmingCharacters(in: .whitespaces)
|
|
if !key.isEmpty { result[key] = value }
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func string(_ key: String, default def: String) -> String { props[key] ?? def }
|
|
func int(_ key: String, default def: Int) -> Int { Int(props[key] ?? "") ?? def }
|
|
|
|
// Convenience accessors used by ServiceChecker/StatusView
|
|
var svcHost: String { string("svc.host", default: "svc.prole.org") }
|
|
var svcPort: Int { int("svc.port", default: 443) }
|
|
|
|
var retropieHost: String { string("k3s.retropie.host", default: "retropie.prole.org") }
|
|
var retropiePort: Int { int("k3s.retropie.port", default: 6443) }
|
|
|
|
var piHost: String { string("k3s.pi.host", default: "pi.prole.org") }
|
|
var piPort: Int { int("k3s.pi.port", default: 6443) }
|
|
|
|
var localHost: String { string("k3d.local.host", default: "localhost") }
|
|
var localPort: Int { int("k3d.local.port", default: 6443) }
|
|
|
|
// UI background config was removed along with the splash screen.
|
|
|
|
// Dev port-forward supervision
|
|
var pfEnabled: Bool {
|
|
let v = (props["pf.enabled"] ?? "").lowercased()
|
|
return v == "1" || v == "true" || v == "yes" || v == "on"
|
|
}
|
|
|
|
// Background/foreground mode for port-forward launcher
|
|
// Defaults to background to honor daemon-style commands.
|
|
var pfModeBackground: Bool {
|
|
let v = (props["pf.mode"] ?? "background").lowercased()
|
|
// Allow synonyms
|
|
if v == "bg" || v == "background" || v == "daemon" { return true }
|
|
if v == "fg" || v == "foreground" { return false }
|
|
return true
|
|
}
|
|
|
|
// Keepalive wrapper for port-forward commands. If true, a shell loop will
|
|
// keep restarting the child command on exit and keep the wrapper process alive.
|
|
// Default: true (so UI remains stable and processes behave like daemons).
|
|
var pfKeepAlive: Bool {
|
|
let v = (props["pf.keepalive"] ?? "true").lowercased()
|
|
return v == "1" || v == "true" || v == "yes" || v == "on"
|
|
}
|
|
|
|
// Enable verbose debug logging for PortForwardManager and related startup.
|
|
// Default: false. Set pf.debug=true to print detailed diagnostics.
|
|
var pfDebug: Bool {
|
|
let v = (props["pf.debug"] ?? "false").lowercased()
|
|
return v == "1" || v == "true" || v == "yes" || v == "on"
|
|
}
|
|
|
|
// Discover and adopt already-running matching port-forward processes at startup/reset
|
|
// Default: true
|
|
var pfDiscovery: Bool {
|
|
let v = (props["pf.discovery"] ?? "true").lowercased()
|
|
return v == "1" || v == "true" || v == "yes" || v == "on"
|
|
}
|
|
|
|
// Adopt existing matching processes instead of launching duplicates
|
|
// Default: true
|
|
var pfAdoptExisting: Bool {
|
|
let v = (props["pf.adoptExisting"] ?? "true").lowercased()
|
|
return v == "1" || v == "true" || v == "yes" || v == "on"
|
|
}
|
|
|
|
// Optional kubeconfig path to export as KUBECONFIG for child processes
|
|
var kubeconfigPath: String? {
|
|
let v = (props["kubeconfig.path"] ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
|
return v.isEmpty ? nil : v
|
|
}
|
|
|
|
var pfCommands: [String] {
|
|
// Collect keys pf.1, pf.2, ... in ascending order
|
|
let keys = props.keys
|
|
.filter { $0.hasPrefix("pf.") }
|
|
.compactMap { k -> (Int, String)? in
|
|
let tail = k.dropFirst(3)
|
|
if let idx = Int(tail) { return (idx, k) }
|
|
return nil
|
|
}
|
|
.sorted { $0.0 < $1.0 }
|
|
.map { $0.1 }
|
|
return keys.compactMap { props[$0] }
|
|
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
|
.filter { !$0.isEmpty }
|
|
}
|
|
}
|