Added a module to configure browser based on the gecko engine, such as Firefox.
This module should provide similar functionality to the firefox module in home-manager. Some notable differences between the two include: * home-manager configures a single browser. This means that any configuration that cannot be done on a per-profile basis is shared between all profiles. This module configures a new copy of the browser for every profile, ensuring that *all* configuration can be on a per-profile basis. This might be seen as insanity in a regular distro, but in NixOS this is trivial to do and requires no extra storage space. * home-manager modifies files in the user's directory to configure things such as extensions and search engines. This module avoids that when possible by pushing configuration into policies and preferences at a browser level. This is much nicer for impermanence-based systems.
This commit is contained in:
parent
977595f7e8
commit
0f0c4c7727
10 changed files with 962 additions and 1 deletions
|
|
@ -1,2 +1,9 @@
|
||||||
sLib: {
|
sLib:
|
||||||
|
let
|
||||||
|
mkGeckoBrowser = import ./gecko-browser/make-module.nix sLib;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
firefox = mkGeckoBrowser "firefox";
|
||||||
|
floorp = mkGeckoBrowser "floorp";
|
||||||
|
librewolf = mkGeckoBrowser "librewolf";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
104
modules/home-manager/gecko-browser/gecko-lib/bookmarks.nix
Normal file
104
modules/home-manager/gecko-browser/gecko-lib/bookmarks.nix
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
info,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
## BookmarkConfig -> BookmarkJSON
|
||||||
|
updateBookmarkJSON =
|
||||||
|
data:
|
||||||
|
builtins.removeAttrs
|
||||||
|
(
|
||||||
|
data
|
||||||
|
// (
|
||||||
|
if (data ? children) then
|
||||||
|
{
|
||||||
|
type = "text/x-moz-place-container";
|
||||||
|
children = builtins.map updateBookmarkJSON data.children;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
type = "text/x-moz-place";
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// lib.optionalAttrs (data ? addTime) { dateAdded = data.addTime; }
|
||||||
|
// lib.optionalAttrs (data ? modTime) { lastModified = data.modTime; }
|
||||||
|
)
|
||||||
|
[
|
||||||
|
"addTime"
|
||||||
|
"modTime"
|
||||||
|
];
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
bookmarks = lib.mkOption {
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
menu = lib.mkOption {
|
||||||
|
type = lib.types.listOf (lib.types.attrsOf lib.types.anything);
|
||||||
|
default = [ ];
|
||||||
|
description = "Bookmarks to be placed in the menu.";
|
||||||
|
};
|
||||||
|
toolbar = lib.mkOption {
|
||||||
|
type = lib.types.listOf (lib.types.attrsOf lib.types.anything);
|
||||||
|
default = [ ];
|
||||||
|
description = "Bookmarks to be placed in the toolbar";
|
||||||
|
};
|
||||||
|
unfiled = lib.mkOption {
|
||||||
|
type = lib.types.listOf (lib.types.attrsOf lib.types.anything);
|
||||||
|
default = [ ];
|
||||||
|
description = "Bookmarks to be placed in the unsorted bookmarks folder.";
|
||||||
|
};
|
||||||
|
mobile = lib.mkOption {
|
||||||
|
type = lib.types.listOf (lib.types.attrsOf lib.types.anything);
|
||||||
|
default = [ ];
|
||||||
|
description = "Bookmarks to be placed in the mobile bookmarks folder. This folder is not visible on PC, but the bookmarks can still be searched for.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default = { };
|
||||||
|
description = "Declarative bookmarks for this profile.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
files =
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
path,
|
||||||
|
bookmarks,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
[
|
||||||
|
({
|
||||||
|
name = "${info.configPath}/${path}/bookmarkbackups/bookmarks-1970-01-01.json";
|
||||||
|
value.text = builtins.toJSON (updateBookmarkJSON {
|
||||||
|
title = "";
|
||||||
|
root = "placesRoot";
|
||||||
|
children = [
|
||||||
|
{
|
||||||
|
title = "menu";
|
||||||
|
root = "bookmarksMenuFolder";
|
||||||
|
children = bookmarks.menu;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
title = "toolbar";
|
||||||
|
root = "toolbarFolder";
|
||||||
|
children = bookmarks.toolbar;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
title = "unfiled";
|
||||||
|
root = "unfiledBookmarksFolder";
|
||||||
|
children = bookmarks.unfiled;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
title = "mobile";
|
||||||
|
root = "mobileFolder";
|
||||||
|
children = bookmarks.mobile;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
});
|
||||||
|
})
|
||||||
|
];
|
||||||
|
}
|
||||||
18
modules/home-manager/gecko-browser/gecko-lib/default.nix
Normal file
18
modules/home-manager/gecko-browser/gecko-lib/default.nix
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
args:
|
||||||
|
|
||||||
|
let
|
||||||
|
args' = args // {
|
||||||
|
inherit geckoLib;
|
||||||
|
};
|
||||||
|
|
||||||
|
geckoLib = {
|
||||||
|
bookmarks = import ./bookmarks.nix args';
|
||||||
|
extensions = import ./extensions.nix args';
|
||||||
|
policies = import ./policies.nix args';
|
||||||
|
prefs = import ./prefs.nix args';
|
||||||
|
profiles = import ./profiles.nix args';
|
||||||
|
search = import ./search.nix args';
|
||||||
|
site-settings = import ./site-settings.nix args';
|
||||||
|
};
|
||||||
|
in
|
||||||
|
geckoLib
|
||||||
157
modules/home-manager/gecko-browser/gecko-lib/extensions.nix
Normal file
157
modules/home-manager/gecko-browser/gecko-lib/extensions.nix
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
info,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
submodule =
|
||||||
|
{ name, ... }:
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
packages = lib.mkOption {
|
||||||
|
type =
|
||||||
|
lib.types.listOf
|
||||||
|
<| lib.types.either lib.types.package
|
||||||
|
<| lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
name = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = name;
|
||||||
|
example = "ublock-origin";
|
||||||
|
description = "The short name of the extension. To find the short name look at the extension's download link.";
|
||||||
|
};
|
||||||
|
|
||||||
|
guid = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
example = "uBlock0@raymondhill.net";
|
||||||
|
description = "The guid of the extension. To find the guid go to https://addons.mozilla.org/api/v5/addons/addon/\${shortName}/";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default = [ ];
|
||||||
|
description = ''
|
||||||
|
Extensions to install for this profile. Takes either a package or a description of an extension.
|
||||||
|
* If a package is given, the extension will be preinstalled.
|
||||||
|
* If a description is given then ${info.name} will download the extension on startup.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
settings = lib.mkOption {
|
||||||
|
type =
|
||||||
|
let
|
||||||
|
jsonFormat = pkgs.formats.json { };
|
||||||
|
in
|
||||||
|
lib.types.attrsOf jsonFormat.type;
|
||||||
|
default = { };
|
||||||
|
description = "Settings for this extension, accessible by the managed storage API. The extension must be referenced by id.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# (Package -> a) -> ({ name, guid } -> a) -> Extension -> a
|
||||||
|
withExtension =
|
||||||
|
f: g: x:
|
||||||
|
if lib.isDerivation x then f x else g x;
|
||||||
|
|
||||||
|
# ublock-origin wants its settings as a string, which is annoying
|
||||||
|
# to merge. For convenience, you can keep ublock-origin's config
|
||||||
|
# as a set and this function will turn it into a string at the
|
||||||
|
# last minute. As a bonus, we can also do other magic for other
|
||||||
|
# extensions if need be.
|
||||||
|
magicInfo = {
|
||||||
|
"uBlock0@raymondhill.net" =
|
||||||
|
x:
|
||||||
|
x
|
||||||
|
// lib.optionalAttrs (x ? adminSettings) {
|
||||||
|
adminSettings =
|
||||||
|
builtins.toJSON
|
||||||
|
<| (
|
||||||
|
y:
|
||||||
|
y
|
||||||
|
// lib.optionalAttrs (y ? userFilters) {
|
||||||
|
userFilters = builtins.concatStringsSep "\n" y.userFilters;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
<| (
|
||||||
|
y:
|
||||||
|
y
|
||||||
|
// lib.optionalAttrs (y ? urlFilteringString) {
|
||||||
|
urlFilteringString = builtins.concatStringsSep "\n" y.urlFilteringString;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
<| (
|
||||||
|
y:
|
||||||
|
y
|
||||||
|
// lib.optionalAttrs (y ? hostnameSwitchesString) {
|
||||||
|
hostnameSwitchesString = builtins.concatStringsSep "\n" y.hostnameSwitchesString;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
<| (
|
||||||
|
y:
|
||||||
|
y
|
||||||
|
// lib.optionalAttrs (y ? dynamicFilteringString) {
|
||||||
|
dynamicFilteringString = builtins.concatStringsSep "\n" y.dynamicFilteringString;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
<| x.adminSettings;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
magic = guid: magicInfo.${guid} or lib.trivial.id;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
extensions = lib.mkOption {
|
||||||
|
type = lib.types.submodule submodule;
|
||||||
|
default = { };
|
||||||
|
# example = {
|
||||||
|
# packages = [
|
||||||
|
# nur.repos.rycee.firefox-addons.ublock-origin
|
||||||
|
# { name = "decentraleyes"; guid = "jid1-BoFifL9Vbdl2zQ@jetpack"; }
|
||||||
|
# ];
|
||||||
|
# settings."uBlock0@raymondhill.net" = {
|
||||||
|
# whitelist = [ "example.org" ];
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
description = "The extensions to install.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
process =
|
||||||
|
{ extensions, ... }:
|
||||||
|
{
|
||||||
|
policies = {
|
||||||
|
"3rdparty".Extensions = extensions.settings;
|
||||||
|
ExtensionSettings =
|
||||||
|
builtins.listToAttrs
|
||||||
|
<| builtins.map (
|
||||||
|
withExtension
|
||||||
|
(x: {
|
||||||
|
name = x.addonId;
|
||||||
|
value = {
|
||||||
|
installation_mode = "force_installed";
|
||||||
|
private_browsing = true;
|
||||||
|
install_url = "file://${x}/share/mozilla/extensions/{ec8030f7-c20a-464f-9b0e-13a3a9e97384}/${x.addonId}.xpi";
|
||||||
|
};
|
||||||
|
})
|
||||||
|
(x: {
|
||||||
|
name = x.guid;
|
||||||
|
value = {
|
||||||
|
installation_mode = "force_installed";
|
||||||
|
private_browsing = true;
|
||||||
|
install_url = "https://addons.mozilla.org/firefox/downloads/latest/${x.name}/latest.xpi";
|
||||||
|
};
|
||||||
|
})
|
||||||
|
)
|
||||||
|
<| extensions.packages;
|
||||||
|
};
|
||||||
|
prefs."extensions.autoDisableScopes" = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
postprocess =
|
||||||
|
{ policies, ... }:
|
||||||
|
lib.optionalAttrs (policies ? "3rdparty".Extensions) {
|
||||||
|
policies."3rdparty".Extensions = builtins.mapAttrs magic <| policies."3rdparty".Extensions or { };
|
||||||
|
};
|
||||||
|
}
|
||||||
32
modules/home-manager/gecko-browser/gecko-lib/policies.nix
Normal file
32
modules/home-manager/gecko-browser/gecko-lib/policies.nix
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
{ pkgs, lib, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
type =
|
||||||
|
let
|
||||||
|
jsonFormat = pkgs.formats.json { };
|
||||||
|
in
|
||||||
|
jsonFormat.type;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
inherit type;
|
||||||
|
|
||||||
|
options = {
|
||||||
|
policies = lib.mkOption {
|
||||||
|
inherit type;
|
||||||
|
default = { };
|
||||||
|
description = "[See list of policies](https://mozilla.github.io/policy-templates/).";
|
||||||
|
example = {
|
||||||
|
DefaultDownloadDirectory = "\${home}/Downloads";
|
||||||
|
BlockAboutConfig = true;
|
||||||
|
ExtensionSettings = {
|
||||||
|
"uBlock0@raymondhill.net" = {
|
||||||
|
install_url = "https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi";
|
||||||
|
installation_mode = "force_installed";
|
||||||
|
default_area = "menupanel";
|
||||||
|
private_browsing = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
49
modules/home-manager/gecko-browser/gecko-lib/prefs.nix
Normal file
49
modules/home-manager/gecko-browser/gecko-lib/prefs.nix
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
info,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
type = lib.types.attrsOf (
|
||||||
|
lib.types.oneOf [
|
||||||
|
lib.types.int
|
||||||
|
lib.types.bool
|
||||||
|
lib.types.str
|
||||||
|
]
|
||||||
|
);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
inherit type;
|
||||||
|
|
||||||
|
options = {
|
||||||
|
prefs = lib.mkOption {
|
||||||
|
inherit type;
|
||||||
|
default = { };
|
||||||
|
description = "User preferences.";
|
||||||
|
};
|
||||||
|
|
||||||
|
lockPref = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
example = true;
|
||||||
|
description = "When adding user preferences, determine whether to use lockPref over user_pref";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
postprocess =
|
||||||
|
{ lockPref, prefs, ... }:
|
||||||
|
{
|
||||||
|
prefs =
|
||||||
|
builtins.concatStringsSep "\n"
|
||||||
|
<| (x: [ "// Generated by ${info.name} module" ] ++ x)
|
||||||
|
<| lib.mapAttrsToList (
|
||||||
|
let
|
||||||
|
prefFunc = if lockPref then "lockPref" else "user_pref";
|
||||||
|
in
|
||||||
|
key: val: ''${prefFunc}("${key}", ${builtins.toJSON val});''
|
||||||
|
)
|
||||||
|
<| prefs;
|
||||||
|
};
|
||||||
|
}
|
||||||
175
modules/home-manager/gecko-browser/gecko-lib/profiles.nix
Normal file
175
modules/home-manager/gecko-browser/gecko-lib/profiles.nix
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
sLib,
|
||||||
|
info,
|
||||||
|
geckoLib,
|
||||||
|
...
|
||||||
|
}@args:
|
||||||
|
|
||||||
|
let
|
||||||
|
# (a -> b -> c) -> [a] -> [b] -> [c]
|
||||||
|
mapProduct =
|
||||||
|
f: xs: ys:
|
||||||
|
builtins.concatMap (y: builtins.concatMap (x: f x y) xs) ys;
|
||||||
|
|
||||||
|
# (Lib -> Profile -> c) -> ProfileSettings -> [c]
|
||||||
|
mapLibAndProfile = f: x: mapProduct f allLibs <| builtins.attrValues <| x;
|
||||||
|
|
||||||
|
allLibs = builtins.attrValues <| lib.filterAttrs (k: _: k != "profiles") <| geckoLib;
|
||||||
|
allLibs' = allLibs ++ [ geckoLib.profiles ];
|
||||||
|
|
||||||
|
submodule =
|
||||||
|
{ config, name, ... }:
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
id = lib.mkOption {
|
||||||
|
type = lib.types.ints.unsigned;
|
||||||
|
default = 0;
|
||||||
|
description = "Profile ID";
|
||||||
|
};
|
||||||
|
|
||||||
|
name = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = name;
|
||||||
|
description = "Profile name";
|
||||||
|
};
|
||||||
|
|
||||||
|
path = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = name;
|
||||||
|
description = "Profile path";
|
||||||
|
};
|
||||||
|
|
||||||
|
isDefault = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = config.id == 0;
|
||||||
|
defaultText = "config.id == 0";
|
||||||
|
description = "Is profile the default?";
|
||||||
|
};
|
||||||
|
|
||||||
|
languagePacks = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ ];
|
||||||
|
example = [
|
||||||
|
"en-GB"
|
||||||
|
"de"
|
||||||
|
];
|
||||||
|
description = "The language packs to install.";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// builtins.foldl' (x: y: x // y.options) { } allLibs;
|
||||||
|
};
|
||||||
|
|
||||||
|
writeProfilesIni = configPath: profileSettings: {
|
||||||
|
name = "${configPath}/profiles.ini";
|
||||||
|
value.text =
|
||||||
|
lib.generators.toINI { }
|
||||||
|
<|
|
||||||
|
lib.mapAttrs' (
|
||||||
|
_: v:
|
||||||
|
lib.nameValuePair "Profile${builtins.toString v.id}" {
|
||||||
|
Name = v.name;
|
||||||
|
Path = v.path;
|
||||||
|
IsRelative = 1;
|
||||||
|
Default = if v.isDefault then 1 else 0;
|
||||||
|
}
|
||||||
|
) profileSettings
|
||||||
|
// {
|
||||||
|
General = {
|
||||||
|
StartWithLastProfile = 1;
|
||||||
|
Version = 2;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf (lib.types.submodule submodule);
|
||||||
|
default = { };
|
||||||
|
description = "Sets browser settings on a per-profile basis.";
|
||||||
|
};
|
||||||
|
|
||||||
|
getDefaultProfileName =
|
||||||
|
profileSettings:
|
||||||
|
builtins.head
|
||||||
|
<| builtins.filter (x: profileSettings.${x}.isDefault)
|
||||||
|
<| builtins.attrNames
|
||||||
|
<| profileSettings;
|
||||||
|
|
||||||
|
process =
|
||||||
|
{ languagePacks, ... }:
|
||||||
|
let
|
||||||
|
extractVersion =
|
||||||
|
(x: if x == [ ] then info.base.version else builtins.head x)
|
||||||
|
<| builtins.match "([^-]*)-.*"
|
||||||
|
<| info.base.version;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
policies.ExtensionSettings =
|
||||||
|
builtins.listToAttrs
|
||||||
|
<| builtins.map (lang: {
|
||||||
|
name = "langpack-${lang}@firefox.mozilla.org";
|
||||||
|
value = {
|
||||||
|
installation_mode = "force_installed";
|
||||||
|
install_url = "https://releases.mozilla.org/pub/firefox/releases/${extractVersion}/linux-x86_64/xpi/${lang}.xpi";
|
||||||
|
};
|
||||||
|
})
|
||||||
|
<| languagePacks;
|
||||||
|
};
|
||||||
|
|
||||||
|
assertions = profiles: [
|
||||||
|
{
|
||||||
|
assertion = profiles != { };
|
||||||
|
message = "No profiles are defined.";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion =
|
||||||
|
(x: x == 1)
|
||||||
|
<| builtins.length
|
||||||
|
<| builtins.filter (x: profiles.${x}.isDefault)
|
||||||
|
<| builtins.attrNames
|
||||||
|
<| profiles;
|
||||||
|
message = "One, and only one, profile must be marked as 'isDefault'. If you have not set this option on any profile, then one and only one profile must have id '0'.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
warnings = _: [ ];
|
||||||
|
|
||||||
|
evaluateAllProfiles =
|
||||||
|
profiles:
|
||||||
|
let
|
||||||
|
processLevel =
|
||||||
|
level: userConfig:
|
||||||
|
sLib.recursiveMergeAll
|
||||||
|
<| (x: [ userConfig ] ++ x)
|
||||||
|
<| builtins.map (myLib: (myLib.${level} or (_: { })) userConfig)
|
||||||
|
<| allLibs';
|
||||||
|
|
||||||
|
# Internal libraries can create their own configuration that needs to be merged
|
||||||
|
# into the user configuration (the user's takes priority). Generate that config.
|
||||||
|
finalConfig = builtins.mapAttrs (
|
||||||
|
_: v: processLevel "postprocess" <| processLevel "process" <| processLevel "preprocess" <| v
|
||||||
|
) profiles;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assertions =
|
||||||
|
mapLibAndProfile (x: x.assertions or (_: [ ])) finalConfig
|
||||||
|
++ geckoLib.profiles.assertions finalConfig;
|
||||||
|
|
||||||
|
warnings =
|
||||||
|
mapLibAndProfile (x: x.warnings or (_: [ ])) finalConfig ++ geckoLib.profiles.warnings finalConfig;
|
||||||
|
|
||||||
|
profiles = lib.mapAttrs (_: v: {
|
||||||
|
policies = v.policies;
|
||||||
|
prefs = v.prefs;
|
||||||
|
files = builtins.concatMap (x: (x.files or (_: [ ])) v) allLibs';
|
||||||
|
extFiles = builtins.concatMap (x: (x.extFiles or (_: [ ])) v) allLibs';
|
||||||
|
}) finalConfig;
|
||||||
|
|
||||||
|
files = [
|
||||||
|
(writeProfilesIni info.configPath finalConfig)
|
||||||
|
]
|
||||||
|
++ mapLibAndProfile (x: x.files or (_: [ ])) finalConfig;
|
||||||
|
};
|
||||||
|
}
|
||||||
118
modules/home-manager/gecko-browser/gecko-lib/search.nix
Normal file
118
modules/home-manager/gecko-browser/gecko-lib/search.nix
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
{ lib, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
submodule =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
default = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
example = "ddg";
|
||||||
|
description = "The default search engine used in the address bar and search bar.";
|
||||||
|
};
|
||||||
|
|
||||||
|
engines = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf (lib.types.submodule engineSubmodule);
|
||||||
|
default = { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
engineSubmodule =
|
||||||
|
{ name, ... }:
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
name = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = name;
|
||||||
|
};
|
||||||
|
|
||||||
|
searchUri = {
|
||||||
|
base = lib.mkOption {
|
||||||
|
type = lib.types.strMatching "[a-z]+://[-A-Za-z0-9.]+(:[0-9]+)?(/[^?]*)?";
|
||||||
|
};
|
||||||
|
|
||||||
|
params = lib.mkOption {
|
||||||
|
type = lib.types.listOf (
|
||||||
|
lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
key = lib.mkOption { type = lib.types.str; };
|
||||||
|
value = lib.mkOption { type = lib.types.str; };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
default = [ ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
method = lib.mkOption {
|
||||||
|
type = lib.types.enum [
|
||||||
|
"GET"
|
||||||
|
"POST"
|
||||||
|
];
|
||||||
|
default = "GET";
|
||||||
|
};
|
||||||
|
|
||||||
|
alias = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
icon = lib.mkOption {
|
||||||
|
type = lib.types.nullOr (
|
||||||
|
lib.types.oneOf [
|
||||||
|
lib.types.package
|
||||||
|
lib.types.str
|
||||||
|
]
|
||||||
|
);
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
buildPolicyFromEngine =
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
searchUri,
|
||||||
|
method,
|
||||||
|
alias,
|
||||||
|
icon,
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
params =
|
||||||
|
builtins.concatStringsSep "&" <| builtins.map (x: "${x.key}=${x.value}") <| searchUri.params;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
URLTemplate =
|
||||||
|
if method == "POST" then
|
||||||
|
searchUri.base
|
||||||
|
else if params == "" then
|
||||||
|
searchUri.base
|
||||||
|
else
|
||||||
|
"${searchUri.base}?${params}";
|
||||||
|
Method = method;
|
||||||
|
${if method == "POST" then "PostData" else null} = params;
|
||||||
|
${if icon != null then "IconURL" else null} = "file://${builtins.toString icon}";
|
||||||
|
${if alias != null then "Alias" else null} = alias;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
search = lib.mkOption {
|
||||||
|
type = lib.types.submodule submodule;
|
||||||
|
default = { };
|
||||||
|
description = "Declarative search engine configuration.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
process =
|
||||||
|
{ search, ... }:
|
||||||
|
{
|
||||||
|
policies.SearchEngines = {
|
||||||
|
${if search.default == null then null else "Default"} = search.default;
|
||||||
|
Add = lib.mapAttrsToList (_: buildPolicyFromEngine) search.engines;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
210
modules/home-manager/gecko-browser/gecko-lib/site-settings.nix
Normal file
210
modules/home-manager/gecko-browser/gecko-lib/site-settings.nix
Normal file
|
|
@ -0,0 +1,210 @@
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
sLib,
|
||||||
|
info,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
submodule = isDefault: {
|
||||||
|
options = {
|
||||||
|
https-only = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false; # if isDefault then false else config.default.https-only;
|
||||||
|
example = true;
|
||||||
|
description = "Whether ${
|
||||||
|
if isDefault then "all domains" else "this domain"
|
||||||
|
} should be accessed over https only.";
|
||||||
|
};
|
||||||
|
|
||||||
|
cookies = lib.mkOption {
|
||||||
|
type = lib.types.enum (
|
||||||
|
[
|
||||||
|
"forever"
|
||||||
|
"session"
|
||||||
|
"never"
|
||||||
|
true
|
||||||
|
false
|
||||||
|
]
|
||||||
|
++ lib.optional (!isDefault) null
|
||||||
|
);
|
||||||
|
default = if isDefault then "forever" else null;
|
||||||
|
example = "session";
|
||||||
|
description = ''
|
||||||
|
Determines how cookies are handled on this domain:
|
||||||
|
forever: Cookies are permitted
|
||||||
|
session: Cookies are permitted, but they are deleted when the session ends
|
||||||
|
never: Cookies are denied
|
||||||
|
true: Equivalent to "session"
|
||||||
|
false: Equivalent to "never"
|
||||||
|
${if isDefault then "" else "null: Use the default settings"}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
${if isDefault then null else "ubo"} = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
example = false;
|
||||||
|
description = "Whether ublock-origin should be enabled on this domain.";
|
||||||
|
};
|
||||||
|
|
||||||
|
scripts = lib.mkOption {
|
||||||
|
type = (if isDefault then (x: x) else lib.types.nullOr) lib.types.bool;
|
||||||
|
default = if isDefault then true else null;
|
||||||
|
example = false;
|
||||||
|
description = "Whether to allow JavaScript to run on websites.${
|
||||||
|
if isDefault then "" else " If null, use the default settings."
|
||||||
|
}";
|
||||||
|
};
|
||||||
|
|
||||||
|
fonts = lib.mkOption {
|
||||||
|
type = (if isDefault then (x: x) else lib.types.nullOr) lib.types.bool;
|
||||||
|
default = if isDefault then true else null;
|
||||||
|
example = false;
|
||||||
|
description = "Whether to allow websites to use remote fonts.";
|
||||||
|
};
|
||||||
|
|
||||||
|
popups = lib.mkOption {
|
||||||
|
type = (if isDefault then (x: x) else lib.types.nullOr) lib.types.bool;
|
||||||
|
default = if isDefault then true else null;
|
||||||
|
example = false;
|
||||||
|
description = "Whether to allow popups.";
|
||||||
|
};
|
||||||
|
|
||||||
|
largeMedia = lib.mkOption {
|
||||||
|
type = (if isDefault then (x: x) else lib.types.nullOr) lib.types.bool;
|
||||||
|
default = if isDefault then true else null;
|
||||||
|
example = false;
|
||||||
|
description = "Whether to allow large media.";
|
||||||
|
};
|
||||||
|
|
||||||
|
subdomain = {
|
||||||
|
allow = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ ];
|
||||||
|
example = [ "google.com" ];
|
||||||
|
description = "List of subdomains that ${
|
||||||
|
if isDefault then "any" else "this"
|
||||||
|
} domain can load resources from.";
|
||||||
|
};
|
||||||
|
block = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ ];
|
||||||
|
example = [ "google.com" ];
|
||||||
|
description = "List of subdomains that ${
|
||||||
|
if isDefault then "any" else "this"
|
||||||
|
} domain cannot load resources from.";
|
||||||
|
};
|
||||||
|
noop = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ ];
|
||||||
|
example = [ "google.com" ];
|
||||||
|
description = "List of subdomains that ${
|
||||||
|
if isDefault then "any" else "this"
|
||||||
|
} domain can load resources from, if a static filter does not override this rule.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
${if isDefault then null else "filters"} = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ ];
|
||||||
|
example = [ "#irritating-popup" ];
|
||||||
|
description = "List of ublock filters to apply to this domain.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
per-site = {
|
||||||
|
default = lib.mkOption {
|
||||||
|
type = lib.types.submodule (submodule true);
|
||||||
|
default = { };
|
||||||
|
description = "Sets options for all websites.";
|
||||||
|
};
|
||||||
|
|
||||||
|
site = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf (lib.types.submodule (submodule false));
|
||||||
|
default = { };
|
||||||
|
description = "Sets options on a per-website basis.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
preprocess =
|
||||||
|
{ per-site, ... }:
|
||||||
|
let
|
||||||
|
inherit (per-site) default site;
|
||||||
|
|
||||||
|
behavior =
|
||||||
|
if
|
||||||
|
builtins.elem default.cookies [
|
||||||
|
"forever"
|
||||||
|
"session"
|
||||||
|
true
|
||||||
|
]
|
||||||
|
then
|
||||||
|
"accept"
|
||||||
|
else
|
||||||
|
"reject";
|
||||||
|
|
||||||
|
filterCookies =
|
||||||
|
x:
|
||||||
|
builtins.concatLists
|
||||||
|
<| lib.mapAttrsToList (
|
||||||
|
k: v:
|
||||||
|
if builtins.elem v.cookies x then
|
||||||
|
[ "https://${k}/" ] ++ lib.optional (!v.https-only) "http://${k}/"
|
||||||
|
else
|
||||||
|
[ ]
|
||||||
|
)
|
||||||
|
<| site;
|
||||||
|
|
||||||
|
makeUboConf = domain: config: {
|
||||||
|
adminSettings = {
|
||||||
|
whitelist = lib.optional (!(config.ubo or true)) domain;
|
||||||
|
hostnameSwitchesString =
|
||||||
|
lib.optional (config.scripts != null) "no-scripting: ${domain} ${builtins.toJSON (!config.scripts)}"
|
||||||
|
++
|
||||||
|
lib.optional (config.fonts != null)
|
||||||
|
"no-remote-fonts: ${domain} ${builtins.toJSON (!config.fonts)}"
|
||||||
|
++ lib.optional (config.popups != null) "no-popups: ${domain} ${builtins.toJSON (!config.popups)}"
|
||||||
|
++
|
||||||
|
lib.optional (config.largeMedia != null)
|
||||||
|
"no-large-media: ${domain} ${builtins.toJSON (!config.largeMedia)}";
|
||||||
|
dynamicFilteringString =
|
||||||
|
builtins.map (x: "${domain} ${x} * allow") config.subdomain.allow
|
||||||
|
++ builtins.map (x: "${domain} ${x} * block") config.subdomain.block
|
||||||
|
++ builtins.map (x: "${domain} ${x} * noop") config.subdomain.noop;
|
||||||
|
userFilters = builtins.map (x: "${domain}##${x}") (config.filters or [ ]);
|
||||||
|
};
|
||||||
|
# toOverwrite = {
|
||||||
|
# trustedSiteDirectives = lib.optional (!(config.ubo or true)) domain;
|
||||||
|
# filters = builtins.map (x: "${domain}##${x}") (config.filters or []);
|
||||||
|
# };
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
policies = {
|
||||||
|
Cookies = {
|
||||||
|
Allow = filterCookies [ "forever" ];
|
||||||
|
AllowSession = filterCookies [
|
||||||
|
"session"
|
||||||
|
true
|
||||||
|
];
|
||||||
|
Block = filterCookies [
|
||||||
|
"never"
|
||||||
|
false
|
||||||
|
];
|
||||||
|
Behavior = behavior;
|
||||||
|
BehaviorPrivateBrowsing = behavior;
|
||||||
|
};
|
||||||
|
SanitizeOnShutdown.Cookies = builtins.elem default.cookies [
|
||||||
|
"session"
|
||||||
|
true
|
||||||
|
];
|
||||||
|
};
|
||||||
|
extensions.settings."uBlock0@raymondhill.net" =
|
||||||
|
sLib.recursiveMergeAll <| [ (makeUboConf "*" default) ] ++ lib.mapAttrsToList makeUboConf site;
|
||||||
|
};
|
||||||
|
}
|
||||||
91
modules/home-manager/gecko-browser/make-module.nix
Normal file
91
modules/home-manager/gecko-browser/make-module.nix
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
sLib: fork:
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
info =
|
||||||
|
{
|
||||||
|
"firefox" = {
|
||||||
|
name = "firefox";
|
||||||
|
base = pkgs.firefox-unwrapped;
|
||||||
|
binName = "firefox";
|
||||||
|
configPath = ".mozilla/firefox";
|
||||||
|
};
|
||||||
|
"librewolf" = {
|
||||||
|
name = "librewolf";
|
||||||
|
base = pkgs.librewolf-unwrapped;
|
||||||
|
binName = "librewolf";
|
||||||
|
configPath = ".librewolf";
|
||||||
|
};
|
||||||
|
"floorp" = {
|
||||||
|
name = "floorp";
|
||||||
|
base = pkgs.floorp-bin-unwrapped;
|
||||||
|
binName = "floorp"; # TODO: test
|
||||||
|
configPath = ".floorp"; # TODO: test
|
||||||
|
};
|
||||||
|
}
|
||||||
|
.${fork};
|
||||||
|
|
||||||
|
cfg = config.hylonix.${info.name};
|
||||||
|
geckoLib = import ./gecko-lib {
|
||||||
|
inherit
|
||||||
|
geckoLib
|
||||||
|
info
|
||||||
|
lib
|
||||||
|
pkgs
|
||||||
|
sLib
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
|
finalConfig = geckoLib.profiles.evaluateAllProfiles cfg.profiles;
|
||||||
|
|
||||||
|
buildFinal =
|
||||||
|
profile: settings:
|
||||||
|
let
|
||||||
|
pkg = (
|
||||||
|
pkgs.wrapFirefox info.base {
|
||||||
|
extraPolicies = settings.policies;
|
||||||
|
extraPrefs = settings.prefs;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
package = pkg;
|
||||||
|
command = "${pkg}/bin/${info.binName} -P ${profile}";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.hylonix.${info.name} = {
|
||||||
|
enable = lib.mkEnableOption fork;
|
||||||
|
|
||||||
|
final = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf (
|
||||||
|
lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
package = lib.mkOption { type = lib.types.package; };
|
||||||
|
command = lib.mkOption { type = lib.types.str; };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
visible = false;
|
||||||
|
readOnly = true;
|
||||||
|
description = "Resulting browser information for this profile.";
|
||||||
|
};
|
||||||
|
|
||||||
|
profiles = geckoLib.profiles.options;
|
||||||
|
}; # end options
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
inherit (finalConfig) assertions warnings;
|
||||||
|
|
||||||
|
hylonix.${info.name}.final = lib.mapAttrs buildFinal finalConfig.profiles;
|
||||||
|
|
||||||
|
home.file = builtins.listToAttrs finalConfig.files;
|
||||||
|
|
||||||
|
home.packages = [ cfg.final.${geckoLib.profiles.getDefaultProfileName cfg.profiles}.package ];
|
||||||
|
}; # end config
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue