diff --git a/users/ryan/common.nix b/users/ryan/common.nix
index cd1557c..b5b3533 100644
--- a/users/ryan/common.nix
+++ b/users/ryan/common.nix
@@ -1,7 +1,7 @@
{ config, pkgs, lib, ... }:
{
- imports = [ ./modules/nvim ./modules/aria2 ];
+ imports = [ ./modules/nvim ./modules/aria2 ./modules/zen ];
news.display = "silent";
@@ -13,6 +13,8 @@
ryan.aria2.enable = true;
+ ryan.zen.enable = true;
+
programs.starship = {
enable = true;
settings = {
@@ -29,14 +31,6 @@
};
};
- # Package/platform overrides (darwinDefaultsId, package = null for homebrew,
- # etc.) live in darwin.nix/linux.nix. Policies/profile are shared.
- programs.zen-browser = {
- enable = true;
- policies = import ./zen/zenPolicies.nix;
- profiles.default = import ./zen/zenProfile.nix;
- };
-
programs.eza = {
enable = true;
enableZshIntegration = true;
diff --git a/users/ryan/darwin.nix b/users/ryan/darwin.nix
index 568306a..81f26e5 100644
--- a/users/ryan/darwin.nix
+++ b/users/ryan/darwin.nix
@@ -10,11 +10,6 @@
ryan.sketchybar.enable = true;
- programs.zen-browser = {
- package = null; # managed via homebrew
- darwinDefaultsId = "app.zen-browser.zen";
- };
-
# Need special config for gpg-agent on macOS
home.file.".gnupg/gpg-agent.conf".text = ''
pinentry-program ${pkgs.pinentry_mac}/Applications/pinentry-mac.app/Contents/MacOS/pinentry-mac
diff --git a/users/ryan/modules/aria2/default.nix b/users/ryan/modules/aria2/default.nix
index 0f06b9b..8082fc1 100644
--- a/users/ryan/modules/aria2/default.nix
+++ b/users/ryan/modules/aria2/default.nix
@@ -109,11 +109,12 @@ in
mv -f "$tmp" "${runtimeConf}"
fi
'';
+ }
- programs.zen-browser.policies.ExtensionSettings.${extId} = {
- install_url = "https://addons.mozilla.org/firefox/downloads/file/5055892/aria2_extension-4.15.4.xpi";
- installation_mode = "force_installed";
- updates_disabled = true;
+ (lib.mkIf config.ryan.zen.enable {
+ ryan.zen.extensions.aria2 = {
+ id = extId;
+ version = "4.15.4";
};
# The extension only reads storage.sync (no storage.managed), so
@@ -145,7 +146,7 @@ in
fi
'';
}];
- }
+ })
(lib.mkIf isDarwin {
launchd.agents.aria2 = {
diff --git a/users/ryan/modules/zen/default.nix b/users/ryan/modules/zen/default.nix
new file mode 100644
index 0000000..8e7a73b
--- /dev/null
+++ b/users/ryan/modules/zen/default.nix
@@ -0,0 +1,94 @@
+{ config, lib, pkgs, ... }:
+let
+ inherit (lib) mkOption mkEnableOption types mkIf mkMerge;
+ inherit (pkgs.stdenv.hostPlatform) isDarwin;
+ cfg = config.ryan.zen;
+
+ extensionType = types.submodule {
+ options = {
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Set false to drop an entry defined elsewhere (attrsOf can't delete keys).";
+ };
+ id = mkOption {
+ type = types.str;
+ description = "Add-on GUID; also the policy key and the AMO API lookup key.";
+ };
+ version = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "Hard pin. null = whatever extensions.lock.json holds (bump with lock.sh).";
+ };
+ mode = mkOption {
+ type = types.enum [ "force_installed" "normal_installed" ];
+ default = "force_installed";
+ };
+ privateBrowsing = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = "Emit private_browsing only when non-null.";
+ };
+ extraSettings = mkOption {
+ type = types.attrsOf types.anything;
+ default = { };
+ description = "Merged last into the ExtensionSettings entry (e.g. default_area).";
+ };
+ };
+ };
+in
+{
+ imports = [ ./extensions.nix ];
+
+ options.ryan.zen = {
+ enable = mkEnableOption "Zen browser with locked policies, profile and extensions";
+
+ extensions = mkOption {
+ type = types.attrsOf extensionType;
+ default = { };
+ description = "Policy-managed extensions, resolved through extensions.lock.json.";
+ };
+
+ exclusive = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Disallow all undeclared extensions and disable all temporary loading";
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+ {
+ programs.zen-browser = {
+ enable = true;
+ policies = import ./policies.nix;
+ profiles.default = import ./profile.nix;
+ };
+
+ ryan.zen.extensions = {
+ ublock-origin = {
+ id = "uBlock0@raymondhill.net";
+ privateBrowsing = true;
+ };
+ bitwarden = {
+ id = "{446900e4-71c2-419f-a6a7-df9c091e268b}";
+ mode = "normal_installed";
+ privateBrowsing = true;
+ };
+ sponsorblock.id = "sponsorBlocker@ajay.app";
+ dearrow.id = "deArrow@ajay.app";
+ return-youtube-dislikes.id = "{762f9885-5a13-4abd-9c77-433dcd38b8fd}";
+ youtube-nonstop.id = "{0d7cafdd-501c-49ca-8ebb-e3341caaa55e}";
+ tampermonkey.id = "firefox@tampermonkey.net";
+ floccus.id = "floccus@handmadeideas.org";
+ mailvelope.id = "jid1-AQqSMBYb0a8ADg@jetpack";
+ };
+ }
+
+ (mkIf isDarwin {
+ programs.zen-browser = {
+ package = null; # managed via homebrew
+ darwinDefaultsId = "app.zen-browser.zen";
+ };
+ })
+ ]);
+}
diff --git a/users/ryan/modules/zen/extensions.lock.json b/users/ryan/modules/zen/extensions.lock.json
new file mode 100644
index 0000000..cd0df78
--- /dev/null
+++ b/users/ryan/modules/zen/extensions.lock.json
@@ -0,0 +1,62 @@
+{
+ "aria2": {
+ "hash": "sha256-0V6zKAqz8uKlDE4q/szSW732B9X0TUKdo8qUChv2IW0=",
+ "id": "baptistecdr@users.noreply.github.com",
+ "url": "https://addons.mozilla.org/firefox/downloads/file/5055892/aria2_extension-4.15.4.xpi",
+ "version": "4.15.4"
+ },
+ "bitwarden": {
+ "hash": "sha256-XdbvpdIo2+rHoMaog/lReH3GbleYUyLOVJIQsN+NBUw=",
+ "id": "{446900e4-71c2-419f-a6a7-df9c091e268b}",
+ "url": "https://addons.mozilla.org/firefox/downloads/file/5076543/bitwarden_password_manager-2026.9.3.xpi",
+ "version": "2026.9.3"
+ },
+ "dearrow": {
+ "hash": "sha256-MVGlHbgJN0a+ZGwEGvPq1VPl3pX6Tr1frgM+A54QVtE=",
+ "id": "deArrow@ajay.app",
+ "url": "https://addons.mozilla.org/firefox/downloads/file/4897568/dearrow-2.3.10.xpi",
+ "version": "2.3.10"
+ },
+ "floccus": {
+ "hash": "sha256-FOk0NhveQToZjq88hRU3wjJVwR82ZQ/DJAz6hqlkJYg=",
+ "id": "floccus@handmadeideas.org",
+ "url": "https://addons.mozilla.org/firefox/downloads/file/5063393/floccus-5.11.0.xpi",
+ "version": "5.11.0"
+ },
+ "mailvelope": {
+ "hash": "sha256-MUAU3OzESJWHE9zkuylyMh8/4nxj725urSDouFA5Juw=",
+ "id": "jid1-AQqSMBYb0a8ADg@jetpack",
+ "url": "https://addons.mozilla.org/firefox/downloads/file/4846833/mailvelope-6.3.0.xpi",
+ "version": "6.3.0"
+ },
+ "return-youtube-dislikes": {
+ "hash": "sha256-WXGXSfbfOMFgHKXznAIVjV8AXEPp7uofGVJ/RoyUEhc=",
+ "id": "{762f9885-5a13-4abd-9c77-433dcd38b8fd}",
+ "url": "https://addons.mozilla.org/firefox/downloads/file/5012638/return_youtube_dislikes-4.0.6.xpi",
+ "version": "4.0.6"
+ },
+ "sponsorblock": {
+ "hash": "sha256-DVDhYyxvFe4VpUPmcOHFcpdGBaXAJiKRbgjgJoA9+D8=",
+ "id": "sponsorBlocker@ajay.app",
+ "url": "https://addons.mozilla.org/firefox/downloads/file/4897574/sponsorblock-6.1.7.xpi",
+ "version": "6.1.7"
+ },
+ "tampermonkey": {
+ "hash": "sha256-GQAxx428VpYRSDVgHyyOa4Va0eE0313yePj8FYwGWQg=",
+ "id": "firefox@tampermonkey.net",
+ "url": "https://addons.mozilla.org/firefox/downloads/file/4797143/tampermonkey-5.5.0.xpi",
+ "version": "5.5.0"
+ },
+ "ublock-origin": {
+ "hash": "sha256-W3RBWGBFY3BkS9gPFhJehlsObDVrtd/PuEBpln6qUoc=",
+ "id": "uBlock0@raymondhill.net",
+ "url": "https://addons.mozilla.org/firefox/downloads/file/5034826/ublock_origin-1.75.0.xpi",
+ "version": "1.75.0"
+ },
+ "youtube-nonstop": {
+ "hash": "sha256-dlnRgPduqQjqgbhO2b3RiGJOqqYriKzL5titToyu/zg=",
+ "id": "{0d7cafdd-501c-49ca-8ebb-e3341caaa55e}",
+ "url": "https://addons.mozilla.org/firefox/downloads/file/4187690/youtube_nonstop-0.9.2.xpi",
+ "version": "0.9.2"
+ }
+}
diff --git a/users/ryan/modules/zen/extensions.nix b/users/ryan/modules/zen/extensions.nix
new file mode 100644
index 0000000..f91984b
--- /dev/null
+++ b/users/ryan/modules/zen/extensions.nix
@@ -0,0 +1,89 @@
+# version = null -> whatever the lock holds; `lock.sh <attr>` bumps it
+# version = "x.y.z" -> hard pin; lock.sh resolves exactly that version and
+# eval fails if the lock disagrees
+{ config, lib, pkgs, ... }:
+let
+ inherit (lib) mapAttrs' mapAttrsToList nameValuePair optionalAttrs;
+ cfg = config.ryan.zen;
+ exts = lib.filterAttrs (_: e: e.enable) cfg.extensions;
+ zen = config.programs.zen-browser;
+
+ lockFile = ./extensions.lock.json;
+ lock = lib.importJSON lockFile;
+
+ # Validated lock entry. Throws (not assertions) so the message surfaces no
+ # matter which consumer forces the policy first (HM wrapper, darwin defaults).
+ locked = name: e:
+ let
+ l = lock.${name} or (throw
+ "zen extension '${name}' has no entry in extensions.lock.json; run modules/zen/lock.sh");
+ in
+ if l.id != e.id then throw
+ "zen extension '${name}': lock has id ${l.id}, config has ${e.id}; re-run lock.sh"
+ else if e.version != null && l.version != e.version then throw
+ "zen extension '${name}': pinned ${e.version}, lock has ${l.version}; re-run lock.sh"
+ else l;
+
+ xpi = name: e:
+ let l = locked name e; in
+ pkgs.fetchurl {
+ name = "zen-ext-${name}-${l.version}.xpi";
+ inherit (l) url hash;
+ };
+
+ settingsFor = name: e:
+ {
+ install_url = "file://${xpi name e}";
+ installation_mode = e.mode;
+ updates_disabled = true;
+ }
+ // optionalAttrs (e.privateBrowsing != null) { private_browsing = e.privateBrowsing; }
+ // e.extraSettings;
+
+ pins = pkgs.writeText "zen-extension-pins.json" (builtins.toJSON
+ (mapAttrsToList (name: e: { inherit (e) id; version = (locked name e).version; }) exts));
+
+ jq = lib.getExe pkgs.jq;
+ sort = "${pkgs.coreutils}/bin/sort";
+in
+{
+ config = lib.mkIf (cfg.enable && exts != { }) {
+ programs.zen-browser.policies = {
+ # Locks extensions.update.enabled=false; belt to updates_disabled's braces.
+ ExtensionUpdate = false;
+ ExtensionSettings =
+ mapAttrs' (name: e: nameValuePair e.id (settingsFor name e)) exts
+ // optionalAttrs cfg.exclusive { "*".installation_mode = "blocked"; };
+ };
+
+ # Explicit GC root for every pinned XPI, independent of how the policy
+ # gets serialized on each platform. Also a handy place to inspect them.
+ home.file.".local/share/zen-extensions".source = pkgs.linkFarm "zen-extensions"
+ (mapAttrsToList (name: e: { name = "${name}.xpi"; path = xpi name e; }) exts);
+
+ # The policy engine refuses downgrades (installed > pinned is cancelled in
+ # PoliciesHelpers installAddonFromURL). For exactly that case, delete the
+ # installed XPI while Zen is closed; the next start drops it from the DB
+ # and the policy installs the pin. Upgrades are left to the policy engine.
+ programs.zen-browser.activationFragments.default = [{
+ requiresLock = true;
+ skipSubject = "zen extension downgrade check";
+ text = ''
+ prof="${zen.profilesPath}/${zen.profiles.default.path}"
+ if [ -f "$prof/extensions.json" ]; then
+ while IFS=$'\t' read -r id want have; do
+ [ -n "$have" ] && [ "$have" != "$want" ] || continue
+ newest="$(printf '%s\n%s\n' "$want" "$have" | ${sort} -V | tail -n1)"
+ [ "$newest" = "$have" ] || continue
+ echo "zen-extensions: $id installed $have > pinned $want; removing installed XPI"
+ [[ -v DRY_RUN ]] || rm -f "$prof/extensions/$id.xpi"
+ done < <(${jq} -r --slurpfile ej "$prof/extensions.json" '
+ .[] | . as $p
+ | [ $p.id, $p.version,
+ ([ $ej[0].addons[] | select(.id == $p.id and .location == "app-profile") | .version ][0] // "") ]
+ | @tsv' ${pins})
+ fi
+ '';
+ }];
+ };
+}
diff --git a/users/ryan/modules/zen/lock.sh b/users/ryan/modules/zen/lock.sh
new file mode 100755
index 0000000..a375fd8
--- /dev/null
+++ b/users/ryan/modules/zen/lock.sh
@@ -0,0 +1,61 @@
+#!/usr/bin/env bash
+# Resolve ryan.zen.extensions into extensions.lock.json via AMO's v5 API.
+#
+# lock.sh <attr> [--missing]
+#
+# <attr> is the evaluated home-manager config, e.g.
+# ~/.config/home-manager#homeConfigurations.ryan.config
+# ~/.config/nix#darwinConfigurations.<host>.config.home-manager.users.ryan
+#
+# Default: re-resolve everything (unpinned entries move to AMO's current version).
+# --missing: keep existing entries that still satisfy the spec; resolve the rest.
+#
+# Only ryan.zen.extensions.*.{id,version} is evaluated, which never touches the
+# lock, so this works when the lock is empty or stale.
+# Needs: nix (>= 2.19 for `nix hash convert`), curl, jq.
+set -euo pipefail
+
+attr="${1:?usage: lock.sh <hm-config-attr> [--missing]}"
+mode="${2:-all}"
+here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+lockfile="$here/extensions.lock.json"
+api="https://addons.mozilla.org/api/v5/addons/addon"
+
+spec="$(nix eval --json "$attr.ryan.zen.extensions" \
+ --apply 'es: builtins.mapAttrs (_: e: { inherit (e) id version; }) (builtins.removeAttrs es (builtins.filter (n: !es.${n}.enable) (builtins.attrNames es)))')"
+old="$(cat "$lockfile" 2>/dev/null || echo '{}')"
+new='{}'
+
+uri() { jq -rn --arg s "$1" '$s|@uri'; }
+
+for name in $(jq -r 'keys[]' <<<"$spec"); do
+ id="$(jq -r --arg n "$name" '.[$n].id' <<<"$spec")"
+ want="$(jq -r --arg n "$name" '.[$n].version // empty' <<<"$spec")"
+
+ if [ "$mode" = "--missing" ]; then
+ keep="$(jq -c --arg n "$name" --arg id "$id" --arg v "$want" \
+ '.[$n] | select(. != null and .id == $id and ($v == "" or .version == $v))' <<<"$old")"
+ if [ -n "$keep" ]; then
+ new="$(jq --arg n "$name" --argjson e "$keep" '.[$n] = $e' <<<"$new")"
+ continue
+ fi
+ fi
+
+ if [ -n "$want" ]; then
+ v="$(curl -fsSL "$api/$(uri "$id")/versions/$(uri "$want")/")"
+ else
+ v="$(curl -fsSL "$api/$(uri "$id")/" | jq '.current_version')"
+ fi
+
+ version="$(jq -r '.version' <<<"$v")"
+ url="$(jq -r '.file.url' <<<"$v")"
+ algo_hex="$(jq -r '.file.hash' <<<"$v")" # "sha256:<hex>"
+ [ "${algo_hex%%:*}" = sha256 ] || { echo "$name: unexpected hash $algo_hex" >&2; exit 1; }
+ hash="$(nix hash convert --hash-algo sha256 --to sri "${algo_hex#sha256:}")"
+
+ echo "$name ($id) -> $version" >&2
+ new="$(jq --arg n "$name" --arg id "$id" --arg ver "$version" --arg url "$url" --arg h "$hash" \
+ '.[$n] = {id: $id, version: $ver, url: $url, hash: $h}' <<<"$new")"
+done
+
+jq -S . <<<"$new" > "$lockfile.tmp" && mv "$lockfile.tmp" "$lockfile"
diff --git a/users/ryan/modules/zen/policies.nix b/users/ryan/modules/zen/policies.nix
new file mode 100644
index 0000000..93a5324
--- /dev/null
+++ b/users/ryan/modules/zen/policies.nix
@@ -0,0 +1,78 @@
+let
+ lock-false = {
+ Value = false;
+ Status = "locked";
+ };
+ lock-true = {
+ Value = true;
+ Status = "locked";
+ };
+in
+{
+ EnableTrackingProtection = {
+ Value = true;
+ Locked = true;
+ Cryptomining = true;
+ Fingerprinting = true;
+ EmailTracking = true;
+ };
+ UserMessaging = {
+ WhatsNew = false;
+ ExtensionRecommendations = false;
+ FeatureRecommendations = false;
+ UrlbarInterventions = false;
+ SkipOnboarding = true;
+ MoreFromMozilla = false;
+ Labs = false;
+ Locked = true;
+ };
+ DisableAppUpdate = true;
+ DisableAccounts = true;
+ DisableFirefoxAccounts = true;
+ DisableFirefoxStudies = true;
+ DisablePocket = true;
+ DisableTelemetry = true;
+ AutofillAddressEnabled = false;
+ AutofillCreditCardEnabled = false;
+ DisableMasterPasswordCreation = true;
+ PasswordManagerEnabled = false;
+ PrimaryPassword = false;
+ OfferToSaveLogins = false;
+ NoDefaultBookmarks = true;
+ OverrideFirstRunPage = "";
+ OverridePostUpdatePage = "";
+ FirefoxHome = {
+ Search = true;
+ TopSites = true;
+ SponsoredTopSites = false;
+ Highlights = false;
+ Pocket = false;
+ SponsoredPocket = false;
+ Snippets = false;
+ Locked = true;
+ };
+ SearchSuggestEnabled = true;
+ FirefoxSuggest = {
+ WebSuggestions = true;
+ SponsoredSuggestions = false;
+ ImproveSuggest = false;
+ Locked = true;
+ };
+ PictureInPicture = lock-true;
+ HardwareAcceleration = true;
+ Certificates = {
+ ImportEnterpriseRoots = true;
+ };
+ Preferences = {
+ "xpinstall.whitelist.required" = lock-true;
+ "dom.webgpu.enabled" = lock-true;
+ "media.eme.enabled" = lock-true;
+ "ui.preferReducedMotion" = lock-false;
+ "general.autoScroll" = lock-true;
+ "general.smoothScroll" = lock-true;
+ "browser.crashReports.unsubmittedCheck.autoSubmit2" = lock-false;
+ "browser.aboutConfig.showWarning" = lock-false;
+ "media.videocontrols.picture-in-picture.enable-when-switching-tabs.enabled" = lock-true;
+ };
+}
+
diff --git a/users/ryan/zen/zenProfile.nix b/users/ryan/modules/zen/profile.nix
similarity index 100%
rename from users/ryan/zen/zenProfile.nix
rename to users/ryan/modules/zen/profile.nix
diff --git a/users/ryan/zen/zenPolicies.nix b/users/ryan/zen/zenPolicies.nix
deleted file mode 100644
index 8d39689..0000000
--- a/users/ryan/zen/zenPolicies.nix
+++ /dev/null
@@ -1,128 +0,0 @@
-let
- lock-false = {
- Value = false;
- Status = "locked";
- };
- lock-true = {
- Value = true;
- Status = "locked";
- };
-in
-{
- EnableTrackingProtection = {
- Value = true;
- Locked = true;
- Cryptomining = true;
- Fingerprinting = true;
- EmailTracking = true;
- };
- UserMessaging = {
- WhatsNew = false;
- ExtensionRecommendations = false;
- FeatureRecommendations = false;
- UrlbarInterventions = false;
- SkipOnboarding = true;
- MoreFromMozilla = false;
- Labs = false;
- Locked = true;
- };
- DisableAppUpdate = true;
- DisableAccounts = true;
- DisableFirefoxAccounts = true;
- DisableFirefoxStudies = true;
- DisablePocket = true;
- DisableTelemetry = true;
- AutofillAddressEnabled = false;
- AutofillCreditCardEnabled = false;
- DisableMasterPasswordCreation = true;
- PasswordManagerEnabled = false;
- PrimaryPassword = false;
- OfferToSaveLogins = false;
- NoDefaultBookmarks = true;
- OverrideFirstRunPage = "";
- OverridePostUpdatePage = "";
- FirefoxHome = {
- Search = true;
- TopSites = true;
- SponsoredTopSites = false;
- Highlights = false;
- Pocket = false;
- SponsoredPocket = false;
- Snippets = false;
- Locked = true;
- };
- SearchSuggestEnabled = true;
- FirefoxSuggest = {
- WebSuggestions = true;
- SponsoredSuggestions = false;
- ImproveSuggest = false;
- Locked = true;
- };
- PictureInPicture = lock-true;
- HardwareAcceleration = true;
- Certificates = {
- ImportEnterpriseRoots = true;
- };
- ExtensionSettings = {
- #"*".installation_mode = "blocked";
- # uBlock Origin
- "uBlock0@raymondhill.net" = {
- install_url = "https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi";
- installation_mode = "force_installed";
- private_browsing = true;
- };
- # Bitwarden
- "{446900e4-71c2-419f-a6a7-df9c091e268b}" = {
- install_url = "https://addons.mozilla.org/firefox/downloads/latest/bitwarden-password-manager/latest.xpi";
- installation_mode = "normal_installed";
- private_browsing = true;
- };
- # SponsorBlock
- "sponsorBlocker@ajay.app" = {
- install_url = "https://addons.mozilla.org/firefox/downloads/latest/sponsorblock/latest.xpi";
- installation_mode = "force_installed";
- };
- # DeArrow
- "deArrow@ajay.app" = {
- install_url = "https://addons.mozilla.org/firefox/downloads/latest/dearrow/latest.xpi";
- installation_mode = "force_installed";
- };
- # Return Youtube Dislike
- "{762f9885-5a13-4abd-9c77-433dcd38b8fd}" = {
- install_url = "https://addons.mozilla.org/firefox/downloads/latest/return-youtube-dislikes/latest.xpi";
- installation_mode = "force_installed";
- };
- # Youtube Nonstop
- "{0d7cafdd-501c-49ca-8ebb-e3341caaa55e}" = {
- install_url = "https://addons.mozilla.org/firefox/downloads/latest/youtube-nonstop/latest.xpi";
- installation_mode = "force_installed";
- };
- # TamperMonkey
- "firefox@tampermonkey.net" = {
- install_url = "https://addons.mozilla.org/firefox/downloads/latest/tampermonkey/latest.xpi";
- installation_mode = "force_installed";
- };
- # Floccus
- "floccus@handmadeideas.org" = {
- install_url = "https://addons.mozilla.org/firefox/downloads/latest/floccus/latest.xpi";
- installation_mode = "force_installed";
- };
- # Mailvelope
- "jid1-AQqSMBYb0a8ADg@jetpack" = {
- install_url = "https://addons.mozilla.org/firefox/downloads/latest/mailvelope/latest.xpi";
- installation_mode = "force_installed";
- };
- };
- Preferences = {
- "xpinstall.whitelist.required" = lock-true;
- "dom.webgpu.enabled" = lock-true;
- "media.eme.enabled" = lock-true;
- "ui.preferReducedMotion" = lock-false;
- "general.autoScroll" = lock-true;
- "general.smoothScroll" = lock-true;
- "browser.crashReports.unsubmittedCheck.autoSubmit2" = lock-false;
- "browser.aboutConfig.showWarning" = lock-false;
- "media.videocontrols.picture-in-picture.enable-when-switching-tabs.enabled" = lock-true;
- };
-}
-