Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
294 changes: 200 additions & 94 deletions .github/workflows/nightly.yml

Large diffs are not rendered by default.

Binary file added Assets.xcassets/AppIcon-RC.appiconset/128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Assets.xcassets/AppIcon-RC.appiconset/128@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Assets.xcassets/AppIcon-RC.appiconset/16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Assets.xcassets/AppIcon-RC.appiconset/16@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Assets.xcassets/AppIcon-RC.appiconset/256.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Assets.xcassets/AppIcon-RC.appiconset/256@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Assets.xcassets/AppIcon-RC.appiconset/32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Assets.xcassets/AppIcon-RC.appiconset/32@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Assets.xcassets/AppIcon-RC.appiconset/512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Assets.xcassets/AppIcon-RC.appiconset/512@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
68 changes: 68 additions & 0 deletions Assets.xcassets/AppIcon-RC.appiconset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"images" : [
{
"filename" : "16.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"filename" : "16@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"filename" : "32.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"filename" : "32@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"filename" : "128.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"filename" : "128@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"filename" : "256.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"filename" : "256@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"filename" : "512.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"filename" : "512@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
15 changes: 7 additions & 8 deletions CLI/CLISocketPathResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ struct CLISocketPathResolver {
static let legacyDefaultSocketPath = "/tmp/cmux.sock"
private static let fallbackSocketPath = "/tmp/cmux-debug.sock"
private static let nightlySocketPath = "/tmp/cmux-nightly.sock"
private static let rcSocketPath = "/tmp/cmux-rc.sock"
private static let stagingSocketPath = "/tmp/cmux-staging.sock"

private let environment: [String: String]
Expand Down Expand Up @@ -161,9 +162,7 @@ struct CLISocketPathResolver {
self.currentUserID = currentUserID
self.inspectSocketPathEntry = inspectSocketPathEntry
self.socketAcceptsConnections = socketAcceptsConnections
self.stateDirectory = stateDirectory ?? CmuxStateDirectory.url(
homeDirectory: fileManager.homeDirectoryForCurrentUser
)
self.stateDirectory = stateDirectory ?? CmuxStateDirectory.url(homeDirectory: fileManager.homeDirectoryForCurrentUser)
}

static func defaultSocketPath(
Expand All @@ -177,14 +176,13 @@ struct CLISocketPathResolver {
stableSocketPath: stableDefaultSocketPath,
debugSocketPath: fallbackSocketPath,
nightlySocketPath: nightlySocketPath,
rcSocketPath: rcSocketPath,
stagingSocketPath: stagingSocketPath
)
}

private static var stableDefaultSocketPath: String {
let stablePath: String? = stableSocketDirectoryURL()?
.appendingPathComponent(stableSocketFileName, isDirectory: false)
.path
let stablePath: String? = stableSocketDirectoryURL()?.appendingPathComponent(stableSocketFileName, isDirectory: false).path
return stablePath ?? legacyDefaultSocketPath
}

Expand All @@ -200,6 +198,7 @@ struct CLISocketPathResolver {
stableSocketPath: resolvedStableDefaultSocketPath,
debugSocketPath: Self.fallbackSocketPath,
nightlySocketPath: Self.nightlySocketPath,
rcSocketPath: Self.rcSocketPath,
stagingSocketPath: Self.stagingSocketPath
)
}
Expand Down Expand Up @@ -307,15 +306,15 @@ struct CLISocketPathResolver {
switch variant {
case .stable:
return true
case .nightly, .staging, .dev:
case .nightly, .rc, .staging, .dev:
return Self.pathsMatch(requestedPath, defaultPath)
|| !Self.containsPath(resolvedStableImplicitDefaultPaths(), requestedPath)
}
}

private func implicitFallbackCandidatePaths(for variant: SocketPathVariant) -> [String] {
switch variant {
case .stable, .nightly, .staging, .dev:
case .stable, .nightly, .rc, .staging, .dev:
return resolvedStableImplicitDefaultPaths()
}
}
Expand Down
5 changes: 5 additions & 0 deletions CLI/CMUXCLI+ThemeSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,8 @@ extension CMUXCLI {
return "com.cmuxterm.app.debug"
case "cmux-nightly.sock":
return "com.cmuxterm.app.nightly"
case "cmux-rc.sock":
return "com.cmuxterm.app.rc"
case "cmux-staging.sock":
return "com.cmuxterm.app.staging"
default:
Expand All @@ -369,6 +371,9 @@ extension CMUXCLI {
if let slug = themeReloadSocketSlug(name, prefix: "cmux-nightly-", suffix: ".sock") {
return "com.cmuxterm.app.nightly.\(slug)"
}
if let slug = themeReloadSocketSlug(name, prefix: "cmux-rc-", suffix: ".sock") {
return "com.cmuxterm.app.rc.\(slug)"
}
if let slug = themeReloadSocketSlug(name, prefix: "cmux-staging-", suffix: ".sock") {
return "com.cmuxterm.app.staging.\(slug)"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ public import Foundation
/// Recognizes and parses cmux auth callback URLs.
///
/// Accepts the built-in cmux callback schemes (`cmux`, `cmux-nightly`,
/// `cmux-dev`) plus an optional runtime override (e.g. a per-tag Debug build's
/// unique scheme), injected at construction so the type never reads the
/// process environment itself. A pure value; construct it once at the
/// `cmux-rc`, `cmux-dev`) plus an optional runtime override (e.g. a per-tag
/// Debug build's unique scheme), injected at construction so the type never
/// reads the process environment itself. A pure value; construct it once at the
/// composition root and inject it.
public struct AuthCallbackRouter: Sendable {
private static let builtInSchemes: Set<String> = ["cmux", "cmux-nightly", "cmux-dev"]
private static let builtInSchemes: Set<String> = ["cmux", "cmux-nightly", "cmux-rc", "cmux-dev"]
private let extraAllowedScheme: String?

/// Creates a router.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ internal import Foundation

/// Defines which authenticated Mac app instances one iOS app build may use.
///
/// Mac app identity remains exact (`default`, `nightly`, or a development tag),
/// Mac app identity remains exact (`default`, `nightly`, `rc`, or a development tag),
/// while this policy supplies the compatibility boundary used by persistence,
/// registry projection, and live connection validation.
public enum MobileMacBuildCompatibilityPolicy: Equatable, Sendable {
Expand All @@ -25,7 +25,7 @@ public enum MobileMacBuildCompatibilityPolicy: Equatable, Sendable {
expectedInstanceTag: String,
additionalInstanceTags: MobileMacTagAllowlist
)
/// A distributed iOS build may use Stable and Nightly Mac releases.
/// A distributed iOS build may use Stable, Nightly, and RC Mac releases.
case official

public static func development(
Expand Down Expand Up @@ -115,7 +115,7 @@ public enum MobileMacBuildCompatibilityPolicy: Equatable, Sendable {
!Self.isOfficialMacNamespace(clientNamespace) {
return false
}
return normalizedTag == "default" || normalizedTag == "nightly"
return normalizedTag == "default" || normalizedTag == "nightly" || normalizedTag == "rc"
}
}

Expand All @@ -128,6 +128,8 @@ public enum MobileMacBuildCompatibilityPolicy: Equatable, Sendable {
value == "mac:com.cmuxterm.app"
|| value == "mac:com.cmuxterm.app.nightly"
|| value.hasPrefix("mac:com.cmuxterm.app.nightly.")
|| value == "mac:com.cmuxterm.app.rc"
|| value.hasPrefix("mac:com.cmuxterm.app.rc.")
}

/// Returns whether authenticated host status is compatible with this build.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,17 +113,29 @@ import Testing
#expect(!untagged.allows(instanceTag: "unrelated"))
}

@Test func officialKeepsStableAndNightlyAsDistinctAllowedIdentities() {
@Test func officialKeepsStableNightlyAndRCAsDistinctAllowedIdentities() {
let policy = MobileMacBuildCompatibilityPolicy.official

#expect(policy.allows(instanceTag: "default"))
#expect(policy.allows(instanceTag: "nightly"))
#expect(policy.allows(instanceTag: "rc"))
#expect(!policy.allows(instanceTag: "icap"))
#expect(!policy.allows(instanceTag: "rc"))
#expect(!policy.allows(instanceTag: "staging"))
#expect(!policy.allows(instanceTag: nil))
}

@Test func officialAcceptsRCMacNamespaces() {
let policy = MobileMacBuildCompatibilityPolicy.official

#expect(policy.allows(instanceTag: "rc", clientNamespace: "mac:com.cmuxterm.app.rc"))
// A tagged RC Mac advertises its slug as the instance tag, so distributed
// builds treat it like a tagged nightly Mac: recognized namespace, but not a
// release-lane tag, so no pairing.
#expect(!policy.allows(instanceTag: "candidate1", clientNamespace: "mac:com.cmuxterm.app.rc.candidate1"))
#expect(!policy.allows(instanceTag: "rc", clientNamespace: "mac:com.cmuxterm.app.debug.rc"))
#expect(!policy.allows(instanceTag: "rc", clientNamespace: "mac:com.cmuxterm.app.staging"))
}

@Test func officialAllowsOnlyAuthorizedLegacy06417WithoutAnInstanceTag() {
let policy = MobileMacBuildCompatibilityPolicy.official

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@ public struct BrowserHistoryLocation: Sendable {
self.bundleIdentifier = bundleIdentifier
}

/// Folds tagged debug/staging bundle identifiers down to a shared namespace
/// Folds tagged debug/rc/staging bundle identifiers down to a shared namespace
/// so every dev build of the same lane reuses one history file, while
/// production identifiers pass through unchanged.
public static func normalizedNamespace(bundleIdentifier: String) -> String {
if bundleIdentifier.hasPrefix("com.cmuxterm.app.debug.") {
return "com.cmuxterm.app.debug"
}
if bundleIdentifier.hasPrefix("com.cmuxterm.app.rc.") {
return "com.cmuxterm.app.rc"
}
if bundleIdentifier.hasPrefix("com.cmuxterm.app.staging.") {
return "com.cmuxterm.app.staging"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,14 +115,15 @@ extension SocketTransport {

/// Whether a refused socket at `path` may be replaced even without a
/// reusable lock marker, based on the well-known cmux socket filenames
/// (debug/nightly/staging and their tagged variants).
/// (debug/nightly/rc/staging and their tagged variants).
func canReplaceUnmarkedRefusedSocket(at path: String) -> Bool {
let filename = URL(fileURLWithPath: (path as NSString).standardizingPath)
.lastPathComponent
.lowercased()
let recoverablePrefixes = ["cmux-debug-", "cmux-nightly-", "cmux-staging-"]
let recoverablePrefixes = ["cmux-debug-", "cmux-nightly-", "cmux-rc-", "cmux-staging-"]
return filename == "cmux-debug.sock" ||
filename == "cmux-nightly.sock" ||
filename == "cmux-rc.sock" ||
filename == "cmux-staging.sock" ||
recoverablePrefixes.contains { prefix in
filename.hasPrefix(prefix) && filename.hasSuffix(".sock")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ public import Foundation
/// an instantiated, dependency-injected resolver is deferred to the engine lift.
public struct CmuxGhosttyConfigPathResolver {
/// The bundle identifier of the released cmux app, used as the canonical
/// config location and the fallback for dev/nightly/staging channels.
/// config location and the fallback for dev/nightly/rc/staging channels.
public static let releaseBundleIdentifier = "com.cmuxterm.app"
private static let releaseFallbackChannelSuffixes = ["debug", "nightly", "staging"]
private static let releaseFallbackChannelSuffixes = ["debug", "nightly", "rc", "staging"]

public init() {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import Testing
"com.cmuxterm.app",
"com.cmuxterm.app.nightly",
"com.cmuxterm.app.nightly.rc1",
"com.cmuxterm.app.rc",
"com.cmuxterm.app.rc.candidate1",
"com.cmuxterm.app.staging",
"com.cmuxterm.app.debug",
"com.cmuxterm.app.debug.snmsc",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ public enum SocketPathMarkerFiles {
public static let stableMarkerFileName = "last-socket-path"
public static let stableTmpPath = "/tmp/cmux-last-socket-path"
public static let nightlyBundleIdentifier = "com.cmuxterm.app.nightly"
public static let rcBundleIdentifier = "com.cmuxterm.app.rc"
public static let stagingBundleIdentifier = "com.cmuxterm.app.staging"
public static let defaultBaseDebugBundleIdentifier = "com.cmuxterm.app.debug"
public static let defaultDebugSocketPath = "/tmp/cmux-debug.sock"
public static let defaultNightlySocketPath = "/tmp/cmux-nightly.sock"
public static let defaultRCSocketPath = "/tmp/cmux-rc.sock"
public static let defaultStagingSocketPath = "/tmp/cmux-staging.sock"

public static func markerFileURL(
Expand Down Expand Up @@ -54,6 +56,13 @@ public enum SocketPathMarkerFiles {
if bundleId.hasPrefix(nightlyPrefix) {
return .nightly(slug: bundleSuffixSlug(bundleId, prefix: nightlyPrefix))
}
if bundleId == rcBundleIdentifier {
return .rc(slug: nil)
}
let rcPrefix = rcBundleIdentifier + "."
if bundleId.hasPrefix(rcPrefix) {
return .rc(slug: bundleSuffixSlug(bundleId, prefix: rcPrefix))
}
if bundleId == stagingBundleIdentifier {
return .staging(slug: nil)
}
Expand Down Expand Up @@ -82,6 +91,7 @@ public enum SocketPathMarkerFiles {
baseDebugBundleIdentifier: String = defaultBaseDebugBundleIdentifier,
debugSocketPath: String = defaultDebugSocketPath,
nightlySocketPath: String = defaultNightlySocketPath,
rcSocketPath: String = defaultRCSocketPath,
stagingSocketPath: String = defaultStagingSocketPath
) -> String {
switch variant(
Expand All @@ -96,6 +106,11 @@ public enum SocketPathMarkerFiles {
return "/tmp/cmux-nightly-\(slug).sock"
}
return nightlySocketPath
case .rc(let slug):
if let slug {
return "/tmp/cmux-rc-\(slug).sock"
}
return rcSocketPath
case .staging(let slug):
if let slug {
return "/tmp/cmux-staging-\(slug).sock"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
/// A flavor of the cmux app, used to derive per-flavor socket and marker file paths.
///
/// Stable, nightly, staging, and dev builds each run side by side with isolated control
/// Stable, nightly, rc, staging, and dev builds each run side by side with isolated control
/// sockets so they never collide. The associated `slug` (when present) further scopes
/// nightly/staging/dev builds that carry a tag in their bundle identifier or `CMUX_TAG`.
/// nightly/rc/staging/dev builds that carry a tag in their bundle identifier or `CMUX_TAG`.
public enum SocketPathVariant: Equatable, Sendable {
/// The shipping release build.
case stable
/// A nightly build, optionally tag-scoped by `slug`.
case nightly(slug: String?)
/// A release-candidate build, optionally tag-scoped by `slug`.
case rc(slug: String?)
/// A staging build, optionally tag-scoped by `slug`.
case staging(slug: String?)
/// A local debug/dev build, optionally tag-scoped by `slug`.
Expand All @@ -24,6 +26,11 @@ public enum SocketPathVariant: Equatable, Sendable {
return "nightly-\(slug)-last-socket-path"
}
return "nightly-last-socket-path"
case .rc(let slug):
if let slug = Self.sanitizedSlug(slug) {
return "rc-\(slug)-last-socket-path"
}
return "rc-last-socket-path"
case .staging(let slug):
if let slug = Self.sanitizedSlug(slug) {
return "staging-\(slug)-last-socket-path"
Expand All @@ -47,6 +54,11 @@ public enum SocketPathVariant: Equatable, Sendable {
return "/tmp/cmux-nightly-\(slug)-last-socket-path"
}
return "/tmp/cmux-nightly-last-socket-path"
case .rc(let slug):
if let slug = Self.sanitizedSlug(slug) {
return "/tmp/cmux-rc-\(slug)-last-socket-path"
}
return "/tmp/cmux-rc-last-socket-path"
case .staging(let slug):
if let slug = Self.sanitizedSlug(slug) {
return "/tmp/cmux-staging-\(slug)-last-socket-path"
Expand Down
Loading