This commit is contained in:
elwrcl committed 2026-08-28 17:23:24 +03:00
1 parent f7e247b0a5
commit 7d815112ab
6 files changed
+245 -90

No files matched your search

-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2026 ntgn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+48 -5
View File
@@ -117,11 +117,33 @@ The following options are available under `programs.helium`:
| `package` | package | `self.packages.helium` | The helium package to use. |
| `extensions` | list of submodules | `[]` | Extensions to install: `{ id, hash }` (`hash` only needed in `unpacked` mode). |
| `extensionInstallMode` | `"policy"` or `"unpacked"` | `"policy"` | How extensions are installed — see below. |
| `extraFlags` | list of strings | `[]` | Command line arguments passed to the wrapper. |
| `extraFlags` | list of strings | `[]` | Command line arguments passed to the wrapper. Do not pass `--enable-features` here — use `extraFeatures`. |
| `extraFeatures` | list of strings | `[]` | Chromium features merged into the single `--enable-features` switch. |
| `allowFileAccessFromFiles` | boolean | `false` | Pass `--allow-file-access-from-files`. Lets any local `file://` page read other files on disk — see below. |
| `extraPolicies` | attribute set | `{}` | Raw Chromium policies to apply. |
| `preferences` | attribute set | `{}` | Json that will be merged into XDG Config. |
| `defaultBrowser` | boolean | `false` | Set Helium as the default browser in XDG mimeapps. |
## Flags and features
Chromium stores one value per command line switch, so a second
`--enable-features=…` silently *replaces* the first instead of adding to it.
Both the package and the home-manager wrapper need to enable features, so they
build a single combined switch: the package exposes its list as
`passthru.enabledFeatures`, and the module merges its own additions and your
`extraFeatures` into it.
That is why features go in `extraFeatures` rather than `extraFlags` — a raw
`--enable-features` in `extraFlags` would come last and wipe out everything
else.
> [!WARNING]
> `allowFileAccessFromFiles` drops the same-origin restriction for `file://`
> pages. Any local HTML file you open — including one you just downloaded — can
> then read other files on your disk and send them anywhere. It is off by
> default; only turn it on if you specifically need local pages to load local
> resources.
## Policies
Since Helium is based on Chromium, you can use any of the standard Chromium
@@ -140,6 +162,14 @@ These are usually what you imperatively choose in the `Settings` menu. You can
find all the json keys and values inside the helium browser by typing
`helium://prefs-internals/` and searching for the values.
> [!IMPORTANT]
> The merge happens once, at activation time. Chromium owns this file while it
> runs and rewrites it from memory on exit, so close the browser before you
> activate or your settings get written back over. Preferences that Chromium
> protects with a MAC in `Secure Preferences` cannot be set this way at all — it
> spots the outside edit and resets them. Use `extraPolicies` for anything that
> has to stick.
```nix
{
programs.helium.preferences = {
@@ -182,10 +212,23 @@ entry needs a `hash` in this mode; see below.
Only needed for `extensionInstallMode = "unpacked"`.
> [!WARNING]
> Be wary that if you are rate-limited that the file will be empty and the build will fail
Use the `prefetch-nix` binary, which the package installs alongside the browser,
to obtain the nix code you need.
Use the provided binary `prefetch-nix-extension` to obtain the nix code you need.
The Web Store decides which build of an extension to serve from the
`prodversion`, `os` and `arch` in the request, so a hash is only valid for the
exact URL it came from. `prefetch-nix` is wrapped by the home-manager module so
it already asks for the same URL the module will later fetch. If you run the
script straight out of the repo instead, override the defaults with
`HELIUM_PRODVERSION`, `HELIUM_OS`, `HELIUM_ARCH`, `HELIUM_OS_ARCH` and
`HELIUM_NACL_ARCH` to match your `programs.helium.prodversion` and target
platform, or the hash will not match at build time.
> [!NOTE]
> If the store rate-limits you it answers with an error page rather than a CRX.
> `prefetch-nix` checks for the CRX3 magic and reports `# FAILED` instead of
> printing a hash for it, and the build refuses such a file too — so a
> rate-limited fetch can never end up as a silently empty extension.
You can copy the IDs from the URL in the chrome web store:
@@ -197,7 +240,7 @@ https://chromewebstore.google.com/detail/bitwarden-password-manage/nngceckbapebf
Example Usage:
```console
./prefetch-nix-extension.sh nngceckbapebfimnlniiiahkandclblb cjpalhdlnbpafiamejdnhcphjbkeiagm
prefetch-nix nngceckbapebfimnlniiiahkandclblb cjpalhdlnbpafiamejdnhcphjbkeiagm
# OUTPUT:
extensions = [
+117 -27
View File
@@ -7,17 +7,43 @@
}:
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 }:
let
os = if pkgs.stdenv.isDarwin then "mac" else "linux";
arch = if pkgs.stdenv.isAarch64 then "arm64" else "x64";
os_arch = if pkgs.stdenv.isDarwin then "arm64" else "x86_64";
in
pkgs.fetchurl {
name = "${id}.crx";
url = "https://clients2.google.com/service/update2/crx?response=redirect&os=${os}&arch=${arch}&os_arch=${os_arch}&nacl_arch=x86-64&prod=chromiumcrx&prodchannel=stable&prodversion=${cfg.prodversion}&acceptformat=crx3&x=id%3D${id}%26installsource%3Dondemand%26uc";
url = crxUrl id;
inherit hash;
};
@@ -29,10 +55,6 @@ let
src = fetchExtension { inherit id hash; };
}
''
# The Web Store answers 204 No Content for extensions that need a newer
# browser than `prodversion` claims. fetchurl stores that empty body
# happily, so without this check the build succeeds and the extension is
# silently missing at runtime.
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
@@ -41,11 +63,23 @@ let
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 || true
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";
@@ -57,10 +91,6 @@ let
}) cfg.extensions
);
# Helium's update requests carry no prodversion, and the Web Store answers
# every one of them with <updatecheck status="noupdate"/> — force-installs
# then silently do nothing. Pinning it in the update URL survives into the
# request Chromium builds, since it only appends its own query parameters.
webStoreUpdateUrl = "https://clients2.google.com/service/update2/crx?prodversion=${cfg.prodversion}";
policyAttrs = {
@@ -82,6 +112,15 @@ let
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 ];
@@ -91,15 +130,26 @@ let
${lib.concatMapStringsSep " \\\n " (f: "--add-flags ${lib.escapeShellArg f}") (
[
"--disable-component-update"
"--allow-file-access-from-files"
]
++ lib.optional cfg.allowFileAccessFromFiles "--allow-file-access-from-files"
++ loadExtensionFlag
++ lib.optionals pkgs.stdenv.isLinux [
++ lib.optionals pkgs.stdenv.hostPlatform.isLinux [
"--ozone-platform-hint=auto"
"--enable-features=WaylandWindowDecorations,NativeNotifications,SystemNotifications"
]
++ 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
'';
};
@@ -167,6 +217,34 @@ in
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;
@@ -190,6 +268,13 @@ in
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 ''
{
@@ -234,20 +319,25 @@ in
heliumPreferences = lib.mkIf (cfg.preferences != { }) (
lib.hm.dag.entryAfter [ "writeBoundary" ] ''
prefs_dir="$HOME/.config/net.imput.helium/Default"
prefs_file="$prefs_dir/Preferences"
nix_prefs='${builtins.toJSON cfg.preferences}'
run mkdir -p "$prefs_dir"
prefs_file="$HOME/.config/net.imput.helium/Default/Preferences"
nix_prefs=${lib.escapeShellArg (builtins.toJSON cfg.preferences)}
merged_prefs="$(mktemp)"
if [ -f "$prefs_file" ]; then
merged=$(${pkgs.jq}/bin/jq -s '.[0] * .[1]' "$prefs_file" - <<< "$nix_prefs")
if [ -n "$merged" ]; then
printf '%s\n' "$merged" > "$prefs_file"
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" > "$prefs_file"
printf '%s\n' "$nix_prefs" > "$merged_prefs"
run ${lib.getExe' pkgs.coreutils "install"} -Dm600 \
"$merged_prefs" "$prefs_file"
fi
rm -f "$merged_prefs"
''
);
};
+3 -2
View File
@@ -5,7 +5,8 @@
let
sources = import ./sources.nix;
version = if pkgs.stdenv.isDarwin then sources.versions.darwin else sources.versions.linux;
version =
if pkgs.stdenv.hostPlatform.isDarwin then sources.versions.darwin else sources.versions.linux;
src = pkgs.fetchurl (
(sources.srcs.${system} or (throw "Unsupported system: ${system}")) sources.versions
@@ -36,7 +37,7 @@
default = helium;
inherit helium;
}
// pkgs.lib.optionalAttrs pkgs.stdenv.isLinux { inherit helium-widevine; };
// pkgs.lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux { inherit helium-widevine; };
apps = {
default = app;
+22 -18
View File
@@ -58,19 +58,24 @@
src,
}:
let
enabledFeatures = [
"StorageAccessAPI"
]
++ lib.optionals stdenv.hostPlatform.isLinux [
"WaylandWindowDecorations"
];
# Chromium flags applied on all platforms to disable update machinery.
commonFlags = [
"--disable-component-update"
"--simulate-outdated-no-au='Tue, 31 Dec 2099 23:59:59 GMT'"
"--check-for-update-interval=0"
"--no-first-run"
"--enable-features=StorageAccessAPI"
"--enable-features=${lib.concatStringsSep "," enabledFeatures}"
"--restore-last-session"
];
addFlags = lib.concatMapStringsSep " \\\n " (f: "--add-flags \"${f}\"");
# Libraries that must appear on LD_LIBRARY_PATH at runtime on Linux.
linuxRuntimeLibs = [
libGL
libvdpau
@@ -87,13 +92,13 @@ stdenv.mkDerivation {
nativeBuildInputs = [
makeWrapper
]
++ lib.optionals stdenv.isLinux [
++ lib.optionals stdenv.hostPlatform.isLinux [
autoPatchelfHook
copyDesktopItems
]
++ lib.optionals stdenv.isDarwin [ _7zz ];
++ lib.optionals stdenv.hostPlatform.isDarwin [ _7zz ];
buildInputs = lib.optionals stdenv.isLinux [
buildInputs = lib.optionals stdenv.hostPlatform.isLinux [
alsa-lib
at-spi2-atk
at-spi2-core
@@ -138,7 +143,7 @@ stdenv.mkDerivation {
];
# QT libraries are bundled; suppress autoPatchelf warnings for them.
autoPatchelfIgnoreMissingDeps = lib.optionals stdenv.isLinux [
autoPatchelfIgnoreMissingDeps = lib.optionals stdenv.hostPlatform.isLinux [
"libQt6Core.so.6"
"libQt6Gui.so.6"
"libQt6Widgets.so.6"
@@ -147,17 +152,17 @@ stdenv.mkDerivation {
"libQt5Widgets.so.5"
];
dontWrapQtApps = stdenv.isLinux;
unpackCmd = lib.optionalString stdenv.isDarwin "7zz x $src";
dontWrapQtApps = stdenv.hostPlatform.isLinux;
unpackCmd = lib.optionalString stdenv.hostPlatform.isDarwin "7zz x -odmg-contents \"$curSrc\"";
sourceRoot = lib.optionalString stdenv.hostPlatform.isDarwin "dmg-contents";
installPhase =
if stdenv.isDarwin then
if stdenv.hostPlatform.isDarwin then
''
runHook preInstall
mkdir -p $out/Applications/Helium.app
cp -r . $out/Applications/Helium.app
mkdir -p $out/Applications
cp -r Helium.app $out/Applications/Helium.app
mkdir -p $out/bin
makeWrapper $out/Applications/Helium.app/Contents/MacOS/Helium $out/bin/helium \
@@ -171,14 +176,12 @@ stdenv.mkDerivation {
mkdir -p $out/bin $out/opt/helium
cp -r * $out/opt/helium
${lib.optionalString enableWidevine ''
${lib.optionalString enableWidevine ''
cp -r ${widevine-cdm}/share/google/chrome/WidevineCdm $out/opt/helium/WidevineCdm
''}
''}
makeWrapper $out/opt/helium/helium $out/bin/helium \
--prefix LD_LIBRARY_PATH : "${lib.makeLibraryPath linuxRuntimeLibs}" \
--add-flags "--ozone-platform-hint=auto" \
--add-flags "--enable-features=WaylandWindowDecorations" \
${addFlags commonFlags}
mkdir -p $out/share/icons/hicolor/256x256/apps
@@ -195,7 +198,7 @@ ${lib.optionalString enableWidevine ''
chmod +x $out/bin/prefetch-nix
'';
desktopItems = lib.optionals stdenv.isLinux [
desktopItems = lib.optionals stdenv.hostPlatform.isLinux [
(makeDesktopItem {
name = "helium";
exec = "helium %U";
@@ -217,6 +220,7 @@ ${lib.optionalString enableWidevine ''
})
];
passthru = { inherit enabledFeatures; };
meta = {
description = "Private, fast, and honest web browser based on ungoogled-chromium";
homepage = "https://helium.computer/";
+55 -17
View File
@@ -1,39 +1,75 @@
#!/usr/bin/env bash
# check if at least one ID was provided
set -euo pipefail
if [ $# -eq 0 ]; then
echo "Usage: fetchext <ID> [<ID> ...]"
echo "Example: fetchext nngceckbapebfimnlniiiahkandclblb"
cat >&2 <<'USAGE'
Usage: prefetch-nix <ID> [<ID> ...]
Example: prefetch-nix nngceckbapebfimnlniiiahkandclblb
Environment (defaults follow programs.helium and the host platform):
HELIUM_PRODVERSION Chrome version advertised to the Web Store
HELIUM_OS "linux" or "mac"
HELIUM_ARCH "x64" or "arm64"
HELIUM_OS_ARCH uname machine name, e.g. "x86_64" / "aarch64" / "arm64"
HELIUM_NACL_ARCH "x86-64" or "arm"
USAGE
exit 1
fi
case "$(uname -s)" in
Darwin) default_os="mac" ;;
*) default_os="linux" ;;
esac
UA="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
case "$(uname -m)" in
arm64 | aarch64)
default_arch="arm64"
default_nacl_arch="arm"
if [ "$default_os" = "mac" ]; then
default_os_arch="arm64"
else
default_os_arch="aarch64"
fi
;;
*)
default_arch="x64"
default_nacl_arch="x86-64"
default_os_arch="x86_64"
;;
esac
prodversion="${HELIUM_PRODVERSION:-151.0.0.0}"
os="${HELIUM_OS:-$default_os}"
arch="${HELIUM_ARCH:-$default_arch}"
os_arch="${HELIUM_OS_ARCH:-$default_os_arch}"
nacl_arch="${HELIUM_NACL_ARCH:-$default_nacl_arch}"
UA="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${prodversion} Safari/537.36"
echo "# prodversion=$prodversion os=$os arch=$arch os_arch=$os_arch" >&2
# print opening for easy copy-pasting
echo "extensions = ["
status=0
for id in "$@"; do
# fetch CRX to calculate the hash
url="https://clients2.google.com/service/update2/crx?response=redirect&os=linux&arch=x64&os_arch=x86_64&nacl_arch=x86-64&prod=chromiumcrx&prodchannel=stable&prodversion=120.0.0.0&acceptformat=crx3&x=id%3D${id}%26installsource%3Dondemand%26uc"
url="https://clients2.google.com/service/update2/crx?response=redirect&os=${os}&arch=${arch}&os_arch=${os_arch}&nacl_arch=${nacl_arch}&prod=chromiumcrx&prodchannel=stable&prodversion=${prodversion}&acceptformat=crx3&x=id%3D${id}%26installsource%3Dondemand%26uc"
tmpfile=$(mktemp)
# -L follows the redirect from Google's service to the actual .crx file
if curl -fsSL -A "$UA" "$url" -o "$tmpfile"; then
size=$(stat -c%s "$tmpfile")
if [ "$size" -gt 1000 ]; then
# generate SRI hash
hash=$(nix-hash --type sha256 --flat --base32 "$tmpfile")
sri=$(nix hash convert --to sri --hash-algo sha256 "$hash")
# output the Nix code block
echo " { id = \"$id\"; hash = \"$sri\"; }"
if [ "$(head -c 4 "$tmpfile")" = "Cr24" ]; then
hash=$(nix hash file --type sha256 --sri "$tmpfile")
echo " { id = \"$id\"; hash = \"$hash\"; }"
else
echo " # FAILED: ID $id returned an empty or invalid file (check ID)."
echo " # FAILED: $id — the store returned no CRX (try raising HELIUM_PRODVERSION)."
status=1
fi
else
echo " # FAILED: Could not reach Google Update servers for ID $id."
status=1
fi
rm -f "$tmpfile"
@@ -42,3 +78,5 @@ for id in "$@"; do
done
echo "];"
exit "$status"