115 lines
4.6 KiB
Swift
115 lines
4.6 KiB
Swift
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))
|
|
}
|
|
}
|