LukeSmithxyz / LARBS

Luke's Auto-Rice Bootstrapping Scripts: Installation Scripts for My Arch Linux Meta-Distribution
GNU General Public License v3.0
2.03k stars 798 forks source link

Reason Arkenfox is used with Librewolf instead of Firefox? #510

Open appeasementPolitik opened 1 year ago

appeasementPolitik commented 1 year ago

Now that the Arkenfox user.js is used, wouldn't it be more beneficial to use default Firefox, or is there a reason for sticking with Librewolf? As far as I know, Firefox with the user.js should have about the same hardening as Librewolf, but with the advantage of quicker package updates.

LukeSmithxyz commented 1 year ago

The reason Librewolf is default is just because as is, I know of absolutely no easy way to make it so the default search engine is not Google. There used to be about:config variables that allowed the user to set this, but in modern Firefox they have all been deprecated/disabled for """""security""""".

I assume there are ways to deploy Firefox with a custom search engine, but they are not well-documented enough for me to have found them out in all my time searching. Additionally, as it is, add-ons like an ad-blocker are installed in a hacky way, but I still haven't been able to have them enabled by default.

appeasementPolitik commented 1 year ago

That makes sense. I'll also check out if it's possible to change the search engine using the script, as I'm also very interested in a hardened browser by default.

Otherwise you could always fork and start Lukebrowser, haha. Shame that it's even needed.

appeasementPolitik commented 1 year ago

I've got some good news, it is possible to set the default search engine using a script, but it's just very very undocumented. I made a simple script which can set the default search engine of a fresh Firefox profile to DuckDuckGo, it only requires mozlz4 from the AUR.

#!/bin/sh

temp=$(mktemp)
searchfile="$HOME/.mozilla/firefox/cacb0a63.default-release/search.json.mozlz4"

mozlz4 "$searchfile" | sed 's/{}/{"hidden":true}/; s/\(Bing[^{]*{\)/\1"hidden":true/' > "$temp"

mozlz4 -c "$temp" > "$searchfile"

Firefox stores the search engine configuration in the search.json.mozlz4 file in the profile which uses the mozlz4 format. I first use a tool to decompress it and then edit it so the first option in the file (Google) is hidden, which shifts the default search engine to Bing. I then set Bing to hidden, which sets DuckDuckGo as the default search engine. I then compress the file again and put it in its original place in the profile.

Upon starting the browser the address bar still shows "Search with Google", but this is purely visual and the default search engine has successfully changed. This visual bug remains until the next browser restart.

If confidence is high enough that this file will not change too often, it is also possible to modify the file beforehand and ship it with larbs to then move it into the right place, which also drops the requirement for mozlz4

appeasementPolitik commented 1 year ago

I'll also place some other stuff here which can help to create a hardened Firefox which can be shipped. Firefox by default has all kinds of menu entries for their sync service, which can all be disabled by setting

// Disable Firefox sync and its menu entries
user_pref("identity.fxaccounts.enabled", false);

The "More from Mozilla" settings entry can also be disabled by setting

#category-more-from-mozilla { display: none !important }

in chrome/userContent.css.

It might also be useful to change arkenfox.js to user.js, and to softlink ~/.config/firefox/larbs.js to user-overrides.js. If the Arkenfox update scripts are also installed into the profile, this gives the possibility of automatically updating the user.js and merging the overrides after Arkenfox updates in order to support new Firefox versions.

Reference: https://github.com/arkenfox/user.js/wiki/3.4-Apply-&-Update-&-Maintain

On my system I've also created a pacman hook to automatically update Arkenfox:

/etc/pacman.d/hooks/upgrade-arkenfox.hook

[Trigger]
Operation = Upgrade
Operation = Install
Type = Package
Target = *

[Action]
Description = Checking for Arkenfox user.js updates...
When = PostTransaction
Exec = /bin/sh -c "/usr/bin/su $SUDO_USER -c 'ffhome=$HOME/.mozilla/firefox/cdk1w5dg.default-release/; cd $ffhome; orig=$(/usr/bin/md5sum user.js); /bin/sh updater.sh -usb >/dev/null && new=$(/usr/bin/md5sum user.js) && { [ ! $orig = $new ] && /usr/bin/echo -e \"\t==> Arkenfox user.js
updated! Execute the following command to clean your preferences:\n\tsh ${ffhome}prefsCleaner.sh\"; exit 0; }'"

