import Foundation import SwiftUI import UIKit struct LocalGitServer: Codable, Identifiable { let id: UUID var displayName: String var tailscaleIP: String var sshUser: String var reposBasePath: String var sshPort: Int var sshBaseURL: String { let p = sshPort == 22 ? "" : ":\(sshPort)" return "ssh://\(sshUser)@\(tailscaleIP)\(p)" } func repoURL(for name: String) -> String { "\(sshBaseURL)\(reposBasePath)/\(name).git" } init(id: UUID = UUID(), displayName: String, tailscaleIP: String, sshUser: String, reposBasePath: String, sshPort: Int = 22) { self.id = id; self.displayName = displayName; self.tailscaleIP = tailscaleIP self.sshUser = sshUser; self.reposBasePath = reposBasePath; self.sshPort = sshPort } } @MainActor final class LocalGitServerStore: ObservableObject { @Published var servers: [LocalGitServer] = [] { didSet { persist() } } private let key = "padxcode.localGitServers" init() { load() } func add(_ s: LocalGitServer) { servers.append(s) } func remove(id: UUID) { servers.removeAll { $0.id == id } } private func persist() { if let d = try? JSONEncoder().encode(servers) { UserDefaults.standard.set(d, forKey: key) } } private func load() { guard let d = UserDefaults.standard.data(forKey: key), let v = try? JSONDecoder().decode([LocalGitServer].self, from: d) else { return } servers = v } } // MARK: - SSH Setup View struct SSHKeySetupView: View { @Environment(\.dismiss) private var dismiss var body: some View { NavigationStack { ScrollView { VStack(alignment: .leading, spacing: 20) { SetupStep("1", title: "Copy Working Copy SSH key", body: "Working Copy → Settings → SSH Keys → copy public key.") SetupStep("2", title: "Enable Remote Login on Mac", body: "System Settings → General → Sharing → Remote Login → On.") SetupStep("3", title: "Add key to authorized_keys", body: "Run on your Mac:") CodeBlock(""" echo "PASTE_YOUR_WC_PUBLIC_KEY" >> ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys """) SetupStep("4", title: "Create a bare repository", body: "Run on your Mac:") CodeBlock(""" mkdir -p ~/GitRemotes/NavidromePlayer.git cd ~/GitRemotes/NavidromePlayer.git && git init --bare cd ~/Dev/NavidromePlayer git remote add origin ~/GitRemotes/NavidromePlayer.git git push -u origin main """) SetupStep("5", title: "Clone in Working Copy", body: "Working Copy → + → Clone → enter your SSH URL:") CodeBlock("ssh://dallas@100.x.x.x/Users/dallas/GitRemotes/NavidromePlayer.git") } .padding(20) } .navigationTitle("SSH + Local Git Setup") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .confirmationAction) { Button("Done") { dismiss() } } } } } } private struct SetupStep: View { let step: String; let title: String; let detail: String init(_ step: String, title: String, body: String) { self.step = step; self.title = title; self.detail = body } var body: some View { HStack(alignment: .top, spacing: 12) { Text(step).font(.system(.title3, design: .rounded).bold()) .foregroundStyle(.white).frame(width: 28, height: 28) .background(Color.accentColor, in: Circle()) VStack(alignment: .leading, spacing: 4) { Text(title).font(.headline) Text(detail).font(.subheadline).foregroundStyle(.secondary) } } } } private struct CodeBlock: View { let code: String init(_ code: String) { self.code = code } var body: some View { HStack(alignment: .top) { ScrollView(.horizontal, showsIndicators: false) { Text(code).font(.system(.caption, design: .monospaced)) .textSelection(.enabled).padding(12) } Button { UIPasteboard.general.string = code } label: { Image(systemName: "doc.on.doc").font(.caption).padding(10) } .buttonStyle(.plain).foregroundStyle(.secondary) } .background(Color(.systemBackground), in: RoundedRectangle(cornerRadius: 8)) .overlay(RoundedRectangle(cornerRadius: 8).stroke(Color(.separator), lineWidth: 0.5)) } }