mirror of
https://github.com/elwrcl/heliux.git
synced 2026-10-31 14:38:02 +03:00
395 lines
13 KiB
Nix
395 lines
13 KiB
Nix
{ self }:
|
|
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
let
|
|
cfg = config.programs.helium;
|
|
webStorePlatform =
|
|
let
|
|
inherit (pkgs.stdenv.hostPlatform) isDarwin isAarch64;
|
|
in
|
|
{
|
|
os = if isDarwin then "mac" else "linux";
|
|
arch = if isAarch64 then "arm64" else "x64";
|
|
os_arch =
|
|
if !isAarch64 then
|
|
"x86_64"
|
|
else if isDarwin then
|
|
"arm64"
|
|
else
|
|
"aarch64";
|
|
nacl_arch = if isAarch64 then "arm" else "x86-64";
|
|
};
|
|
|
|
# Shared by fetchExtension and the prefetch-nix wrapper, so a hash obtained by
|
|
# hand is always a hash for the CRX the module will go on to download.
|
|
crxUrl =
|
|
id:
|
|
"https://clients2.google.com/service/update2/crx"
|
|
+ "?response=redirect"
|
|
+ "&os=${webStorePlatform.os}"
|
|
+ "&arch=${webStorePlatform.arch}"
|
|
+ "&os_arch=${webStorePlatform.os_arch}"
|
|
+ "&nacl_arch=${webStorePlatform.nacl_arch}"
|
|
+ "&prod=chromiumcrx&prodchannel=stable"
|
|
+ "&prodversion=${cfg.prodversion}"
|
|
+ "&acceptformat=crx3"
|
|
+ "&x=id%3D${id}%26installsource%3Dondemand%26uc";
|
|
|
|
fetchExtension =
|
|
{ id, hash }:
|
|
pkgs.fetchurl {
|
|
name = "${id}.crx";
|
|
url = crxUrl id;
|
|
inherit hash;
|
|
};
|
|
|
|
unpackExtension =
|
|
{ id, hash }:
|
|
pkgs.runCommand "helium-ext-${id}"
|
|
{
|
|
nativeBuildInputs = [ pkgs.unzip ];
|
|
src = fetchExtension { inherit id hash; };
|
|
}
|
|
''
|
|
if [ ! -s "$src" ]; then
|
|
echo "error: the CRX for ${id} is empty." >&2
|
|
echo "The Chrome Web Store returned no content, which usually means" >&2
|
|
echo "programs.helium.prodversion (${cfg.prodversion}) is older than the" >&2
|
|
echo "extension supports. Raise it and re-run the hash prefetch." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$(head -c 4 "$src")" != "Cr24" ]; then
|
|
echo "error: the CRX for ${id} is not a CRX3 file." >&2
|
|
echo "The Chrome Web Store served something else — an error or rate" >&2
|
|
echo "limit page, most likely. Re-run the hash prefetch." >&2
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p $out
|
|
unzip -q $src -d $out || [ "$?" -le 1 ]
|
|
|
|
# Remove the system-reserved metadata folder that causes the load error
|
|
rm -rf $out/_metadata
|
|
|
|
if [ ! -f "$out/manifest.json" ]; then
|
|
echo "error: the CRX for ${id} unpacked without a manifest.json." >&2
|
|
exit 1
|
|
fi
|
|
'';
|
|
|
|
unpackedMode = cfg.extensionInstallMode == "unpacked";
|
|
|
|
resolvedExtensions = lib.optionals unpackedMode (
|
|
map (spec: {
|
|
inherit (spec) id;
|
|
unpacked = unpackExtension { inherit (spec) id hash; };
|
|
}) cfg.extensions
|
|
);
|
|
|
|
webStoreUpdateUrl = "https://clients2.google.com/service/update2/crx?prodversion=${cfg.prodversion}";
|
|
|
|
policyAttrs = {
|
|
ExtensionInstallAllowlist = map (ext: ext.id) cfg.extensions;
|
|
}
|
|
// lib.optionalAttrs (!unpackedMode && cfg.extensions != [ ]) {
|
|
ExtensionInstallForcelist = map (ext: "${ext.id};${webStoreUpdateUrl}") cfg.extensions;
|
|
}
|
|
// cfg.extraPolicies;
|
|
userDataDir = "${config.xdg.configHome}/net.imput.helium";
|
|
widevineCdm =
|
|
let
|
|
subdir = cfg.package.widevineCdmSubdir or null;
|
|
in
|
|
if subdir == null then null else "${cfg.package}/${subdir}";
|
|
|
|
widevineHintFile = "${userDataDir}/WidevineCdm/latest-component-updated-widevine-cdm";
|
|
|
|
extensionRoot = "${config.xdg.dataHome}/helium/extensions";
|
|
loadExtensionFlag =
|
|
if resolvedExtensions != [ ] then
|
|
[
|
|
"--load-extension=${
|
|
lib.concatMapStringsSep "," (ext: "${extensionRoot}/${ext.id}") resolvedExtensions
|
|
}"
|
|
]
|
|
else
|
|
[ ];
|
|
|
|
enabledFeatures = lib.unique (
|
|
(cfg.package.enabledFeatures or [ ])
|
|
++ lib.optionals pkgs.stdenv.hostPlatform.isLinux [
|
|
"NativeNotifications"
|
|
"SystemNotifications"
|
|
]
|
|
++ cfg.extraFeatures
|
|
);
|
|
|
|
heliumWithFlags = pkgs.symlinkJoin {
|
|
name = "helium-configured";
|
|
paths = [ cfg.package ];
|
|
nativeBuildInputs = [ pkgs.makeWrapper ];
|
|
postBuild = ''
|
|
wrapProgram $out/bin/helium \
|
|
${lib.concatMapStringsSep " \\\n " (f: "--add-flags ${lib.escapeShellArg f}") (
|
|
[
|
|
"--disable-component-update"
|
|
]
|
|
++ lib.optional cfg.allowFileAccessFromFiles "--allow-file-access-from-files"
|
|
++ loadExtensionFlag
|
|
++ lib.optionals pkgs.stdenv.hostPlatform.isLinux [
|
|
"--ozone-platform-hint=auto"
|
|
]
|
|
++ lib.optional (
|
|
enabledFeatures != [ ]
|
|
) "--enable-features=${lib.concatStringsSep "," enabledFeatures}"
|
|
++ cfg.extraFlags
|
|
)}
|
|
|
|
if [ -e $out/bin/prefetch-nix ]; then
|
|
wrapProgram $out/bin/prefetch-nix \
|
|
--set-default HELIUM_PRODVERSION ${lib.escapeShellArg cfg.prodversion} \
|
|
--set-default HELIUM_OS ${lib.escapeShellArg webStorePlatform.os} \
|
|
--set-default HELIUM_ARCH ${lib.escapeShellArg webStorePlatform.arch} \
|
|
--set-default HELIUM_OS_ARCH ${lib.escapeShellArg webStorePlatform.os_arch} \
|
|
--set-default HELIUM_NACL_ARCH ${lib.escapeShellArg webStorePlatform.nacl_arch}
|
|
fi
|
|
'';
|
|
};
|
|
|
|
in
|
|
{
|
|
options.programs.helium = {
|
|
enable = lib.mkEnableOption "Helium browser";
|
|
package = lib.mkOption {
|
|
type = lib.types.package;
|
|
default = self.packages.${pkgs.stdenv.hostPlatform.system}.helium;
|
|
};
|
|
extensions = lib.mkOption {
|
|
type = lib.types.listOf (
|
|
lib.types.submodule {
|
|
options = {
|
|
id = lib.mkOption { type = lib.types.str; };
|
|
hash = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = ''
|
|
CRX hash, only needed when extensionInstallMode is "unpacked".
|
|
'';
|
|
};
|
|
};
|
|
}
|
|
);
|
|
default = [ ];
|
|
};
|
|
extensionInstallMode = lib.mkOption {
|
|
type = lib.types.enum [
|
|
"policy"
|
|
"unpacked"
|
|
];
|
|
default = "policy";
|
|
description = ''
|
|
How `extensions` reach the browser.
|
|
|
|
"policy" force-installs them through the ExtensionInstallForcelist
|
|
Chromium policy. The browser downloads each extension itself, so the
|
|
Web Store IDs and signatures are preserved (native messaging hosts
|
|
such as KeePassXC depend on the real ID), the install is recorded in
|
|
the profile, and extensions keep updating themselves. Requires the
|
|
NixOS module so the policy file lands in /etc/chromium/policies.
|
|
|
|
"unpacked" pins the CRXs in the Nix store and passes them via
|
|
--load-extension. It is reproducible and works without the NixOS
|
|
module, but Chromium treats command-line extensions as freshly
|
|
installed on every start, which re-fires each extension's onInstalled
|
|
handler (welcome tabs on every launch), and derives IDs from the load
|
|
path unless the manifest carries a "key".
|
|
'';
|
|
};
|
|
prodversion = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "151.0.0.0";
|
|
description = ''
|
|
Chrome version advertised to the Web Store when downloading extensions.
|
|
|
|
The store replies 204 No Content for any extension that requires a newer
|
|
browser than this, and an empty body still hashes fine, so too low a
|
|
value yields extensions that build but are empty. Keep it at or above
|
|
the Chromium version Helium is based on.
|
|
'';
|
|
};
|
|
extraFlags = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ ];
|
|
description = ''
|
|
Extra command line arguments added to the wrapper.
|
|
|
|
Do not pass `--enable-features` here: Chromium keeps a single value per
|
|
switch, so it would replace the feature list the wrapper builds instead
|
|
of adding to it. Use `extraFeatures` for that.
|
|
'';
|
|
};
|
|
extraFeatures = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ ];
|
|
description = ''
|
|
Additional Chromium features, merged into the single `--enable-features`
|
|
switch alongside the ones the package and this module already enable.
|
|
'';
|
|
example = [ "VaapiVideoDecodeLinuxGL" ];
|
|
};
|
|
allowFileAccessFromFiles = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = ''
|
|
Pass `--allow-file-access-from-files`.
|
|
|
|
This drops the same-origin restriction for `file://` pages, so any local
|
|
HTML file you open — including one you just downloaded — can read other
|
|
files on disk and send them off. Only enable it if you specifically need
|
|
local pages to load local resources.
|
|
'';
|
|
};
|
|
extraPolicies = lib.mkOption {
|
|
type = lib.types.attrsOf lib.types.anything;
|
|
default = { };
|
|
};
|
|
defaultBrowser = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
};
|
|
|
|
finalPolicyJson = lib.mkOption {
|
|
type = lib.types.str;
|
|
internal = true;
|
|
default = builtins.toJSON policyAttrs;
|
|
};
|
|
|
|
preferences = lib.mkOption {
|
|
type = lib.types.attrsOf lib.types.anything;
|
|
default = { };
|
|
description = ''
|
|
Chromium preferences to set in the Default profile.
|
|
These are merged into ~/.config/net.imput.helium/Default/Preferences.
|
|
Type: 'helium://prefs-internals/' to search for the json keys and values
|
|
|
|
The merge happens once, during activation. Chromium owns this file at
|
|
runtime and rewrites it from memory when it exits, so close the browser
|
|
before activating or your settings are written back over. Preferences
|
|
that Chromium protects with a MAC in Secure Preferences cannot be set
|
|
this way at all — it notices the outside edit and resets them. Use
|
|
`extraPolicies` for anything that has to stick.
|
|
'';
|
|
example = lib.literalExpression ''
|
|
{
|
|
"browser"."show_home_button" = true;
|
|
}
|
|
'';
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
assertions = map (ext: {
|
|
assertion = ext.hash != null;
|
|
message = ''
|
|
programs.helium.extensions."${ext.id}".hash is required when
|
|
programs.helium.extensionInstallMode = "unpacked".
|
|
'';
|
|
}) (lib.optionals unpackedMode cfg.extensions);
|
|
|
|
home = {
|
|
packages = [
|
|
heliumWithFlags
|
|
pkgs.jq
|
|
pkgs.coreutils
|
|
];
|
|
|
|
activation = {
|
|
heliumExtensions = lib.hm.dag.entryAfter [ "writeBoundary" ] (
|
|
if resolvedExtensions != [ ] then
|
|
''
|
|
run rm -rf ${lib.escapeShellArg extensionRoot}
|
|
run mkdir -p ${lib.escapeShellArg extensionRoot}
|
|
${lib.concatMapStringsSep "\n" (ext: ''
|
|
run cp -r ${ext.unpacked} ${lib.escapeShellArg "${extensionRoot}/${ext.id}"}
|
|
'') resolvedExtensions}
|
|
run chmod -R u+w ${lib.escapeShellArg extensionRoot}
|
|
''
|
|
else
|
|
''
|
|
run rm -rf ${lib.escapeShellArg extensionRoot}
|
|
''
|
|
);
|
|
|
|
heliumWidevine = lib.hm.dag.entryAfter [ "writeBoundary" ] (
|
|
if widevineCdm != null then
|
|
''
|
|
run ${lib.getExe' pkgs.coreutils "install"} -Dm644 \
|
|
${pkgs.writeText "helium-widevine-cdm-hint" (builtins.toJSON { Path = widevineCdm; })} \
|
|
${lib.escapeShellArg widevineHintFile}
|
|
''
|
|
else
|
|
''
|
|
if [ -f ${lib.escapeShellArg widevineHintFile} ] \
|
|
&& grep -q '"/nix/store/' ${lib.escapeShellArg widevineHintFile}; then
|
|
run rm -f ${lib.escapeShellArg widevineHintFile}
|
|
fi
|
|
''
|
|
);
|
|
|
|
heliumPreferences = lib.mkIf (cfg.preferences != { }) (
|
|
lib.hm.dag.entryAfter [ "writeBoundary" ] ''
|
|
prefs_file=${lib.escapeShellArg "${userDataDir}/Default/Preferences"}
|
|
nix_prefs=${lib.escapeShellArg (builtins.toJSON cfg.preferences)}
|
|
merged_prefs="$(mktemp)"
|
|
|
|
if [ -f "$prefs_file" ]; then
|
|
if ${lib.getExe pkgs.jq} -s '.[0] * .[1]' "$prefs_file" - \
|
|
<<< "$nix_prefs" > "$merged_prefs"; then
|
|
run ${lib.getExe' pkgs.coreutils "install"} -Dm600 \
|
|
"$merged_prefs" "$prefs_file"
|
|
else
|
|
warnEcho "helium: $prefs_file is not valid JSON; leaving it alone."
|
|
fi
|
|
else
|
|
printf '%s\n' "$nix_prefs" > "$merged_prefs"
|
|
run ${lib.getExe' pkgs.coreutils "install"} -Dm600 \
|
|
"$merged_prefs" "$prefs_file"
|
|
fi
|
|
|
|
rm -f "$merged_prefs"
|
|
''
|
|
);
|
|
};
|
|
};
|
|
|
|
xdg.mimeApps = lib.mkIf cfg.defaultBrowser {
|
|
enable = true;
|
|
defaultApplications = {
|
|
"text/html" = "helium.desktop";
|
|
"x-scheme-handler/http" = "helium.desktop";
|
|
"x-scheme-handler/https" = "helium.desktop";
|
|
};
|
|
};
|
|
xdg.desktopEntries.helium = lib.mkIf cfg.defaultBrowser {
|
|
name = "Helium";
|
|
exec = "${heliumWithFlags}/bin/helium %U";
|
|
icon = "helium";
|
|
terminal = false;
|
|
categories = [
|
|
"Network"
|
|
"WebBrowser"
|
|
];
|
|
mimeType = [
|
|
"text/html"
|
|
"x-scheme-handler/http"
|
|
"x-scheme-handler/https"
|
|
];
|
|
};
|
|
};
|
|
}
|