From d17b10b9d5c87fc42490074dda56626536331fc2 Mon Sep 17 00:00:00 2001 From: Mitch Francese <2227948+tux234@users.noreply.github.com> Date: Thu, 18 Jun 2026 16:02:40 -0400 Subject: [PATCH] Add Vivaldi as a Fleet-maintained app (#41552) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Related issue:** N/A — adds Vivaldi to the Fleet-maintained apps catalog. ## Summary Adds **Vivaldi** as a Fleet-maintained app for **macOS** and **Windows**. - **macOS (`vivaldi/darwin`)** — Homebrew cask input plus a `VivaldiDMGInstaller` enricher that redirects Homebrew's unsupported `tar.xz` download to Vivaldi's direct universal DMG (`https://downloads.vivaldi.com/stable/Vivaldi.{version}.universal.dmg`), keeping `installer_format: dmg` so the standard `hdiutil`-based install/uninstall scripts are generated. `unique_identifier` is the verified bundle id `com.vivaldi.Vivaldi`. - **Windows (`vivaldi/windows`)** — winget input (`Vivaldi.Vivaldi`) with custom install/uninstall scripts. Installs **machine-wide** with `--vivaldi-silent --do-not-launch-chrome --system-level`. Because Fleet runs installers as `SYSTEM`, the `--system-level` flag is required — without it the Chromium-based installer lands in the `SYSTEM` profile and is invisible to the real user. The exists query matches the registry `DisplayName` `Vivaldi` and verified `Publisher` `Vivaldi Technologies AS.`; the uninstall script looks up the entry under HKLM (with an HKCU fallback) and runs the Chromium uninstaller with `--force-uninstall`. Outputs were regenerated via the ingester (`go run cmd/maintained-apps/main.go --slug=...`) at Vivaldi **8.0.4033.46**; script refs are content-addressed, not hand-edited. ## Changes - `ee/maintained-apps/inputs/homebrew/vivaldi.json`, `ingesters/homebrew/external_refs/vivaldi.go` (+ registration in `main.go`) — macOS input and DMG-URL enricher - `ee/maintained-apps/inputs/winget/vivaldi.json` + `scripts/vivaldi_install.ps1` / `vivaldi_uninstall.ps1` — Windows input and custom machine-wide install/uninstall - `ee/maintained-apps/outputs/vivaldi/darwin.json`, `vivaldi/windows.json`, `outputs/apps.json` — generated catalog entries (with descriptions) - `frontend/pages/SoftwarePage/components/icons/Vivaldi.tsx` + `index.ts`, `website/assets/images/app-icon-vivaldi-60x60@2x.png` — UI/website icon # Checklist for submitter - [x] Input data is properly validated; untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. (Silent-install flags are documented Vivaldi switches; the uninstall script parses the registry UninstallString defensively.) ## Testing - [x] Regenerated both outputs via the ingester; verified `installer_url`, SHA, and non-empty install/uninstall script refs. - [x] Verified macOS `unique_identifier` (`com.vivaldi.Vivaldi`) and Windows identity (DisplayName `Vivaldi`, Publisher `Vivaldi Technologies AS.`) against the cask/winget manifests. - [x] `apps.json` validates as JSON; `go build ./cmd/maintained-apps/...` and `GOOS=windows go build ./cmd/maintained-apps/validate/` pass. - [ ] QA'd all new/changed functionality manually (via the FMA validator on macOS + Windows hosts). ## Summary by CodeRabbit * **New Features** * Added support for Vivaldi browser across macOS (Homebrew) and Windows (Winget) platforms with version management capabilities. * Included automated installation and uninstallation scripts for both operating systems. * Added Vivaldi icon to the application interface. --------- Co-authored-by: Allen Houchins <32207388+allenhouchins@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../ingesters/homebrew/external_refs/main.go | 1 + .../homebrew/external_refs/vivaldi.go | 16 ++++ .../inputs/homebrew/vivaldi.json | 8 ++ .../inputs/winget/scripts/vivaldi_install.ps1 | 8 ++ .../winget/scripts/vivaldi_uninstall.ps1 | 74 ++++++++++++++++++ ee/maintained-apps/inputs/winget/vivaldi.json | 12 +++ ee/maintained-apps/outputs/apps.json | 14 ++++ .../outputs/vivaldi/darwin.json | 22 ++++++ .../outputs/vivaldi/windows.json | 22 ++++++ .../SoftwarePage/components/icons/Vivaldi.tsx | 14 ++++ .../SoftwarePage/components/icons/index.ts | 2 + .../images/app-icon-vivaldi-60x60@2x.png | Bin 0 -> 18827 bytes 12 files changed, 193 insertions(+) create mode 100644 ee/maintained-apps/ingesters/homebrew/external_refs/vivaldi.go create mode 100644 ee/maintained-apps/inputs/homebrew/vivaldi.json create mode 100644 ee/maintained-apps/inputs/winget/scripts/vivaldi_install.ps1 create mode 100644 ee/maintained-apps/inputs/winget/scripts/vivaldi_uninstall.ps1 create mode 100644 ee/maintained-apps/inputs/winget/vivaldi.json create mode 100644 ee/maintained-apps/outputs/vivaldi/darwin.json create mode 100644 ee/maintained-apps/outputs/vivaldi/windows.json create mode 100644 frontend/pages/SoftwarePage/components/icons/Vivaldi.tsx create mode 100644 website/assets/images/app-icon-vivaldi-60x60@2x.png diff --git a/ee/maintained-apps/ingesters/homebrew/external_refs/main.go b/ee/maintained-apps/ingesters/homebrew/external_refs/main.go index 82653fc68a..411cbb9a2e 100644 --- a/ee/maintained-apps/ingesters/homebrew/external_refs/main.go +++ b/ee/maintained-apps/ingesters/homebrew/external_refs/main.go @@ -25,6 +25,7 @@ var Funcs = map[string][]func(*maintained_apps.FMAManifestApp) (*maintained_apps "parallels/darwin": {ParallelsVersionShortener}, "github/darwin": {GitHubDesktopVersionShortener}, "camtasia/darwin": {CamtasiaVersionTransformer}, + "vivaldi/darwin": {VivaldiDMGInstaller}, "warp/darwin": {WarpDirectInstaller}, "android-studio/darwin": {AndroidStudioVersionShortener}, "microsoft-auto-update/darwin": {MicrosoftAutoUpdateVersionShortener}, diff --git a/ee/maintained-apps/ingesters/homebrew/external_refs/vivaldi.go b/ee/maintained-apps/ingesters/homebrew/external_refs/vivaldi.go new file mode 100644 index 0000000000..ba135dcc59 --- /dev/null +++ b/ee/maintained-apps/ingesters/homebrew/external_refs/vivaldi.go @@ -0,0 +1,16 @@ +package externalrefs + +import ( + maintained_apps "github.com/fleetdm/fleet/v4/ee/maintained-apps" +) + +func VivaldiDMGInstaller(app *maintained_apps.FMAManifestApp) (*maintained_apps.FMAManifestApp, error) { + // Override installer URL to use Vivaldi's direct DMG instead of Homebrew's tar.xz + // The tar.xz format is not supported by the Fleet ingester's install script generator. + // Version is kept from Homebrew (not set to "latest") + app.InstallerURL = "https://downloads.vivaldi.com/stable/Vivaldi." + app.Version + ".universal.dmg" + // Set SHA256 to "no_check" since we're using a different installer URL than Homebrew + app.SHA256 = "no_check" + + return app, nil +} diff --git a/ee/maintained-apps/inputs/homebrew/vivaldi.json b/ee/maintained-apps/inputs/homebrew/vivaldi.json new file mode 100644 index 0000000000..5b758687f7 --- /dev/null +++ b/ee/maintained-apps/inputs/homebrew/vivaldi.json @@ -0,0 +1,8 @@ +{ + "name": "Vivaldi", + "slug": "vivaldi/darwin", + "unique_identifier": "com.vivaldi.Vivaldi", + "token": "vivaldi", + "installer_format": "dmg", + "default_categories": ["Browsers"] +} diff --git a/ee/maintained-apps/inputs/winget/scripts/vivaldi_install.ps1 b/ee/maintained-apps/inputs/winget/scripts/vivaldi_install.ps1 new file mode 100644 index 0000000000..6205f13747 --- /dev/null +++ b/ee/maintained-apps/inputs/winget/scripts/vivaldi_install.ps1 @@ -0,0 +1,8 @@ +# Install Vivaldi silently, machine-wide (Chromium-based browser). +# Fleet runs installs as SYSTEM, so --system-level is required to install for +# all users under %ProgramFiles% (and register under HKLM). Without it the +# installer lands in the SYSTEM profile and is invisible to the real user. +$process = Start-Process -FilePath $env:INSTALLER_PATH ` + -ArgumentList "--vivaldi-silent --do-not-launch-chrome --system-level" ` + -NoNewWindow -PassThru -Wait +Exit $process.ExitCode diff --git a/ee/maintained-apps/inputs/winget/scripts/vivaldi_uninstall.ps1 b/ee/maintained-apps/inputs/winget/scripts/vivaldi_uninstall.ps1 new file mode 100644 index 0000000000..58ef2671b6 --- /dev/null +++ b/ee/maintained-apps/inputs/winget/scripts/vivaldi_uninstall.ps1 @@ -0,0 +1,74 @@ +# Uninstall Vivaldi (machine-wide Chromium-based browser). +# Looks up the uninstall entry under HKLM (machine install) with an HKCU +# fallback, then runs the Chromium uninstaller with --force-uninstall. + +$displayName = "Vivaldi" +$publisher = "Vivaldi Technologies AS." + +# Install is machine-wide (--system-level), which registers under HKLM, so look +# there first. HKCU is only a fallback for a stale user-level install. +$paths = @( + 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall', + 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall', + 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall' +) + +$uninstall = $null +foreach ($p in $paths) { + $items = Get-ItemProperty "$p\*" -ErrorAction SilentlyContinue | Where-Object { + $_.DisplayName -eq $displayName -and $_.Publisher -eq $publisher + } + if ($items) { $uninstall = $items | Select-Object -First 1; break } +} + +if (-not $uninstall -or (-not $uninstall.UninstallString -and -not $uninstall.QuietUninstallString)) { + Write-Host "Uninstall entry not found for '$displayName'" + Exit 0 +} + +# Kill any running Vivaldi processes before uninstalling +Stop-Process -Name "vivaldi" -Force -ErrorAction SilentlyContinue +Start-Sleep -Seconds 2 + +$uninstallCommand = if ($uninstall.QuietUninstallString) { + $uninstall.QuietUninstallString +} else { + $uninstall.UninstallString +} + +# Parse the executable + trailing args, handling the three registry shapes: +# quoted, unquoted-with-spaces (capture through .exe), and a bare token. +if ($uninstallCommand -match '^\s*"([^"]+)"\s*(.*)$') { + $exe = $Matches[1] + $existingArgs = $Matches[2].Trim() +} elseif ($uninstallCommand -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { + $exe = $Matches[1] + $existingArgs = $Matches[2].Trim() +} elseif ($uninstallCommand -match '^\s*(\S+)\s*(.*)$') { + $exe = $Matches[1] + $existingArgs = $Matches[2].Trim() +} else { + Write-Host "Unable to parse uninstall command: $uninstallCommand" + Exit 1 +} + +# Chromium-based uninstaller flags +$uninstallArgs = "$existingArgs --uninstall --force-uninstall".Trim() + +Write-Host "Uninstall command: $exe" +Write-Host "Uninstall args: $uninstallArgs" + +try { + $process = Start-Process -FilePath $exe -ArgumentList $uninstallArgs -NoNewWindow -PassThru -Wait + $exitCode = $process.ExitCode + Write-Host "Uninstall exit code: $exitCode" + + # Chromium uninstallers return 19 on success + if ($exitCode -eq 0 -or $exitCode -eq 19) { + Exit 0 + } + Exit $exitCode +} catch { + Write-Host "Error running uninstaller: $_" + Exit 1 +} diff --git a/ee/maintained-apps/inputs/winget/vivaldi.json b/ee/maintained-apps/inputs/winget/vivaldi.json new file mode 100644 index 0000000000..17aa4d0aef --- /dev/null +++ b/ee/maintained-apps/inputs/winget/vivaldi.json @@ -0,0 +1,12 @@ +{ + "name": "Vivaldi", + "slug": "vivaldi/windows", + "package_identifier": "Vivaldi.Vivaldi", + "unique_identifier": "Vivaldi", + "install_script_path": "ee/maintained-apps/inputs/winget/scripts/vivaldi_install.ps1", + "uninstall_script_path": "ee/maintained-apps/inputs/winget/scripts/vivaldi_uninstall.ps1", + "installer_arch": "x64", + "installer_type": "exe", + "installer_scope": "machine", + "default_categories": ["Browsers"] +} diff --git a/ee/maintained-apps/outputs/apps.json b/ee/maintained-apps/outputs/apps.json index 31d93cba6c..5073f1e029 100644 --- a/ee/maintained-apps/outputs/apps.json +++ b/ee/maintained-apps/outputs/apps.json @@ -8114,6 +8114,20 @@ "unique_identifier": "Microsoft Visual Studio Code", "description": "Microsoft Visual Studio Code (VS Code) is an open-source, lightweight, and powerful code editor." }, + { + "name": "Vivaldi", + "slug": "vivaldi/darwin", + "platform": "darwin", + "unique_identifier": "com.vivaldi.Vivaldi", + "description": "Vivaldi is a customizable, privacy-focused web browser built on Chromium." + }, + { + "name": "Vivaldi", + "slug": "vivaldi/windows", + "platform": "windows", + "unique_identifier": "Vivaldi", + "description": "Vivaldi is a customizable, privacy-focused web browser built on Chromium." + }, { "name": "Vivid", "slug": "vivid-app/darwin", diff --git a/ee/maintained-apps/outputs/vivaldi/darwin.json b/ee/maintained-apps/outputs/vivaldi/darwin.json new file mode 100644 index 0000000000..7a919d43ed --- /dev/null +++ b/ee/maintained-apps/outputs/vivaldi/darwin.json @@ -0,0 +1,22 @@ +{ + "versions": [ + { + "version": "8.0.4033.46", + "queries": { + "exists": "SELECT 1 FROM apps WHERE bundle_identifier = 'com.vivaldi.Vivaldi';", + "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'com.vivaldi.Vivaldi' AND version_compare(bundle_short_version, '8.0.4033.46') < 0);" + }, + "installer_url": "https://downloads.vivaldi.com/stable/Vivaldi.8.0.4033.46.universal.dmg", + "install_script_ref": "6a60dca7", + "uninstall_script_ref": "7c1876ca", + "sha256": "no_check", + "default_categories": [ + "Browsers" + ] + } + ], + "refs": { + "6a60dca7": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nTMPDIR=$(dirname \"$(realpath \"$INSTALLER_PATH\")\")\n# functions\n\nquit_and_track_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n eval \"export $var_name=0\"\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n eval \"export $var_name=0\"\n return\n fi\n\n # App was running, mark it for relaunch\n eval \"export $var_name=1\"\n echo \"Application '$bundle_id' was running; will relaunch after installation.\"\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nrelaunch_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local was_running\n\n # Check if the app was running before installation\n eval \"was_running=\\$$var_name\"\n if [[ \"$was_running\" != \"1\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping relaunching application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Relaunching application '$bundle_id'...\"\n\n # Launch the app in the logged-in user's GUI session. Apps launched by root\n # won't register with the user's Dock/GUI, so run 'open' as the console user.\n # Use 'launchctl asuser' to bootstrap into the console user's Mach namespace\n # and GUI session — 'sudo -u' alone doesn't do this, which can cause\n # LSOpenURLsWithRole() failures even when 'open' exits 0.\n local open_status=0\n if [[ $EUID -eq 0 ]]; then\n local console_uid\n console_uid=$(id -u \"$console_user\")\n /bin/launchctl asuser \"$console_uid\" sudo -u \"$console_user\" open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n else\n open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n fi\n\n if [[ $open_status -eq 0 ]]; then\n echo \"Application '$bundle_id' relaunched successfully.\"\n else\n echo \"Failed to relaunch application '$bundle_id'.\"\n fi\n}\n\n\n# extract contents\nMOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX)\nyes | hdiutil attach -plist -nobrowse -readonly -mountpoint \"$MOUNT_POINT\" \"$INSTALLER_PATH\" || exit 1\nsudo cp -R \"$MOUNT_POINT\"/* \"$TMPDIR\"\nhdiutil detach \"$MOUNT_POINT\" || true\n# copy to the applications folder\nquit_and_track_application 'com.vivaldi.Vivaldi'\nif [ -d \"$APPDIR/Vivaldi.app\" ]; then\n\tsudo mv \"$APPDIR/Vivaldi.app\" \"$TMPDIR/Vivaldi.app.bkp\"\nfi\nsudo cp -R \"$TMPDIR/Vivaldi.app\" \"$APPDIR\"\nrelaunch_application 'com.vivaldi.Vivaldi'\n", + "7c1876ca": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nquit_application 'com.vivaldi.Vivaldi'\nsudo rm -rf \"$APPDIR/Vivaldi.app\"\ntrash $LOGGED_IN_USER '/Library/Logs/DiagnosticReports/Vivaldi Helper (Renderer)_*.diag'\ntrash $LOGGED_IN_USER '/Library/Logs/DiagnosticReports/Vivaldi_*.diag'\ntrash $LOGGED_IN_USER '~/Library/Application Support/CrashReporter/Vivaldi_*.plist'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Vivaldi'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.vivaldi.Vivaldi'\ntrash $LOGGED_IN_USER '~/Library/Caches/Vivaldi'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.vivaldi.Vivaldi'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.vivaldi.Vivaldi.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.vivaldi.Vivaldi.savedState'\ntrash $LOGGED_IN_USER '~/Library/WebKit/com.vivaldi.Vivaldi'\n" + } +} diff --git a/ee/maintained-apps/outputs/vivaldi/windows.json b/ee/maintained-apps/outputs/vivaldi/windows.json new file mode 100644 index 0000000000..b24a0b1bc7 --- /dev/null +++ b/ee/maintained-apps/outputs/vivaldi/windows.json @@ -0,0 +1,22 @@ +{ + "versions": [ + { + "version": "8.0.4033.46", + "queries": { + "exists": "SELECT 1 FROM programs WHERE name = 'Vivaldi' AND publisher = 'Vivaldi Technologies AS.';", + "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name = 'Vivaldi' AND publisher = 'Vivaldi Technologies AS.' AND version_compare(version, '8.0.4033.46') < 0);" + }, + "installer_url": "https://downloads.vivaldi.com/stable/Vivaldi.8.0.4033.46.x64.exe", + "install_script_ref": "d5d49757", + "uninstall_script_ref": "7b14fec4", + "sha256": "35ab32e990973e1896df8587f68b480fb4d89512030e9228d0ec39b3cda1b958", + "default_categories": [ + "Browsers" + ] + } + ], + "refs": { + "7b14fec4": "# Uninstall Vivaldi (machine-wide Chromium-based browser).\n# Looks up the uninstall entry under HKLM (machine install) with an HKCU\n# fallback, then runs the Chromium uninstaller with --force-uninstall.\n\n$displayName = \"Vivaldi\"\n$publisher = \"Vivaldi Technologies AS.\"\n\n# Install is machine-wide (--system-level), which registers under HKLM, so look\n# there first. HKCU is only a fallback for a stale user-level install.\n$paths = @(\n 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall',\n 'HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall',\n 'HKCU:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall'\n)\n\n$uninstall = $null\nforeach ($p in $paths) {\n $items = Get-ItemProperty \"$p\\*\" -ErrorAction SilentlyContinue | Where-Object {\n $_.DisplayName -eq $displayName -and $_.Publisher -eq $publisher\n }\n if ($items) { $uninstall = $items | Select-Object -First 1; break }\n}\n\nif (-not $uninstall -or (-not $uninstall.UninstallString -and -not $uninstall.QuietUninstallString)) {\n Write-Host \"Uninstall entry not found for '$displayName'\"\n Exit 1\n}\n\n# Kill any running Vivaldi processes before uninstalling\nStop-Process -Name \"vivaldi\" -Force -ErrorAction SilentlyContinue\nStart-Sleep -Seconds 2\n\n$uninstallCommand = if ($uninstall.QuietUninstallString) {\n $uninstall.QuietUninstallString\n} else {\n $uninstall.UninstallString\n}\n\n# Parse the executable + trailing args, handling the three registry shapes:\n# quoted, unquoted-with-spaces (capture through .exe), and a bare token.\nif ($uninstallCommand -match '^\\s*\"([^\"]+)\"\\s*(.*)$') {\n $exe = $Matches[1]\n $existingArgs = $Matches[2].Trim()\n} elseif ($uninstallCommand -match '(?i)^\\s*(.+?\\.exe)\\s*(.*)$') {\n $exe = $Matches[1]\n $existingArgs = $Matches[2].Trim()\n} elseif ($uninstallCommand -match '^\\s*(\\S+)\\s*(.*)$') {\n $exe = $Matches[1]\n $existingArgs = $Matches[2].Trim()\n} else {\n Write-Host \"Unable to parse uninstall command: $uninstallCommand\"\n Exit 1\n}\n\n# Chromium-based uninstaller flags\n$uninstallArgs = \"$existingArgs --uninstall --force-uninstall\".Trim()\n\nWrite-Host \"Uninstall command: $exe\"\nWrite-Host \"Uninstall args: $uninstallArgs\"\n\ntry {\n $process = Start-Process -FilePath $exe -ArgumentList $uninstallArgs -NoNewWindow -PassThru -Wait\n $exitCode = $process.ExitCode\n Write-Host \"Uninstall exit code: $exitCode\"\n\n # Chromium uninstallers return 19 on success\n if ($exitCode -eq 0 -or $exitCode -eq 19) {\n Exit 0\n }\n Exit $exitCode\n} catch {\n Write-Host \"Error running uninstaller: $_\"\n Exit 1\n}\n", + "d5d49757": "# Install Vivaldi silently, machine-wide (Chromium-based browser).\n# Fleet runs installs as SYSTEM, so --system-level is required to install for\n# all users under %ProgramFiles% (and register under HKLM). Without it the\n# installer lands in the SYSTEM profile and is invisible to the real user.\n$process = Start-Process -FilePath $env:INSTALLER_PATH `\n -ArgumentList \"--vivaldi-silent --do-not-launch-chrome --system-level\" `\n -NoNewWindow -PassThru -Wait\nExit $process.ExitCode\n" + } +} diff --git a/frontend/pages/SoftwarePage/components/icons/Vivaldi.tsx b/frontend/pages/SoftwarePage/components/icons/Vivaldi.tsx new file mode 100644 index 0000000000..65911b8b45 --- /dev/null +++ b/frontend/pages/SoftwarePage/components/icons/Vivaldi.tsx @@ -0,0 +1,14 @@ +import * as React from "react"; + +import type { SVGProps } from "react"; + +const Vivaldi = (props: SVGProps) => ( + + + +); +export default Vivaldi; diff --git a/frontend/pages/SoftwarePage/components/icons/index.ts b/frontend/pages/SoftwarePage/components/icons/index.ts index ca6f0fee3f..9e056ae876 100644 --- a/frontend/pages/SoftwarePage/components/icons/index.ts +++ b/frontend/pages/SoftwarePage/components/icons/index.ts @@ -950,6 +950,7 @@ import VirtualBuddy from "./VirtualBuddy"; import Viscosity from "./Viscosity"; import VisualParadigm from "./VisualParadigm"; import VisualStudioCode from "./VisualStudioCode"; +import Vivaldi from "./Vivaldi"; import VividApp from "./VividApp"; import Viz from "./Viz"; import Vlc from "./Vlc"; @@ -1992,6 +1993,7 @@ export const SOFTWARE_NAME_TO_ICON_MAP = { viscosity: Viscosity, "visual paradigm": VisualParadigm, "visual studio code": VisualStudioCode, + vivaldi: Vivaldi, vivid: VividApp, viz: Viz, vlc: Vlc, diff --git a/website/assets/images/app-icon-vivaldi-60x60@2x.png b/website/assets/images/app-icon-vivaldi-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0d9620d2aa5ef2066c2d69cf7c937a64319d28a5 GIT binary patch literal 18827 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 Hu0mjfG%lu? literal 0 HcmV?d00001