Open Sergong opened 3 years ago
I'm seeing the same issue on MAS 1.7.1 on macOS 11.2
Same as @endlesslycurious; at least for the second part it seems like MAS is engineered so that when run as root with sudo (or su) it would look for app store installations by the root user, which would (normally) be none, as opposed to when run with normal user privileges. And macOS probably doesn't allow uninstallation of an app without root password at all... interesting quandary, not sure how you might fix it.
Works with sudo -s
if that helps anyone, but things end up in root's trash:
==> App moved to trash: /private/var/root/.Trash/Telegram.app
Same issue here, my mas
' version is 1.8.2
and macOS
is 11.4
.
Same in BS 11.6 and mas 1.8.3. Uninstall not working, says "not installed".
same issue on:
mas uninstall 408981434
Warning: Apps installed from the Mac App Store require root permission to remove.
Error: Unable to move app to trash.
Error: Uninstall failed
sudo mas uninstall 408981434
Error: Not installed
sudo -s mas uninstall 408981434
Error: Not installed
mas list
408981434 iMovie (10.2.5)
Same issue
Same issue for me:
mas uninstall 897118787
Error: Not installed
Any new on what's causing this?
sudo -s doesn't sove it!
MacOS 12.2.1 and still not working
Also an issue for me on MAS 1.8.6
for Mac 12.4
Same issue for me:
mas uninstall 897118787 Error: Not installed
Any new on what's causing this?
sudo -s doesn't sove it!
Same here!
Macos 12.6.4 (21G511) Same here
I'm not a Swift programmer but I'm guessing something in this function has changed? https://github.com/mas-cli/mas/blob/main/Sources/MasKit/Commands/Uninstall.swift
public func run(_ options: Options) -> Result<Void, MASError> { let appId = UInt64(options.appId)
guard let product = appLibrary.installedApp(forId: appId) else {
return .failure(.notInstalled)
}
It's a permission issue related to macOS's security sandbox : When you use sudo, the command is ran with root privileges and in the root environment, but doesn't have access to your user-specific App Store data. This is why the mas
command is unable to see the installed apps and uninstall them. You can verify this by running mas list
(will show your apps) and sudo mas list
(will be empty).
To fix this, I'm afraid we'll need something like a Privileged Helper Tool (https://developer.apple.com/library/archive/documentation/Security/Conceptual/SecureCodingGuide/Articles/AccessControl.html#//apple_ref/doc/uid/TP40007244-SW5). A working example of this can be found under "EvenBetterAuthorizationSample".
Unfortunately I'm not experienced with Swift, otherwise I'd look into this, but that's basically what's going on.
(related?: https://github.com/mas-cli/mas/issues/417)
So mas hasn't had an uninstall function in 3 years?
Still having the same issue
So mas hasn't had an uninstall function in 3 years?
You could have fixed it all along yet here we are đ
Unfortunately I'm not experienced with Swift, otherwise I'd look into this, but that's basically what's going on.
I don't think this is the only issue, you can run things with root as your regular user with sudo -u <username> mas list
and you'll see it works just fine yet uninstall still fails.
I'm wondering what's the issue, I haven't inspected the code, but can't we just sudo rm -rf "/Applications/<name_of_the_app>.app"
and then optionally something like find ~/Library -name "*<name_of_the_app>*" -print0 | xargs -0 sudo rm -rf
?
What am I missing?
The problem is occurring because an earlier part of the code must be run by a user whose associated Apple ID was used to install the app that you want to uninstall (uninstall
can be rewritten to skip this part), while a later part must be run as root
(thus we cannot avoid needing root
access). What follows is a description of what is currently done, why it causes a problem, various solutions to avoid the problem, and my preferred solution:
The folders & files on the file system for apps installed from the App Store (i.e. App Store apps) are owned by the root
user, instead of by the user who installed them. An app is uninstalled by trashing its app folder, but that can only be done by root
. Thus, no matter what, mas uninstall
must have root privileges. The easiest way is to require that the user run sudo mas uninstall <app id>
. This ensures that users know that we donât do anything with their password, and allows the use of /etc/sudoers
to allow running without manual password input. Since users shouldnât be logged in as root
, Iâll just talk about sudo
from here on out.
The other option is to allow mas uninstall <app id>
to be run without sudo
, but have it directly ask the user for their password so it can then run sudo
in another process or call an equivalent privilege elevation function from Swift. The problems with this are:
/etc/sudoers
canât be used (IIUC), so uninstall wouldnât work well in scriptssudo
with the password or learn how to elevate privileges in Swift without spawning another process, which is supposedly very difficultThus, I suggest requiring being called with sudo
.
To trash the app, we need to know the path to the app folder on the file system (e.g., /Applications/Xcode.app
). Since we only have the app id, we use it to lookup the appâs path from an app list provided by an Apple private framework, which is only populated with apps installed for the Apple ID associated with the current user. The list lookup also limits us to uninstalling apps only for the Apple ID associated with the current user.
sudo mas uninstall <app id>
currently fails, obviously, because the current user is therefore root
; root
is not associated with any Apple ID., so the app list is empty, so we cannot find the app folder path to trash.
If we donât care about preventing users from uninstalling apps from other Apple IDs, we can try to find the app path without using the list lookup. That would avoid needing mas
(or parts thereof) being run as a user with an associated Apple ID that was used to install the app.
Unfortunately, the app ID does not seem to be deterministically findable from the files for an App Store app. The plists Iâve seen under App Store app folders do not include the app ID. Recursively greppjng for the app ID in an appâs files sometime shows it in a binary executable, but sometimes not. One extended attribute (com.apple.appstore.store_cohort
) of some App Store app folders includes the app ID (as the value for pgid
), but that extended attribute does not include the app ID for some other App Store app folders.
The bundle ID (which should be unique for each app), however, seems to be in Bundle identifier
in each appâs Contents/Info.plist
(I cannot verify it's there for all, but it's there for all I've seen). So, we could lookup app info (including the bundle ID) from the Apple web API using the app ID, then find the app folder path by searching through the plists for the bundle ID. All as root
so we donât need to switch to any other user.
If we do the above, we should allow the bundle ID to be passed as a command line argument (i.e. mas uninstall <bundle id>
) instead of the app ID, so the call to the App Store would be unnecessary in that case. Moreover, we should allow bundle IDs to be used anywhere app IDs are allowed on the command line for already installed apps (e.g., mas upgrade <bundle id>
) or to install new apps if the Apple endpoints can take bundle IDs instead of app IDs.
Or we could still use the app list provided by the Apple private framework to get the app path by switching from root
to the user that ran sudo
, either by switching users within Swift itself just for that one lookup, or by calling sudo -u $SUDO_USER mas path <app id>
in a spawned process (mas path <app id>
is a new command, but maybe it could be handled by some other new command or by an existing one). I donât know how hard it will be to implement either of those options. They also might not be able to work, because maybe switching users only switches the âUNIXâ user without starting the service or whatever that populates the installed App Store app list.
So, my best guess is:
uninstall
be called as root
(normally via sudo
)/etc/sudoers
can be setup to not require manual password input, thereby enabling automationContents/Info.plist
Contents/Info.plist
is found with a matching bundle ID, output an error listing all matching app folder paths, but donât uninstall any of them (unless a new --all
option was specified). You can then uninstall whichever you want manually using the displayed app folder path to trash the app, or you can uninstall all apps by rerunning, this time with --all
.Do people agree or disagree with this plan?
@rgoldberg I've got to agree your solution seems the most elegant for the problem. Obviously an update to the docs and man pages would need to be done đ
@jacoboneill Thanks.
I assume I would start just fixing uninstall to work with app ID, then later I'd implement bundle IDs as command line arguments for all appropriate commands at once, just so we could get uninstall fixed a bit faster.
I'm working on some other simpler issues first. I am also hoping to get in touch with some other project members before making larger changes like this, but I might work on this if I don't hear back from others soon.
@chris-araman provided some details about elevated-privilege solutions here: https://github.com/mas-cli/mas/issues/216#issuecomment-985746532
Hello, I wanted to share my workaround that others may use in the meantime. It is not without issues, but it works for me very well! I am trying to bootstrap my new laptop, and just wanted to declare exactly which mas apps should be installed. Part of doing that requires removing ones that are not declared.
Thank you, @rgoldberg, for providing the detailed write up of how to do this. I never would have thought of this. Also, I feel guilty not mentioning bots wrote most the GNU Parallel code
@xav-i.e. Thanks.
To simplify your workaround, you can use kMDItemAppStoreAdamID
instead of kMDItemCFBundleIdentifier
, so you can skip the bundleId
steps.
I'll use NSMetadataQuery
to access this information from Swift.
Your Environment
mas Install Method
brew install mas
(homebrew-core)Describe the Bug
mas uninstall [id]
complains it needs root permissions to uninstall butsudo mas uninstall [id]
says the app is not installed.To Reproduce
Steps to reproduce the behavior:
mas install [id]
sudo mas uninstall [id]
Expected Behavior
Moves the app to the Trash Bin
Actual Behavior
It will say: Error: not installed
Screenshots, Terminal Output
Additional Context