Looks messy, but could always be split into a separate script if you'd like to use it. It first checks for an Arkenfox update and if it finds one, it installs it into user.js while at the same time also merging user-overrides.js if it exists. When the user.js or user-overrides have been updated, it also prompts the user to run the prefCleaner.sh script. Even with the exit 0, pacman still gives an error if updater.sh has failed.

appeasementPolitik commented 1 year ago

I also managed to enable the addons of a fresh Firefox profile by default, which is a bit more involved than setting the default search engine:

#!/bin/sh

ffhome="$HOME/.mozilla/firefox/0mw3v6dx.default-release/"
firstaddonid='jid1-BoFifL9Vbdl2zQ@jetpack'

# Copy extensions into extensions dir
mkdir "${ffhome}extensions/"
cp addons/* "${ffhome}extensions/"

# Let Firefox generate extensions.json, needs quite a long delay (about 5 seconds, made it a bit longer to be sure)
firefox --headless >/dev/null 2>&1 &
sleep 8
pkill firefox

# Set the extensions to active in the menu
#
# Get byte location of the first match of the first addon id
total=$(wc -c "${ffhome}extensions.json" | cut -d' ' -f1)
offset=$(grep -ob "$firstaddonid" "${ffhome}extensions.json" | head -n 1 | cut -d':' -f1)

# Split file into the part before first addon id is found, and the part after
beforefound=$(head -c "$offset" "${ffhome}extensions.json")
afterfound=$(tail -c $((total - offset)) "${ffhome}extensions.json")

# Set all "seen":false to true, "active":false to true, and all "userDisabled":true to false in the second part
afterfound=$(echo "$afterfound" | sed 's/\(seen":\)false/\1true/g; s/\(active":\)false\(,"userDisabled":\)true/\1true\2false/g')

# Move both parts back to the original location
echo -E "${beforefound}${afterfound}" > "${ffhome}extensions.json"

# Edit prefs as well so the extensions actually get enabled
#
sed -i 's/\(extensions\.pendingOperations", \)false/\1true/' "${ffhome}prefs.js"

The copying addons step can be replaced with the existing loop, I just used an addons directory for debugging purposes. The id of the first addon in the extensions.json file is also needed, because it is used to split the extensions.json file in two, as everything after the first id is entries containing the new addons, which need to have their configurations changed.

First the addons are moved into the extensions/ folder, and then Firefox is started so that it can generate extensions.json with the new addons. It is closed again, and then some addon settings in extensions.json are changed which change them to be active. Afterwards the changed extensions.json is moved back to its original location, and then the last step is to set extensions.pendingOperations in prefs.js to true so that Firefox notices that the extensions have changed and makes sure they are enabled and show up in the addons dropdown.

appeasementPolitik commented 1 year ago

Also to avoid having to manually update the addon.xpi urls every so often, it is also possible to automatically fetch the download url of the latest addon xpi version:

#!/bin/sh

addonlist="ublock-origin
decentraleyes
istilldontcareaboutcookies
vim-vixen"

for addon in $addonlist; do
    curl --silent "https://addons.mozilla.org/en-US/firefox/addon/${addon}/" | grep -o 'https://addons.mozilla.org/firefox/downloads/file/[^"]*'
done
LukeSmithxyz commented 1 year ago

All this just to do something so basic. It's a shame you have to fight against this software to do the bare minimum nowadays.

I went ahead and added your suggestions to get the newest extensions automatically, as well as the disabling the Firefox Sync and "More from Mozilla."

I'm aware of the mozlz4 tool. The thing is, I don't want the script to have to download the entirely of Rust just to run some stupid program (note that Librewolf itself is smaller than Rust). If there is an easier way to do this with vanilla Firefox, but honestly, I don't mind not having Firefox branding because they don't deserve the branding for putting us through this unnecessary pain.

I haven't tested your block that enables the add-ons. If it is robust and doesn't break too easily, I'll add something like it.

LukeSmithxyz commented 1 year ago

Looks like there's even an Arkenfox updater in the AUR. I might add that as well in concert with your recommendation to link the overrides.

appeasementPolitik commented 1 year ago

You're right about mozlz4 being too big. I've been looking for some other ways as well, and I've got another way working with only python-lz4 as extra dependency, which is about 200 KiB. I wanted to avoid it by using normal lz4, but this was not possible.

#!/bin/sh

searchfile="$HOME/.mozilla/firefox/krsdeftt.default-release/search.json.mozlz4"

python -c "import lz4.block; data=open('$searchfile', 'rb'); data.read(8); print(lz4.block.decompress(data.read()).decode())" | sed 's/{}/{\"hidden\":true}/; s/\(Bing[^{]*{\)/\1\"hidden\":true/' | python -c "import lz4.block; import sys; open('$searchfile', 'wb').write(b'mozLz40\0' + lz4.block.compress(sys.stdin.buffer.read()))"

All compressing and decompressing of the file are done using the python-lz4 library instead.

It feels wrong to call python in a pipe, but that's how far you need to go when dealing with browsers apparently.

LukeSmithxyz commented 1 year ago

I've now set it so LARBS should use that AUR package with a wrapper script and hook to update all instances of Arkenfox's user.js on the update of Firefox or Librewolf. I haven't tested it on a fresh machine yet, but will soon.

Any particular reason why it's impossible with lz4?

appeasementPolitik commented 1 year ago

mozlz4 is very similar to lz4, except for the fact that the header is different from the normal lz4 compression, as mozlz4 uses a special header, and has less metadata. This header difference means that the lz4 program does not recognize the mozlz4 file as correct, even when the header is removed.

As a test I've decompressed the mozlz4 file and recompressed it with lz in order to check for differences with the mozlz4 file, and the only thing I saw was the different header and a different end, which might have to do with the normal mozlz4 not having an end signal like lz4 has. When I copied the header from the lz4 compressed file to the mozlz4 file, it successfully decompressed, even with the end header. I think the problem with decompression is that lz4 doesn't have an option to ignore invalid headers, and I don't know of a way to manually generate a header in order to make lz4 happy.

And for compression, I also tried replacing lz's generated header with the header from the mozlz4 file, but Firefox rejected the file and I suspect that it is because the end is different. (now that I say it, it might be possible to also strip the end signal in order to make Firefox not reject it)

LZ4 libraries like python-lz4 expose some functions which are a bit less picky about specific headers and metadata and things like that, which is why they do work. lz4 wants to generate its own metadata and recognizes its own metadata, whereas no extra metadata is used in mozlz4 except for the custom header. lz4 uses the frame format, and mozlz4 only uses the block format.

Some references:

https://github.com/lz4/lz4/issues/880 https://github.com/lz4/lz4/issues/584 (error I got when trying to decompress with missing/mozlz4 header)

appeasementPolitik commented 1 year ago

I've looked for other available lz4 tools as well to see if they could also be used, but the only one that is available in the standard repos or the AUR which can be used for this is python-lz4, which means that the python solution is the best it gets as far as I know.

This would mean that python-lz4 would have to be installed to run this script just to be removed again once it's done, but you can decide if that is worth it or not.

The python part of the script is very stable, compared to a similar project like:

https://gist.github.com/Tblue/62ff47bef7f894e92ed5

That tool works with the same library in the same way, and since its start in 2015 it has needed only one revision in order to keep supporting the mozlz4 format and the new lz4 versions.

The sed part is less stable, as it currently depends on the available search engines and the order of them not changing. To make it more stable, it could be changed so that every available search engine except for DuckDuckGo (or whatever search engine chosen) gets disabled, like this:

sed 's/\(_metaData":{\)/\1\"hidden\":true/g; s/\(DuckDuckGo[^{]*{\"hidden\":\)true/\1false/'

I've also tried just changing the default search engine as that is an available option in the configuration file, but the user chosen default search engine comes with some kind of hash which seems dependent on the Firefox installation somehow, because just copying the generated hash for DuckDuckGo to a fresh profile doesn't work. Therefore just hiding all search engines that are not default looks like the way to go.