From e04efe4ec3c397603ec43ce245d675ccc8e549fc Mon Sep 17 00:00:00 2001 From: jatmn Date: Wed, 4 Nov 2026 10:15:03 -0800 Subject: [PATCH] fix(macos): regenerate the app's compose file instead of treating it as a repo's Config.repoPath fell back to the data directory when no repo clone was found, and DockerCompose then picked up the compose file it had generated there as if it belonged to a repo. The generator therefore only ran on first launch, so upgraded installs kept their original compose, including the old ghcr.io/gitlawb/node image, which no longer serves anonymous pulls. repoPath now returns nil when there is no clone, and the app regenerates its compose file whenever no repo compose is in use. Project directory and named volumes are unchanged. Co-Authored-By: Claude Opus 5.5 --- macos-app/Sources/GitlawbNode/Config.swift | 11 +++++++--- .../Sources/GitlawbNode/DockerCompose.swift | 21 +++++++++++-------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/macos-app/Sources/GitlawbNode/Config.swift b/macos-app/Sources/GitlawbNode/Config.swift index 406999ba..eef54ed7 100644 --- a/macos-app/Sources/GitlawbNode/Config.swift +++ b/macos-app/Sources/GitlawbNode/Config.swift @@ -28,8 +28,13 @@ class Config { // MARK: - Paths - /// Path to the gitlawb-node repo clone (for local builds). - var repoPath: URL { + /// Path to the gitlawb-node repo clone (for local builds), or nil when there is none. + /// + /// Must not fall back to `dataDirectory`: the app writes its generated compose + /// file there, and treating that file as a repo's own would stop it from ever + /// being regenerated, pinning upgraded installs to the compose (and image) + /// they first started with. + var repoPath: URL? { if !repoPathString.isEmpty { return URL(fileURLWithPath: repoPathString) } @@ -48,7 +53,7 @@ class Config { FileManager.default.fileExists(atPath: dockerfile.path) { return repoRoot } - return dataDirectory + return nil } var dataDirectory: URL { diff --git a/macos-app/Sources/GitlawbNode/DockerCompose.swift b/macos-app/Sources/GitlawbNode/DockerCompose.swift index 1d005c5e..2750f016 100644 --- a/macos-app/Sources/GitlawbNode/DockerCompose.swift +++ b/macos-app/Sources/GitlawbNode/DockerCompose.swift @@ -39,11 +39,11 @@ class DockerCompose { /// Path to the docker-compose.yml — uses the repo's file if available, otherwise generates one. var composeFilePath: String { // Prefer the repo's own docker-compose.yml (supports `build: .`) - let repoCompose = Config.shared.repoPath.appendingPathComponent("docker-compose.yml") - if FileManager.default.fileExists(atPath: repoCompose.path) { - return repoCompose.path + if let repo = repoWithCompose { + return repo.appendingPathComponent("docker-compose.yml").path } - // Fallback: generate a compose file with pre-built image + // Fallback: (re)generate the compose file with the pre-built image each time, + // so an app upgrade also upgrades the stack it runs. let userPath = Config.shared.dataDirectory.appendingPathComponent("docker-compose.yml").path try? FileManager.default.createDirectory(at: Config.shared.dataDirectory, withIntermediateDirectories: true) let content = Self.generateComposeFile() @@ -53,11 +53,14 @@ class DockerCompose { /// Project directory for docker compose (needed for `build: .` context). var projectDirectory: String { - let repoCompose = Config.shared.repoPath.appendingPathComponent("docker-compose.yml") - if FileManager.default.fileExists(atPath: repoCompose.path) { - return Config.shared.repoPath.path - } - return Config.shared.dataDirectory.path + (repoWithCompose ?? Config.shared.dataDirectory).path + } + + /// The repo clone, if there is one and it has its own docker-compose.yml. + private var repoWithCompose: URL? { + guard let repo = Config.shared.repoPath else { return nil } + let compose = repo.appendingPathComponent("docker-compose.yml") + return FileManager.default.fileExists(atPath: compose.path) ? repo : nil } /// Path to the .env file with user configuration.