rename: Jarvis -> Jervis target, scheme, and folder throughout
The bundle-ID and display-name renames weren't enough — the actual Xcode target/product name was still "Jarvis", which drives CFBundleName, Xcode's Organizer archive list, the .xcodeproj filename, and the scheme name. Renamed all the way through: - project.yml: top-level name, target key, PRODUCT_NAME, source/info paths - Jarvis/ -> Jervis/ (source folder, git-tracked as renames, no content diffs on the moved files) - .gitignore, CI workflows, README, CONTRIBUTING: Jarvis.xcodeproj / scheme Jarvis -> Jervis.xcodeproj / scheme Jervis Verified: xcodebuild -scheme Jervis succeeds, produces Jervis.app, CFBundleName/CFBundleExecutable/CFBundleDisplayName all read "Jervis". CFBundleIdentifier intentionally stays com.kisani.jarvis (per prior decision — bundle ID isn't user-facing anywhere including Organizer). Swift type names (JarvisApp, JarvisWordmark, etc.) and the Gitea repo name/URL are unchanged — pure internal source identifiers, not user or developer-facing product identity. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
101
Jervis/Connectivity/VPNProvider.swift
Normal file
101
Jervis/Connectivity/VPNProvider.swift
Normal file
@@ -0,0 +1,101 @@
|
||||
// VPNProvider.swift
|
||||
// Jarvis — remote-access providers. The app can't start another app's VPN tunnel
|
||||
// (iOS sandboxing), so the provider only tailors hints, help text, and an
|
||||
// "open the app" jump. Reachability + endpoint selection is provider-agnostic.
|
||||
|
||||
import UIKit
|
||||
|
||||
enum VPNProvider: String, CaseIterable, Codable, Identifiable {
|
||||
case direct // LAN only, no VPN
|
||||
case tailscale
|
||||
case headscale // self-hosted control plane, Tailscale clients
|
||||
case netbird
|
||||
case zerotier
|
||||
case netmaker
|
||||
case wireguard
|
||||
|
||||
var id: String { rawValue }
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .direct: return "Direct (LAN)"
|
||||
case .tailscale: return "Tailscale"
|
||||
case .headscale: return "Headscale"
|
||||
case .netbird: return "NetBird"
|
||||
case .zerotier: return "ZeroTier"
|
||||
case .netmaker: return "Netmaker"
|
||||
case .wireguard: return "WireGuard"
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a separate remote/VPN address is meaningful for this provider.
|
||||
var needsRemote: Bool { self != .direct }
|
||||
|
||||
/// Placeholder shown in the remote-address field.
|
||||
var remoteHint: String {
|
||||
switch self {
|
||||
case .direct: return "—"
|
||||
case .tailscale: return "jarvis.tailnet.ts.net:8080 or 100.x.y.z:8080"
|
||||
case .headscale: return "100.x.y.z:8080 (MagicDNS or tailnet IP)"
|
||||
case .netbird: return "100.x.y.z:8080"
|
||||
case .zerotier: return "10.x.x.x:8080 (managed IP)"
|
||||
case .netmaker: return "10.x.x.x:8080"
|
||||
case .wireguard: return "10.0.0.x:8080 (peer IP)"
|
||||
}
|
||||
}
|
||||
|
||||
var blurb: String {
|
||||
switch self {
|
||||
case .direct:
|
||||
return "Use the LAN address only. Best when the phone is always on the home network."
|
||||
case .tailscale:
|
||||
return "On your home wifi Jarvis uses the LAN address directly; away, it uses your Tailscale address. Make sure Tailscale is connected when remote."
|
||||
case .headscale:
|
||||
return "Self-hosted Tailscale control plane. Same client behaviour as Tailscale — keep the client connected when away."
|
||||
case .netbird:
|
||||
return "Open-source WireGuard mesh. Keep the NetBird app connected when off the LAN."
|
||||
case .zerotier:
|
||||
return "Managed network via ZeroTier. Keep the ZeroTier app joined to the network when remote."
|
||||
case .netmaker:
|
||||
return "Self-hosted WireGuard mesh. Keep your Netmaker client up when off the LAN."
|
||||
case .wireguard:
|
||||
return "Plain WireGuard tunnel. Enable the tunnel in the WireGuard app when remote."
|
||||
}
|
||||
}
|
||||
|
||||
/// Best-effort URL scheme + App Store fallback used by "Open <provider>".
|
||||
/// iOS can't start the tunnel for us — this just brings the app forward.
|
||||
private var urlScheme: URL? {
|
||||
switch self {
|
||||
case .tailscale, .headscale: return URL(string: "tailscale://")
|
||||
case .zerotier: return URL(string: "zerotier://")
|
||||
case .wireguard: return URL(string: "wireguard://")
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
private var appStoreURL: URL? {
|
||||
switch self {
|
||||
case .tailscale, .headscale: return URL(string: "https://apps.apple.com/app/id1470499037")
|
||||
case .zerotier: return URL(string: "https://apps.apple.com/app/id1084101492")
|
||||
case .wireguard: return URL(string: "https://apps.apple.com/app/id1441195209")
|
||||
case .netbird: return URL(string: "https://apps.apple.com/app/id6443155627")
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
var canOpenApp: Bool { urlScheme != nil || appStoreURL != nil }
|
||||
|
||||
/// Try the app's scheme; fall back to its App Store page.
|
||||
@MainActor
|
||||
func openApp() {
|
||||
let candidates = [urlScheme, appStoreURL].compactMap { $0 }
|
||||
func attempt(_ i: Int) {
|
||||
guard i < candidates.count else { return }
|
||||
UIApplication.shared.open(candidates[i]) { ok in
|
||||
if !ok { attempt(i + 1) }
|
||||
}
|
||||
}
|
||||
attempt(0)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user