1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2026-03-27 14:30:08 +01:00

Add automatic fix during startup (sudoers step only for now)

This commit is contained in:
2026-02-17 17:03:44 +01:00
parent ed6a02e253
commit b95faa6895
9 changed files with 68 additions and 21 deletions

View File

@@ -2621,7 +2621,7 @@
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 1420;
LastUpgradeCheck = 2620;
LastUpgradeCheck = 2630;
ORGANIZATIONNAME = "Nico Verbruggen";
TargetAttributes = {
C406A5EF298AD2CE00B5B85A = {
@@ -4696,7 +4696,7 @@
repositoryURL = "https://github.com/nicoverbruggen/NVAlert";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.0.0;
minimumVersion = 2.1.0;
};
};
/* End XCRemoteSwiftPackageReference section */

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2620"
LastUpgradeVersion = "2630"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2620"
LastUpgradeVersion = "2630"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2620"
LastUpgradeVersion = "2630"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2620"
LastUpgradeVersion = "2630"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -14,6 +14,7 @@ import Foundation
*/
struct EnvironmentCheck {
let command: (_ container: Container) async -> Bool
let fixCommand: ((_ container: Container) async throws -> Void)?
let name: String
let titleText: String
let subtitleText: String
@@ -23,6 +24,7 @@ struct EnvironmentCheck {
init(
command: @escaping (_ container: Container) async -> Bool,
fix: ((_ container: Container) async throws -> Void)? = nil,
name: String,
titleText: String,
subtitleText: String,
@@ -31,6 +33,7 @@ struct EnvironmentCheck {
requiresAppRestart: Bool = false,
) {
self.command = command
self.fixCommand = fix
self.name = name
self.titleText = titleText
self.subtitleText = subtitleText

View File

@@ -44,7 +44,26 @@ class Startup {
// If we get here, something's gone wrong and the check has failed...
Log.info("[FAIL] \(check.name) (\(start.milliseconds) ms)")
await showAlert(for: check)
// We will present the user with an option (potentially)
let outcome = await showAlert(for: check)
if outcome == .shouldRunFix {
// First, validate there's a fix
guard let command = check.fixCommand else {
return false
}
// We will try to run the fix, it may fail!
do {
try await command(App.shared.container)
return await check.succeeds()
} catch {
// our fix failed :(
return false
}
}
return false
}
} else {
@@ -59,12 +78,17 @@ class Startup {
return true
}
enum EnvironmentAlertOutcome {
case shouldRunFix
case shouldRetryStartup
}
/**
Displays an alert for a particular check. There are two types of alerts:
- ones that require an app restart, which prompt the user to exit the app
- ones that allow the app to continue, which allow the user to retry
*/
@MainActor private func showAlert(for check: EnvironmentCheck) {
@MainActor private func showAlert(for check: EnvironmentCheck) -> EnvironmentAlertOutcome {
// Ensure that the timeout does not fire until we restart
Self.startupTimer?.invalidate()
@@ -80,14 +104,30 @@ class Startup {
}).show(urgency: .bringToFront)
}
NVAlert()
// Verify if an automatic fix is available
let hasAutomaticFix = check.fixCommand != nil
// Present an alert with one or two buttons (depending on fix)
let outcome = NVAlert()
.withInformation(
title: check.titleText,
subtitle: check.subtitleText,
description: check.descriptionText
)
.withPrimary(text: "generic.ok".localized)
.show(urgency: .bringToFront)
.withPrimary(text: hasAutomaticFix ? "startup.fix_for_me".localized : "startup.fix_manually".localized)
.withSecondary(if: hasAutomaticFix, text: "startup.fix_manually".localized)
.withTertiary(if: hasAutomaticFix, text: "", action: { _ in
NSWorkspace.shared.open(Constants.Urls.FrequentlyAskedQuestions)
})
.runModal(urgency: .bringToFront)
// If there's an automatic fix and we chose to fix it, return outcome
if hasAutomaticFix && outcome == .alertFirstButtonReturn {
return .shouldRunFix
}
// In any other situation, we will require a retry of the startup
return .shouldRetryStartup
}
// MARK: - Check (List)
@@ -212,6 +252,9 @@ class Startup {
.pipe("cat /private/etc/sudoers.d/brew")
.out.contains(container.paths.brew)
},
fix: { container in
try sudo("export USER=\(container.paths.whoami) && export PATH=/bin:/usr/bin:\(container.paths.binPath) && valet trust")
},
name: "`/private/etc/sudoers.d/brew` contains brew",
titleText: "startup.errors.sudoers_brew.title".localized,
subtitleText: "startup.errors.sudoers_brew.subtitle".localized,

View File

@@ -73,7 +73,7 @@ class ValetUpgrader {
}
@MainActor private static func notifyAboutCompletion() {
return NVAlert().withInformation(
NVAlert().withInformation(
title: "valet_upgraded.title".localized,
subtitle: "valet_upgraded.subtitle".localized,
description: "valet_upgraded.description".localized,
@@ -85,7 +85,7 @@ class ValetUpgrader {
}
@MainActor private static func notifyAboutUpgrade(latest: String, constraint: String, passing: Bool) {
let alert = NVAlert().withInformation(
return NVAlert().withInformation(
title: "valet_upgrade_available.title".localized,
subtitle: "valet_upgrade_available.subtitle".localized(latest),
description: passing
@@ -97,13 +97,9 @@ class ValetUpgrader {
ValetUpgrader.upgradeValet()
})
.withSecondary(text: "valet_upgrade_available.cancel".localized)
if !passing {
_ = alert.withTertiary(text: "valet_upgrade_available.open_composer".localized, action: { _ in
.withTertiary(if: !passing, text: "valet_upgrade_available.open_composer".localized, action: { _ in
MainMenu.shared.openGlobalComposerFolder()
})
}
alert.show(urgency: .bringToFront)
.show(urgency: .bringToFront)
}
}

View File

@@ -958,3 +958,8 @@ PHP Monitor will tell Valet to unsecure and re-secure all expired domains for yo
"alert.enable_integrations.desc" = "If you did not trigger this via Alfred or Raycast, there may be another application trying to control PHP Monitor.\n\nIn such a case, I recommend keeping this integration turned off, unless you are fine with another third party app controlling PHP Monitor for you, which could present a potential security risk.";
"alert.enable_integrations.ok" = "Allow Integrations";
"alert.enable_integrations.cancel" = "Don't Allow";
// AUTOMATIC FIXES AT STARTUP
"startup.fix_for_me" = "Fix For Me";
"startup.fix_manually" = "I Fixed It";