Files
jarvis/Jervis/Connectivity/VPNProvider.swift
Kutesir bd632a1592
Some checks are pending
CI / Build · Debug (push) Waiting to run
CI / Build · Release (push) Waiting to run
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>
2026-07-13 03:49:31 +03:00

102 lines
4.0 KiB
Swift

// 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)
}
}