2025-07-31 20:23:36 +00:00
|
|
|
//go:build darwin
|
|
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"fmt"
|
|
|
|
|
"os"
|
|
|
|
|
"os/exec"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/fleetdm/fleet/v4/orbit/pkg/constant"
|
|
|
|
|
"github.com/fleetdm/fleet/v4/orbit/pkg/scripts"
|
|
|
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
|
|
|
queries "github.com/fleetdm/fleet/v4/server/service/osquery_utils"
|
|
|
|
|
kitlog "github.com/go-kit/log"
|
|
|
|
|
"github.com/go-kit/log/level"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var preInstalled = []string{
|
|
|
|
|
"firefox/darwin",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func postApplicationInstall(appLogger kitlog.Logger, appPath string) error {
|
|
|
|
|
if appPath == "" {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
level.Info(appLogger).Log("msg", fmt.Sprintf("Forcing LaunchServices refresh for: '%s'", appPath))
|
|
|
|
|
err := forceLaunchServicesRefresh(appPath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("Error forcing LaunchServices refresh: %v. Attempting to continue", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
level.Info(appLogger).Log("msg", fmt.Sprintf("Attempting to remove quarantine for: '%s'", appPath))
|
|
|
|
|
quarantineResult, err := removeAppQuarantine(appPath)
|
|
|
|
|
|
|
|
|
|
level.Info(appLogger).Log("msg", fmt.Sprintf("Quarantine output error: %v", quarantineResult.QuarantineOutputError))
|
|
|
|
|
level.Info(appLogger).Log("msg", fmt.Sprintf("Quarantine status: %s", quarantineResult.QuarantineStatus))
|
|
|
|
|
level.Info(appLogger).Log("msg", fmt.Sprintf("Spctl output error: %v", quarantineResult.SpctlOutputError))
|
|
|
|
|
level.Info(appLogger).Log("msg", fmt.Sprintf("spctl status: %s", quarantineResult.SpctlStatus))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("Error removing app quarantine: %v. Attempting to continue", err)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type QuarantineResult struct {
|
|
|
|
|
QuarantineOutputError error
|
|
|
|
|
QuarantineStatus string
|
|
|
|
|
SpctlOutputError error
|
|
|
|
|
SpctlStatus string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func removeAppQuarantine(appPath string) (QuarantineResult, error) {
|
|
|
|
|
var result QuarantineResult
|
|
|
|
|
|
|
|
|
|
cmd := exec.Command("xattr", "-p", "com.apple.quarantine", appPath)
|
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
|
|
|
if err != nil {
|
|
|
|
|
result.QuarantineOutputError = fmt.Errorf("checking quarantine status: %v", err)
|
|
|
|
|
}
|
|
|
|
|
result.QuarantineStatus = fmt.Sprintf("Quarantine status: '%s'", strings.TrimSpace(string(output)))
|
|
|
|
|
cmd = exec.Command("spctl", "-a", "-v", appPath)
|
|
|
|
|
output, err = cmd.CombinedOutput()
|
|
|
|
|
if err != nil {
|
|
|
|
|
result.SpctlOutputError = fmt.Errorf("checking spctl status: %v", err)
|
|
|
|
|
}
|
|
|
|
|
result.SpctlStatus = fmt.Sprintf("spctl status: '%s'", strings.TrimSpace(string(output)))
|
|
|
|
|
|
|
|
|
|
cmd = exec.Command("sudo", "spctl", "--add", appPath)
|
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
|
|
|
return result, fmt.Errorf("adding app to quarantine exceptions: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd = exec.Command("sudo", "xattr", "-r", "-d", "com.apple.quarantine", appPath)
|
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
|
|
|
return result, fmt.Errorf("removing quarantine attribute: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func forceLaunchServicesRefresh(appPath string) error {
|
|
|
|
|
cmd := exec.Command("/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister", "-f", appPath)
|
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
|
|
|
return fmt.Errorf("forcing LaunchServices refresh: %w", err)
|
|
|
|
|
}
|
|
|
|
|
time.Sleep(2 * time.Second)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
Add multiple macOS FMAs (#37069)
This pull request adds support for several new macOS applications to the
maintained apps system. It introduces new input definitions, output
metadata, and installation/uninstallation scripts for each app, enabling
automated management and categorization. The changes primarily focus on
expanding the catalog with productivity, developer, and communication
tools.
**New App Integrations:**
* Added input definitions and output metadata for the following apps:
- Inkscape (vector graphics editor)
[[1]](diffhunk://#diff-7ce5d431eb69c5546afaa12ada343d8584e835e855ecebf4adc13801ab3eab78R1-R8)
[[2]](diffhunk://#diff-4c1446cfc02c6bb0bda874481e333c65b84e184fcea52f656b49a6489f73c9c2R697-R703)
[[3]](diffhunk://#diff-00054bf967e9164fbd87bfc1518f6332cbee50e85936c7e9617a0bfefe405fa4R1-R21)
- Jabra Direct (headset management)
[[1]](diffhunk://#diff-78c52106d0314ec88decc1b6e6490ea8519a45170df466789414c8eac90825f9R1-R8)
[[2]](diffhunk://#diff-4c1446cfc02c6bb0bda874481e333c65b84e184fcea52f656b49a6489f73c9c2R739-R745)
[[3]](diffhunk://#diff-cd97d46bd199c6376c16fe81366404c1a3010d3f75b8b8b9a611d354a9edc2cbR1-R21)
- Keeper Password Manager (password manager)
[[1]](diffhunk://#diff-a973ebde05953000c62f2492b17c079a9e552fd3bbab3b678ee2aa0757dafeadR1-R8)
[[2]](diffhunk://#diff-4c1446cfc02c6bb0bda874481e333c65b84e184fcea52f656b49a6489f73c9c2R767-R787)
[[3]](diffhunk://#diff-5b88b8712d6bf670a34dd05a496fa7a036b9f53dda262bea1f57deb9a10f6f6eR1-R21)
- Keka (file archiver)
[[1]](diffhunk://#diff-2c462b137233fc73d72dd6f42b4bc07e58d6396e6b178741d496536822f4e732R1-R8)
[[2]](diffhunk://#diff-4c1446cfc02c6bb0bda874481e333c65b84e184fcea52f656b49a6489f73c9c2R767-R787)
- Lens (Kubernetes IDE)
[[1]](diffhunk://#diff-51e6db269aa8b8fc295f8573eb1582adbb28bcf1f1fd040fe92f8e0e95c816b2R1-R8)
[[2]](diffhunk://#diff-4c1446cfc02c6bb0bda874481e333c65b84e184fcea52f656b49a6489f73c9c2R767-R787)
- Maccy (clipboard manager)
[[1]](diffhunk://#diff-a497d9aa8763fd1b331ce6d454994c1fc955e9fec54d996dae5134c2c64d5e2cR1-R8)
[[2]](diffhunk://#diff-4c1446cfc02c6bb0bda874481e333c65b84e184fcea52f656b49a6489f73c9c2R830-R843)
- Mattermost (open-source chat)
[[1]](diffhunk://#diff-7286f40f757bf5322974f664bb55fd6ef89ca486aacba5e524e3e4a7055f24bcR1-R8)
[[2]](diffhunk://#diff-4c1446cfc02c6bb0bda874481e333c65b84e184fcea52f656b49a6489f73c9c2R830-R843)
- MongoDB Compass (database GUI)
[[1]](diffhunk://#diff-c03bf76b1aa1a8a9a857a235e2eaa46a35c5d0a8b8331d5990251eadf1855568R1-R8)
[[2]](diffhunk://#diff-4c1446cfc02c6bb0bda874481e333c65b84e184fcea52f656b49a6489f73c9c2R921-R927)
**Installer and Uninstaller Scripts:**
* Added custom install and uninstall shell scripts for Inkscape, Jabra
Direct, and Keeper Password Manager to handle application lifecycle
tasks, including quitting running apps and cleaning up user data.
[[1]](diffhunk://#diff-00054bf967e9164fbd87bfc1518f6332cbee50e85936c7e9617a0bfefe405fa4R1-R21)
[[2]](diffhunk://#diff-cd97d46bd199c6376c16fe81366404c1a3010d3f75b8b8b9a611d354a9edc2cbR1-R21)
[[3]](diffhunk://#diff-5b88b8712d6bf670a34dd05a496fa7a036b9f53dda262bea1f57deb9a10f6f6eR1-R21)
**App Categorization:**
* Assigned appropriate default categories (e.g., Productivity, Developer
tools, Communication) to each new app for better organization and
filtering in the catalog.
[[1]](diffhunk://#diff-7ce5d431eb69c5546afaa12ada343d8584e835e855ecebf4adc13801ab3eab78R1-R8)
[[2]](diffhunk://#diff-78c52106d0314ec88decc1b6e6490ea8519a45170df466789414c8eac90825f9R1-R8)
[[3]](diffhunk://#diff-a973ebde05953000c62f2492b17c079a9e552fd3bbab3b678ee2aa0757dafeadR1-R8)
[[4]](diffhunk://#diff-2c462b137233fc73d72dd6f42b4bc07e58d6396e6b178741d496536822f4e732R1-R8)
[[5]](diffhunk://#diff-51e6db269aa8b8fc295f8573eb1582adbb28bcf1f1fd040fe92f8e0e95c816b2R1-R8)
[[6]](diffhunk://#diff-a497d9aa8763fd1b331ce6d454994c1fc955e9fec54d996dae5134c2c64d5e2cR1-R8)
[[7]](diffhunk://#diff-7286f40f757bf5322974f664bb55fd6ef89ca486aacba5e524e3e4a7055f24bcR1-R8)
[[8]](diffhunk://#diff-c03bf76b1aa1a8a9a857a235e2eaa46a35c5d0a8b8331d5990251eadf1855568R1-R8)
**Metadata and Version Tracking:**
* Introduced detailed metadata and version tracking for each app,
including installer URLs, bundle identifiers, SHA256 checksums, and SQL
queries for existence checks.
[[1]](diffhunk://#diff-00054bf967e9164fbd87bfc1518f6332cbee50e85936c7e9617a0bfefe405fa4R1-R21)
[[2]](diffhunk://#diff-cd97d46bd199c6376c16fe81366404c1a3010d3f75b8b8b9a611d354a9edc2cbR1-R21)
[[3]](diffhunk://#diff-5b88b8712d6bf670a34dd05a496fa7a036b9f53dda262bea1f57deb9a10f6f6eR1-R21)
**Catalog Expansion:**
* Updated the main `apps.json` output to include all new apps with
descriptions and platform information, ensuring visibility in the
maintained apps catalog.
[[1]](diffhunk://#diff-4c1446cfc02c6bb0bda874481e333c65b84e184fcea52f656b49a6489f73c9c2R697-R703)
[[2]](diffhunk://#diff-4c1446cfc02c6bb0bda874481e333c65b84e184fcea52f656b49a6489f73c9c2R739-R745)
[[3]](diffhunk://#diff-4c1446cfc02c6bb0bda874481e333c65b84e184fcea52f656b49a6489f73c9c2R767-R787)
[[4]](diffhunk://#diff-4c1446cfc02c6bb0bda874481e333c65b84e184fcea52f656b49a6489f73c9c2R830-R843)
[[5]](diffhunk://#diff-4c1446cfc02c6bb0bda874481e333c65b84e184fcea52f656b49a6489f73c9c2R921-R927)
2025-12-10 23:14:01 +00:00
|
|
|
// normalizeVersion removes common suffixes from version strings
|
|
|
|
|
func normalizeVersion(v string) string {
|
|
|
|
|
suffixes := []string{"-latest", "-beta", "-alpha", "-rc", "-pre"}
|
|
|
|
|
normalized := v
|
|
|
|
|
for _, suffix := range suffixes {
|
|
|
|
|
normalized = strings.TrimSuffix(normalized, suffix)
|
|
|
|
|
}
|
|
|
|
|
return normalized
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// checkVersionMatch checks if the expected version matches any of the found versions
|
|
|
|
|
// using various matching strategies: exact match, normalization, concatenation, and prefix matching
|
|
|
|
|
func checkVersionMatch(expectedVersion, foundVersion, foundBundledVersion string) bool {
|
|
|
|
|
// Check exact matches first (no normalization needed)
|
|
|
|
|
if expectedVersion == foundVersion || expectedVersion == foundBundledVersion {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Only normalize if exact match failed (lazy normalization)
|
|
|
|
|
normalizedExpected := normalizeVersion(expectedVersion)
|
|
|
|
|
normalizedFound := normalizeVersion(foundVersion)
|
|
|
|
|
normalizedBundled := normalizeVersion(foundBundledVersion)
|
|
|
|
|
|
|
|
|
|
// Check normalized exact matches
|
|
|
|
|
if normalizedExpected == normalizedFound || normalizedExpected == normalizedBundled {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if expected version is a concatenation of short version + bundled version
|
|
|
|
|
// This handles cases like "1.4.230579" = "1.4.2" + "30579" or "1.4.2.30579" = "1.4.2" + "." + "30579"
|
|
|
|
|
// Only check concatenation if the expected version is longer than the short version alone,
|
|
|
|
|
// which indicates it might be a concatenation (avoids false positives)
|
|
|
|
|
if foundVersion != "" && foundBundledVersion != "" && len(expectedVersion) > len(foundVersion) {
|
|
|
|
|
// Try direct concatenation (no separator)
|
|
|
|
|
concatenated := foundVersion + foundBundledVersion
|
|
|
|
|
if expectedVersion == concatenated {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
// Check normalized concatenation
|
|
|
|
|
normalizedConcatenated := normalizedFound + normalizedBundled
|
|
|
|
|
if normalizedExpected == normalizedConcatenated {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
// Try concatenation with dot separator
|
|
|
|
|
concatenatedWithDot := foundVersion + "." + foundBundledVersion
|
|
|
|
|
if expectedVersion == concatenatedWithDot {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
normalizedConcatenatedWithDot := normalizedFound + "." + normalizedBundled
|
|
|
|
|
if normalizedExpected == normalizedConcatenatedWithDot {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if found version starts with expected version (handles suffixes like ".CE")
|
|
|
|
|
// This handles cases where the app version is "8.0.44.CE" but expected is "8.0.44"
|
|
|
|
|
if strings.HasPrefix(foundVersion, expectedVersion+".") ||
|
|
|
|
|
strings.HasPrefix(foundBundledVersion, expectedVersion+".") {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
if strings.HasPrefix(normalizedFound, normalizedExpected+".") ||
|
|
|
|
|
strings.HasPrefix(normalizedBundled, normalizedExpected+".") {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if expected version starts with found version (handles cases where osquery reports shorter version)
|
|
|
|
|
// This handles cases where expected is "2025.2.1.8" but osquery reports "2025.2"
|
|
|
|
|
if strings.HasPrefix(expectedVersion, foundVersion+".") {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
if strings.HasPrefix(normalizedExpected, normalizedFound+".") {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
// Also check bundled version for prefix matches
|
|
|
|
|
if strings.HasPrefix(expectedVersion, foundBundledVersion+".") {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
if strings.HasPrefix(normalizedExpected, normalizedBundled+".") {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-31 20:23:36 +00:00
|
|
|
func appExists(ctx context.Context, logger kitlog.Logger, appName, uniqueAppIdentifier, appVersion, appPath string) (bool, error) {
|
|
|
|
|
execTimeout, cancel := context.WithTimeout(ctx, 30*time.Second)
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
|
|
if err := validateSqlInput(appName); err != nil {
|
|
|
|
|
return false, fmt.Errorf("Invalid character found in appName: '%w'. Not executing query...", err)
|
|
|
|
|
}
|
|
|
|
|
if err := validateSqlInput(uniqueAppIdentifier); err != nil {
|
|
|
|
|
return false, fmt.Errorf("Invalid character found in uniqueAppIdentifier: '%w'. Not executing query...", err)
|
|
|
|
|
}
|
|
|
|
|
if err := validateSqlInput(appPath); err != nil {
|
|
|
|
|
return false, fmt.Errorf("Invalid character found in appPath: '%w'. Not executing query...", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
level.Info(logger).Log("msg", fmt.Sprintf("Looking for app: %s, version: %s\n", appName, appVersion))
|
|
|
|
|
query := `
|
|
|
|
|
SELECT
|
|
|
|
|
COALESCE(NULLIF(display_name, ''), NULLIF(bundle_name, ''), NULLIF(bundle_executable, ''), TRIM(name, '.app') ) AS name,
|
|
|
|
|
path,
|
|
|
|
|
bundle_short_version,
|
|
|
|
|
bundle_version
|
|
|
|
|
FROM apps
|
|
|
|
|
WHERE
|
|
|
|
|
bundle_identifier LIKE '%` + uniqueAppIdentifier + `%' OR
|
|
|
|
|
LOWER(COALESCE(NULLIF(display_name, ''), NULLIF(bundle_name, ''), NULLIF(bundle_executable, ''), TRIM(name, '.app'))) LIKE LOWER('%` + appName + `%')
|
|
|
|
|
`
|
|
|
|
|
if appPath != "" {
|
|
|
|
|
query += fmt.Sprintf(" OR path LIKE '%%%s%%'", appPath)
|
|
|
|
|
}
|
|
|
|
|
cmd := exec.CommandContext(execTimeout, "osqueryi", "--json", query)
|
|
|
|
|
output, err := cmd.Output()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return false, fmt.Errorf("executing osquery command: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type AppResult struct {
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
Path string `json:"path"`
|
|
|
|
|
Version string `json:"bundle_short_version"`
|
|
|
|
|
BundledVersion string `json:"bundle_version"`
|
|
|
|
|
}
|
|
|
|
|
var results []AppResult
|
|
|
|
|
if err := json.Unmarshal(output, &results); err != nil {
|
|
|
|
|
return false, fmt.Errorf("parsing osquery JSON output: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(results) > 0 {
|
|
|
|
|
for _, result := range results {
|
|
|
|
|
software := &fleet.Software{
|
|
|
|
|
Name: result.Name,
|
|
|
|
|
Version: result.Version,
|
|
|
|
|
BundleIdentifier: uniqueAppIdentifier,
|
|
|
|
|
Source: "apps",
|
|
|
|
|
}
|
|
|
|
|
queries.MutateSoftwareOnIngestion(software, logger)
|
|
|
|
|
result.Version = software.Version
|
|
|
|
|
result.Name = software.Name
|
|
|
|
|
|
|
|
|
|
level.Info(logger).Log("msg", fmt.Sprintf("Found app: '%s' at %s, Version: %s, Bundled Version: %s", result.Name, result.Path, result.Version, result.BundledVersion))
|
2025-11-26 02:25:23 +00:00
|
|
|
|
|
|
|
|
// OneDrive auto-updates immediately after installation, so the installed version
|
|
|
|
|
// might be newer than the installer version. For OneDrive, we only verify that
|
|
|
|
|
// the app exists rather than checking the version.
|
|
|
|
|
if uniqueAppIdentifier == "com.microsoft.OneDrive" {
|
|
|
|
|
level.Info(logger).Log("msg", "OneDrive detected - skipping version check due to auto-update behavior")
|
|
|
|
|
return true, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-08 15:45:53 +00:00
|
|
|
// GPG Suite's installer version (e.g., "2023.3") doesn't match the app bundle version
|
|
|
|
|
// (e.g., "1.12" with bundled version "1800"). We only verify that the app exists
|
|
|
|
|
// rather than checking the version.
|
|
|
|
|
if uniqueAppIdentifier == "org.gpgtools.gpgkeychain" {
|
|
|
|
|
level.Info(logger).Log("msg", "GPG Suite detected - skipping version check due to version mismatch between installer and app bundle")
|
|
|
|
|
return true, nil
|
|
|
|
|
}
|
|
|
|
|
|
Add Adobe DNG Converter as a macOS FMA (#37238)
This pull request adds support for managing Adobe DNG Converter on
macOS. The main changes include introducing metadata and
installation/uninstallation scripts for this application, updating the
app registry, and improving version detection logic to handle Adobe’s
unique versioning format.
**Support for Adobe DNG Converter:**
* Added a new input definition for Adobe DNG Converter in
`homebrew/adobe-dng-converter.json`, specifying its identifiers,
installer format, and default category.
* Generated an output manifest in
`outputs/adobe-dng-converter/darwin.json` with version info, download
URL, SHA256, and detailed install/uninstall scripts for managing the app
lifecycle.
* Registered Adobe DNG Converter in the main `apps.json` registry,
including a description, unique identifier, and platform.
**Improvements to version detection:**
* Updated the `appExists` function in `darwin.go` to correctly detect
Adobe DNG Converter even when the installed version string includes a
build number in parentheses, improving reliability of version checks.
2025-12-15 17:12:11 +00:00
|
|
|
// Adobe DNG Converter's version format includes build number in parentheses
|
|
|
|
|
// (e.g., "18.0 (2389)") which doesn't match the installer version (e.g., "18.0")
|
|
|
|
|
// Check if the version starts with the expected version to handle this case
|
|
|
|
|
if uniqueAppIdentifier == "com.adobe.DNGConverter" {
|
|
|
|
|
if strings.HasPrefix(result.Version, appVersion+" ") || strings.HasPrefix(result.Version, appVersion+"(") {
|
|
|
|
|
level.Info(logger).Log("msg", "Adobe DNG Converter detected - version matches with build number")
|
|
|
|
|
return true, nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Add multiple macOS FMAs (#37069)
This pull request adds support for several new macOS applications to the
maintained apps system. It introduces new input definitions, output
metadata, and installation/uninstallation scripts for each app, enabling
automated management and categorization. The changes primarily focus on
expanding the catalog with productivity, developer, and communication
tools.
**New App Integrations:**
* Added input definitions and output metadata for the following apps:
- Inkscape (vector graphics editor)
[[1]](diffhunk://#diff-7ce5d431eb69c5546afaa12ada343d8584e835e855ecebf4adc13801ab3eab78R1-R8)
[[2]](diffhunk://#diff-4c1446cfc02c6bb0bda874481e333c65b84e184fcea52f656b49a6489f73c9c2R697-R703)
[[3]](diffhunk://#diff-00054bf967e9164fbd87bfc1518f6332cbee50e85936c7e9617a0bfefe405fa4R1-R21)
- Jabra Direct (headset management)
[[1]](diffhunk://#diff-78c52106d0314ec88decc1b6e6490ea8519a45170df466789414c8eac90825f9R1-R8)
[[2]](diffhunk://#diff-4c1446cfc02c6bb0bda874481e333c65b84e184fcea52f656b49a6489f73c9c2R739-R745)
[[3]](diffhunk://#diff-cd97d46bd199c6376c16fe81366404c1a3010d3f75b8b8b9a611d354a9edc2cbR1-R21)
- Keeper Password Manager (password manager)
[[1]](diffhunk://#diff-a973ebde05953000c62f2492b17c079a9e552fd3bbab3b678ee2aa0757dafeadR1-R8)
[[2]](diffhunk://#diff-4c1446cfc02c6bb0bda874481e333c65b84e184fcea52f656b49a6489f73c9c2R767-R787)
[[3]](diffhunk://#diff-5b88b8712d6bf670a34dd05a496fa7a036b9f53dda262bea1f57deb9a10f6f6eR1-R21)
- Keka (file archiver)
[[1]](diffhunk://#diff-2c462b137233fc73d72dd6f42b4bc07e58d6396e6b178741d496536822f4e732R1-R8)
[[2]](diffhunk://#diff-4c1446cfc02c6bb0bda874481e333c65b84e184fcea52f656b49a6489f73c9c2R767-R787)
- Lens (Kubernetes IDE)
[[1]](diffhunk://#diff-51e6db269aa8b8fc295f8573eb1582adbb28bcf1f1fd040fe92f8e0e95c816b2R1-R8)
[[2]](diffhunk://#diff-4c1446cfc02c6bb0bda874481e333c65b84e184fcea52f656b49a6489f73c9c2R767-R787)
- Maccy (clipboard manager)
[[1]](diffhunk://#diff-a497d9aa8763fd1b331ce6d454994c1fc955e9fec54d996dae5134c2c64d5e2cR1-R8)
[[2]](diffhunk://#diff-4c1446cfc02c6bb0bda874481e333c65b84e184fcea52f656b49a6489f73c9c2R830-R843)
- Mattermost (open-source chat)
[[1]](diffhunk://#diff-7286f40f757bf5322974f664bb55fd6ef89ca486aacba5e524e3e4a7055f24bcR1-R8)
[[2]](diffhunk://#diff-4c1446cfc02c6bb0bda874481e333c65b84e184fcea52f656b49a6489f73c9c2R830-R843)
- MongoDB Compass (database GUI)
[[1]](diffhunk://#diff-c03bf76b1aa1a8a9a857a235e2eaa46a35c5d0a8b8331d5990251eadf1855568R1-R8)
[[2]](diffhunk://#diff-4c1446cfc02c6bb0bda874481e333c65b84e184fcea52f656b49a6489f73c9c2R921-R927)
**Installer and Uninstaller Scripts:**
* Added custom install and uninstall shell scripts for Inkscape, Jabra
Direct, and Keeper Password Manager to handle application lifecycle
tasks, including quitting running apps and cleaning up user data.
[[1]](diffhunk://#diff-00054bf967e9164fbd87bfc1518f6332cbee50e85936c7e9617a0bfefe405fa4R1-R21)
[[2]](diffhunk://#diff-cd97d46bd199c6376c16fe81366404c1a3010d3f75b8b8b9a611d354a9edc2cbR1-R21)
[[3]](diffhunk://#diff-5b88b8712d6bf670a34dd05a496fa7a036b9f53dda262bea1f57deb9a10f6f6eR1-R21)
**App Categorization:**
* Assigned appropriate default categories (e.g., Productivity, Developer
tools, Communication) to each new app for better organization and
filtering in the catalog.
[[1]](diffhunk://#diff-7ce5d431eb69c5546afaa12ada343d8584e835e855ecebf4adc13801ab3eab78R1-R8)
[[2]](diffhunk://#diff-78c52106d0314ec88decc1b6e6490ea8519a45170df466789414c8eac90825f9R1-R8)
[[3]](diffhunk://#diff-a973ebde05953000c62f2492b17c079a9e552fd3bbab3b678ee2aa0757dafeadR1-R8)
[[4]](diffhunk://#diff-2c462b137233fc73d72dd6f42b4bc07e58d6396e6b178741d496536822f4e732R1-R8)
[[5]](diffhunk://#diff-51e6db269aa8b8fc295f8573eb1582adbb28bcf1f1fd040fe92f8e0e95c816b2R1-R8)
[[6]](diffhunk://#diff-a497d9aa8763fd1b331ce6d454994c1fc955e9fec54d996dae5134c2c64d5e2cR1-R8)
[[7]](diffhunk://#diff-7286f40f757bf5322974f664bb55fd6ef89ca486aacba5e524e3e4a7055f24bcR1-R8)
[[8]](diffhunk://#diff-c03bf76b1aa1a8a9a857a235e2eaa46a35c5d0a8b8331d5990251eadf1855568R1-R8)
**Metadata and Version Tracking:**
* Introduced detailed metadata and version tracking for each app,
including installer URLs, bundle identifiers, SHA256 checksums, and SQL
queries for existence checks.
[[1]](diffhunk://#diff-00054bf967e9164fbd87bfc1518f6332cbee50e85936c7e9617a0bfefe405fa4R1-R21)
[[2]](diffhunk://#diff-cd97d46bd199c6376c16fe81366404c1a3010d3f75b8b8b9a611d354a9edc2cbR1-R21)
[[3]](diffhunk://#diff-5b88b8712d6bf670a34dd05a496fa7a036b9f53dda262bea1f57deb9a10f6f6eR1-R21)
**Catalog Expansion:**
* Updated the main `apps.json` output to include all new apps with
descriptions and platform information, ensuring visibility in the
maintained apps catalog.
[[1]](diffhunk://#diff-4c1446cfc02c6bb0bda874481e333c65b84e184fcea52f656b49a6489f73c9c2R697-R703)
[[2]](diffhunk://#diff-4c1446cfc02c6bb0bda874481e333c65b84e184fcea52f656b49a6489f73c9c2R739-R745)
[[3]](diffhunk://#diff-4c1446cfc02c6bb0bda874481e333c65b84e184fcea52f656b49a6489f73c9c2R767-R787)
[[4]](diffhunk://#diff-4c1446cfc02c6bb0bda874481e333c65b84e184fcea52f656b49a6489f73c9c2R830-R843)
[[5]](diffhunk://#diff-4c1446cfc02c6bb0bda874481e333c65b84e184fcea52f656b49a6489f73c9c2R921-R927)
2025-12-10 23:14:01 +00:00
|
|
|
// Check various version matching strategies
|
|
|
|
|
if checkVersionMatch(appVersion, result.Version, result.BundledVersion) {
|
2025-12-01 16:03:24 +00:00
|
|
|
return true, nil
|
|
|
|
|
}
|
2025-07-31 20:23:36 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func executeScript(cfg *Config, scriptContents string) (string, error) {
|
|
|
|
|
scriptExtension := ".sh"
|
|
|
|
|
scriptPath := filepath.Join(cfg.tmpDir, "script"+scriptExtension)
|
|
|
|
|
if err := os.WriteFile(scriptPath, []byte(scriptContents), constant.DefaultFileMode); err != nil {
|
|
|
|
|
return "", fmt.Errorf("writing script: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
|
|
output, exitCode, err := scripts.ExecCmd(ctx, scriptPath, cfg.env)
|
|
|
|
|
result := fmt.Sprintf(`
|
|
|
|
|
--------------------
|
|
|
|
|
%s
|
|
|
|
|
--------------------`, string(output))
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
return result, err
|
|
|
|
|
}
|
|
|
|
|
if exitCode != 0 {
|
|
|
|
|
return result, fmt.Errorf("script execution failed with exit code %d: %s", exitCode, string(output))
|
|
|
|
|
}
|
|
|
|
|
return result, nil
|
|
|
|
|
}
|