feat(firefox): add the desktop Firefox browser module (task 0032) #25
37
.claude/adr/0005-stock-firefox-policy-extensions.md
Normal file
37
.claude/adr/0005-stock-firefox-policy-extensions.md
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
status: accepted
|
||||
---
|
||||
|
||||
# Stock Firefox with policy-installed extensions
|
||||
|
||||
The browser Module ships **stock mainline Firefox** (`pkgs.firefox`, the release train), and installs its three extensions — an ad and content blocker, the operator's password manager, and a video sponsor-skipper — through Mozilla's enterprise `ExtensionSettings` policy, keyed by add-on id with an install URL and `installation_mode = "force_installed"`.
|
||||
Firefox fetches each signed add-on from Mozilla's add-on site at runtime and enables it automatically.
|
||||
|
||||
We chose this over an ESR, unbranded, or Developer Edition build carrying hash-pinned add-on packages from the Nix store.
|
||||
Stock mainline Firefox refuses to load unsigned locally-built add-ons, so the pinned-package path forces the browser variant: it works only on a build that relaxes signature enforcement, which the mainline release does not.
|
||||
Pairing the variant to the extension mechanism makes this the pivotal, hard-to-reverse decision — the choice of build dictates the whole extension story — so it is recorded here rather than left implicit in the Module.
|
||||
|
||||
The trade-off is deliberate.
|
||||
The policy path gives up build-time reproducibility of the extension binaries, and needs network on first launch to populate them, in exchange for staying on current mainline Firefox with add-ons that are actually enabled and no new flake input.
|
||||
|
||||
## Considered Options
|
||||
|
||||
- **Stock mainline Firefox with policy-installed extensions** (chosen).
|
||||
Current release train, no signature-enforcement caveat, no extra flake input.
|
||||
The extension binaries are fetched signed at runtime rather than pinned, so their exact versions are not reproducible from the flake and first launch needs network.
|
||||
- **ESR or unbranded Firefox with hash-pinned add-on packages** (e.g. via a NUR add-ons input).
|
||||
Rejected: it buys reproducible extension binaries but drags in an older or unusual browser variant to satisfy the signature check the mainline build enforces, plus a new flake input to maintain, for a browser the operator wants on the mainline feature and security cadence.
|
||||
- **Stock Firefox with extensions installed by hand.**
|
||||
Rejected: the state would live outside the flake, would not survive a reimage, and defeats the point of declaring the browser at all.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The Module needs no new flake input and no add-on package set.
|
||||
The extension list is three id/URL pairs under the enterprise policy.
|
||||
- Extension versions are whatever Mozilla currently serves, not a pinned hash, so the browser tracks upstream add-on updates automatically and the flake does not gate them.
|
||||
- First launch after a fresh build requires network to fetch the add-ons.
|
||||
An offline first boot comes up with the extensions not yet present, populating them once online.
|
||||
- Moving to a pinned-package posture later would mean changing the browser variant as well, since the two are coupled — the reason this is captured as a decision rather than a detail.
|
||||
- The no-pinned-package rule scopes to the three functional extensions, which is where the signature-enforcement conflict bites.
|
||||
Nord chrome theming comes from the Stylix Firefox Color add-on, a signed add-on that Stylix pins and manages, so it loads on stock mainline Firefox and adds no flake input of ours.
|
||||
That is a bounded, deliberate exception, not a reversal: it is what lets the browser be themed from the shared Stylix scheme without hand-written chrome CSS.
|
||||
122
.claude/spec/firefox.md
Normal file
122
.claude/spec/firefox.md
Normal file
@@ -0,0 +1,122 @@
|
||||
## Problem Statement
|
||||
|
||||
Neogaia now boots into the keyboard-driven Hyprland desktop, but the session ships no web browser.
|
||||
The operator lives in this desktop daily and needs a browser, yet a stock browser install would arrive un-themed, telemetry-on, cluttered with sponsored surfaces, and requiring a round of manual clicking to reach a usable state.
|
||||
That manual state would also be invisible to the flake and would not survive a reimage or transfer to the future desktop Host.
|
||||
|
||||
The operator wants the browser configured the same way as the rest of the system: declared once, hardened and themed by default, and reproduced automatically on any Host that runs the desktop.
|
||||
|
||||
## Solution
|
||||
|
||||
Add Firefox as a new single-purpose desktop Module, configured entirely through home-manager's `programs.firefox`, and fold it into the desktop aggregator so the browser is part of the daily-drivable session rather than a separate opt-in.
|
||||
|
||||
Ship stock mainline Firefox, hardened and de-monetized through locked enterprise policies, with a small fixed set of extensions force-installed by policy from Mozilla's add-on site.
|
||||
Default search to DuckDuckGo over a lean, pruned engine list.
|
||||
Theme the browser Nord from the same single Stylix source that themes the rest of the graphical layer, with no hand-maintained browser CSS.
|
||||
Register Firefox as the system default handler for web links.
|
||||
|
||||
Leave the most personal, frequently-changing state — bookmarks and container tabs — to Firefox's own runtime management rather than declaring it, keeping the Module lean and avoiding the destructive overwrite those declarative options impose.
|
||||
|
||||
Because the Module joins the aggregator, it comes up on any Host with the desktop enabled: neogaia now, and the future desktop Host for free, with no per-Host browser flag.
|
||||
|
||||
## User Stories
|
||||
|
||||
1. As the operator, I want a web browser present the moment the desktop comes up, so that a daily-drivable session includes browsing without a separate install step.
|
||||
2. As the operator, I want the browser expressed as one more enable in the desktop aggregator, so that any Host running the desktop inherits it and the future desktop Host adopts it without rework.
|
||||
3. As the operator, I want the browser configured declaratively alongside every other Module, so that it is reproduced identically on reimage and never depends on manual post-install clicking.
|
||||
4. As the operator, I want to stay on current mainline Firefox rather than an older release train, so that I get up-to-date browser features and security without maintaining an unusual package variant.
|
||||
5. As the operator, I want a fixed set of extensions installed and actually enabled automatically, so that ad-blocking, password management, and sponsor-skipping work on first launch with no add-on hunting.
|
||||
6. As the operator, I want ad and content blocking, so that pages are lighter and less hostile.
|
||||
7. As the operator, I want my password manager available in the browser, so that credentials autofill without me reaching for another app.
|
||||
8. As the operator, I want sponsor segments skipped in videos, so that watching is uninterrupted.
|
||||
9. As the operator, I want telemetry, studies, the read-it-later widget, and the sponsored surfaces on the new-tab and address bar turned off and kept off, so that the browser is quiet, private, and un-monetized without me policing settings.
|
||||
10. As the operator, I want the browser to stop offering to save logins and to stop nagging about being the default, so that it does not fight the password manager or interrupt me.
|
||||
11. As the operator, I want Firefox accounts and sync disabled, so that no account surface appears for a feature I do not use.
|
||||
12. As the operator, I want DuckDuckGo as the default search with only a lean set of engines present, so that search is private and uncluttered.
|
||||
13. As the operator, I want the browser themed Nord from the same source as the rest of the desktop, so that it coheres with the bar, launcher, and lock screen without me hand-theming it.
|
||||
14. As the operator, I want the browser registered as the system default for web links, so that links opened from the bar, notifications, the launcher, or the terminal land in it.
|
||||
15. As the operator, I want bookmarks and container tabs left to the browser itself, so that the things I add in the moment are never wiped by a rebuild.
|
||||
16. As the operator, I want the whole Host to still build green under the existing check, so that I gain confidence before switching a live machine.
|
||||
|
||||
## Implementation Decisions
|
||||
|
||||
**Module and placement**
|
||||
|
||||
- A new single-purpose Module is added under the desktop group, namespaced to mirror its location per the Namespace convention, exposing one `enable` option guarded by the Enable convention.
|
||||
- The Module is configured entirely through the primary user's home-manager `programs.firefox`, matching every other user-facing Module in the repo; no NixOS-level Firefox program integration and no manual package override are used.
|
||||
- The desktop aggregator turns the Module on at default priority alongside the terminal and the other session pieces, so a single desktop flag brings the browser up while a Host retains the ability to override it.
|
||||
The browser is treated as an essential application of the session rather than optional plumbing, following the precedent that the aggregator already enables the terminal.
|
||||
|
||||
**Package variant and extension mechanism**
|
||||
|
||||
- The package is stock mainline Firefox, not ESR, unbranded, or Developer Edition.
|
||||
- Extensions are installed through Mozilla's enterprise policy `force_installed`, keyed by add-on id with an install URL, so the browser fetches the signed add-on from Mozilla's add-on site and enables it automatically.
|
||||
- Nix-built or hash-pinned add-on packages are not used, because stock Firefox refuses unsigned locally-built add-ons; the trade-off — losing build-time reproducibility of the extension binaries in exchange for current mainline Firefox with add-ons that are actually enabled and no new flake input — is accepted deliberately.
|
||||
This decision is the pivotal, hard-to-reverse one and is called out for an ADR in Further Notes.
|
||||
|
||||
**Extensions**
|
||||
|
||||
- Three extensions are force-installed: an ad and content blocker, the operator's password manager, and a video sponsor-skipper.
|
||||
- All three are self-contained web extensions, so no native messaging host is wired.
|
||||
|
||||
**Hardening**
|
||||
|
||||
- Hardening is split across two mechanisms by intent: things with a corresponding enterprise policy are set as locked policies so they cannot be toggled back in the UI, and the remainder are set as ordinary profile preferences.
|
||||
- Locked policies turn off telemetry and studies and data reporting, turn off the read-it-later widget, stop the browser offering to save logins, stop the default-browser check, strip the sponsored shortcuts, sponsored stories, and snippets from the new-tab page, and disable Firefox accounts and sync.
|
||||
- Profile preferences turn off sponsored address-bar suggestions and tidy the new-tab surface.
|
||||
- Fingerprinting resistance is deliberately left off, because it breaks enough everyday browsing to be a deliberate per-need choice rather than a baseline.
|
||||
|
||||
**Search**
|
||||
|
||||
- A single profile is declared, named as the default profile.
|
||||
- The default engine is DuckDuckGo, and the engine list is pruned to a lean set with the general-purpose commercial engines removed; the removed engines remain reachable through DuckDuckGo's bang syntax.
|
||||
- Declaring search requires the module's authoritative-overwrite acknowledgement, so engines added later through the UI are not preserved across a rebuild; this is accepted as the point of declaring search.
|
||||
|
||||
**Theming**
|
||||
|
||||
- The browser is themed by enabling the Stylix Firefox target against the declared profile, driven from the same single Nord scheme that themes the rest of the graphical layer.
|
||||
- No hand-written browser chrome CSS is shipped; a small chrome-CSS layer remains a later addition on top of Stylix if deeper chrome restyling is ever wanted.
|
||||
- The Firefox Stylix target is enabled from within the browser Module, mirroring how the theming Module already sets per-Module Stylix targets, so the target only takes effect when the browser is enabled.
|
||||
|
||||
**Default browser**
|
||||
|
||||
- Firefox is registered as the default handler for the web-link schemes and HTML through the primary user's home-manager mime-association config, placed in the browser Module.
|
||||
|
||||
**Existing conventions already satisfied**
|
||||
|
||||
- The Waybar workspace indicator already carries a window-rewrite icon mapping for Firefox, so the graphical-application icon convention needs no change.
|
||||
|
||||
## Testing Decisions
|
||||
|
||||
- A good test asserts externally-observable evaluation and build success of the whole Host, not the internals of the Module.
|
||||
This mirrors the desktop and laptop-MVI stance, where the config-merge model makes the whole-Host build the meaningful unit and the highest available seam.
|
||||
- Primary seam, required and existing: the neogaia Host evaluates and its system toplevel builds under the flake check.
|
||||
Building the toplevel drives the Auto-loader discovering the new Module, the aggregator fan-out, home-manager wrapping the Firefox package with the policies, extensions, search, and preferences baked in, the Stylix Firefox target wiring, and the default-handler association, surfacing nearly all config-authoring errors short of launching the browser.
|
||||
- Cheap targeted checks: evaluate specific configuration paths to confirm the aggregator fans the browser enable out, the rendered policies carry the three force-installed extensions, the default search engine resolves to DuckDuckGo, and the Stylix Firefox target is on, reusing the repo's existing lightweight eval-probe pattern.
|
||||
- No Module-level unit tests are added; there is no seam below the whole-Host build worth testing here, and the prior art is the desktop and laptop-MVI build-the-toplevel checks.
|
||||
- The genuine end-to-end confirmation is manual and irreducible: switch the configuration on neogaia, launch the browser, and confirm the three extensions are present and enabled, the Nord theme is applied, DuckDuckGo is the default search, and a link opened from another application lands in the browser.
|
||||
A browser cannot self-test headless, but unlike a reimage this is reversible, so verification is done by living in it with a safety net of a generation rollback or the desktop flag.
|
||||
- The build validates that the policy document is well-formed and baked into the package, but not that Firefox accepts every policy key semantically, since the policy document is only text until the browser loads it; that last mile is part of the manual confirmation.
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- ESR, unbranded, and Developer Edition Firefox variants, and the nix-built or hash-pinned add-on path they would enable; the reproducibility trade-off was weighed and declined.
|
||||
- Native messaging hosts of any kind, including a password-manager native connector and desktop browser-integration bridges; none of the chosen extensions need one.
|
||||
- Declarative bookmarks and declarative container tabs, both deliberately left to the browser's own runtime state.
|
||||
- The Multi-Account Containers extension and the contextual-identity preference it relies on.
|
||||
- Multiple Firefox profiles; a single default profile carries everything.
|
||||
- Custom or Nix-oriented search engines beyond the lean pruned set.
|
||||
- Hand-written browser chrome CSS and any deep chrome-layout restyling such as compact tabs or a hidden title bar.
|
||||
- Fingerprinting resistance and any harder privacy posture that routinely breaks everyday browsing.
|
||||
- Firefox accounts and sync.
|
||||
- Any second browser, and any per-Host browser divergence; the future desktop Host inherits this same Module unchanged.
|
||||
- Any Skeleton, Auto-loader, or secret-wiring change; the Module uses the existing plumbing unchanged.
|
||||
|
||||
## Further Notes
|
||||
|
||||
- The stock-Firefox-plus-policy-extensions decision is hard to reverse — the variant dictates the entire extension mechanism — surprising without context, since a Nix-purist default would expect hash-pinned add-on packages, and the product of a real trade-off between reproducibility and current mainline Firefox with working extensions.
|
||||
It should be recorded as an ADR.
|
||||
- Home-manager's `programs.firefox` was confirmed to expose a top-level policies option that merges into the wrapper's enterprise policies, which is what lets the whole Module, hardening included, live under home-manager rather than needing the NixOS-level program integration.
|
||||
- The declarative search, bookmarks, and containers options all share one authoritative-overwrite model: the browser owns those files at runtime, so declaring them means home-manager overwrites them wholesale and runtime-added entries do not survive a rebuild.
|
||||
This is why bookmarks and containers are left out and why declaring search is an explicit acknowledgement.
|
||||
- The extension set fetches signed add-ons from Mozilla's add-on site at runtime rather than from the Nix store, so first launch after a fresh build needs network to populate the extensions; this is inherent to the policy-based path chosen over the pinned-package path.
|
||||
71
.claude/tasks/0032-firefox-browser.md
Normal file
71
.claude/tasks/0032-firefox-browser.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
spec: firefox
|
||||
---
|
||||
|
||||
## What to build
|
||||
|
||||
Add Firefox as a new single-purpose Module under the desktop group, configured entirely through the primary user's home-manager `programs.firefox`, and fold it into the desktop aggregator so the browser comes up as part of the daily-drivable session on any Host that enables the desktop.
|
||||
|
||||
The browser is stock mainline Firefox, hardened and de-monetized through locked enterprise policies, carrying a small fixed set of extensions force-installed by policy from Mozilla's add-on site.
|
||||
Search defaults to DuckDuckGo over a lean, pruned engine list.
|
||||
The browser is themed Nord from the same single Stylix source as the rest of the graphical layer, and registered as the system default handler for web links.
|
||||
Bookmarks and container tabs are deliberately not declared, leaving that state to the browser's own runtime management.
|
||||
|
||||
The end-to-end result: a Host with the desktop enabled boots into a session where the browser is present, launchable, themed to match, telemetry-quiet, has its extensions installed and enabled on first launch, uses DuckDuckGo, and receives links opened from other applications.
|
||||
|
||||
Scope details, all following the domain conventions (Namespace convention, Enable convention, aggregator fan-out):
|
||||
|
||||
- **Module and placement.** One `enable` option namespaced to mirror the file's location under the desktop group, guarded by the Enable convention. Configured only through home-manager `programs.firefox`; no NixOS-level Firefox program integration and no manual package override. The desktop aggregator turns it on at default priority alongside the terminal, so the single desktop flag brings it up while a Host can still override it.
|
||||
- **Package and extensions.** Stock mainline Firefox, not ESR/unbranded/Developer Edition. The three extensions — an ad and content blocker, the operator's password manager, and a video sponsor-skipper — are installed through the enterprise `force_installed` policy keyed by add-on id with an install URL, so Firefox fetches the signed add-on and enables it automatically. No Nix-built or hash-pinned add-on packages, and no native messaging host.
|
||||
- **Hardening.** Split by intent: policy-backed items are set as locked policies (telemetry, studies, and data reporting off; read-it-later widget off; offer-to-save-logins off; default-browser check off; sponsored shortcuts, stories, and snippets stripped from the new-tab page; Firefox accounts and sync disabled), and the rest as ordinary profile preferences (sponsored address-bar suggestions off, new-tab surface tidied). Fingerprinting resistance stays off.
|
||||
- **Search.** A single profile, named the default. DuckDuckGo as the default engine, the engine list pruned to a lean set with the general-purpose commercial engines removed, using the module's authoritative-overwrite acknowledgement.
|
||||
- **Theming.** Enable the Stylix Firefox target against the declared profile, driven from the shared Nord scheme, set from within this Module (mirroring how the theming Module already sets per-Module Stylix targets). No hand-written browser chrome CSS.
|
||||
- **Default browser.** Register Firefox as the default handler for the web-link schemes and HTML through the user's home-manager mime-association config, placed in this Module.
|
||||
|
||||
Record the pivotal, hard-to-reverse decision — stock Firefox plus policy-installed extensions over an ESR/unbranded build with hash-pinned add-on packages — as an ADR, following the ADR format and the next ADR number.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [x] A new Firefox Module exists under the desktop group with a single `enable` option, namespaced to mirror its directory per the Namespace convention and guarded per the Enable convention; `modules.desktop.firefox.enable` resolves.
|
||||
- [x] The Module is configured only through the primary user's home-manager `programs.firefox`; there is no NixOS-level Firefox program integration and no manual package override.
|
||||
- [x] The desktop aggregator enables the Module at default priority, so `modules.desktop.enable` brings the browser up and a Host can still override the single flag; neogaia carries it through the desktop flag with no per-Host browser line.
|
||||
- [x] The package is stock mainline Firefox (not ESR, unbranded, or Developer Edition).
|
||||
- [x] The three extensions are force-installed via enterprise policy keyed by add-on id with an install URL: the ad and content blocker, the password manager, and the video sponsor-skipper; no native messaging host is declared.
|
||||
- [x] Locked policies turn off telemetry, studies, and data reporting; turn off the read-it-later widget; stop offer-to-save-logins; stop the default-browser check; strip sponsored shortcuts, stories, and snippets from the new-tab page; and disable Firefox accounts and sync.
|
||||
- [x] Profile preferences turn off sponsored address-bar suggestions and tidy the new-tab surface; fingerprinting resistance is left off.
|
||||
- [x] A single default profile is declared with DuckDuckGo as the default search engine and the engine list pruned to a lean set (general-purpose commercial engines removed), using the search authoritative-overwrite acknowledgement.
|
||||
- [x] The Stylix Firefox target is enabled against the declared profile from within this Module, driven from the shared Nord scheme; no hand-written browser chrome CSS is shipped.
|
||||
- [x] Firefox is registered as the default handler for the web-link schemes and HTML through the user's home-manager mime-association config.
|
||||
- [x] An ADR (next number) records the stock-Firefox-plus-policy-extensions decision over an ESR/unbranded build with hash-pinned add-on packages, following the ADR format.
|
||||
- [x] `nix flake check` builds `checks.x86_64-linux.neogaia` green, with the new file staged so evaluation sees it.
|
||||
- [-] Manual confirmation on neogaia: after switching, the browser launches with the three extensions present and enabled, the Nord theme applied, DuckDuckGo as default search, and a link opened from another application lands in it.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The module lives at `modules/desktop/firefox.nix`, declares `modules.desktop.firefox.enable`, and is fanned out by the desktop aggregator at `lib.mkDefault true`.
|
||||
Everything is configured through `home-manager.users.<user>.programs.firefox`, with no NixOS-level program integration and no `package` override, so it stays on stock `pkgs.firefox` (built as `firefox-152.0.6`, the mainline release train).
|
||||
|
||||
The three force-installed extensions are keyed by their real add-on ids, verified against Mozilla's AMO API rather than guessed: uBlock Origin (`uBlock0@raymondhill.net`), Proton Pass (`78272b6fa58f4a1abaac99321d503a20@proton.me`), and SponsorBlock (`sponsorBlocker@ajay.app`).
|
||||
Proton Pass is the operator's password manager, per ADR 0002 and the sops spec.
|
||||
|
||||
Search pruning deviated from a first pass that merely omitted the commercial engines.
|
||||
Omission does not remove them: home-manager's search module writes `search.json.mozlz4`, but Firefox reconciles its locale's app-provided engines back in for any not present in the file, so the general-purpose commercial engines would reappear.
|
||||
The lean set is instead reached by explicitly hiding them with `<engine>.metaData.hidden = true` (the module's documented builtin-hiding idiom), confirmed by decoding the built `search.json.mozlz4`: it carries `_metaData.hidden` on google, bing, ebay, and amazon, with `defaultEngineId = "ddg"`.
|
||||
DuckDuckGo and Wikipedia remain visible; the hidden engines stay reachable through DuckDuckGo bangs.
|
||||
Engines are referenced by their current id form (`ddg`, `google`, …), which the module maps from the old display names — `default = "ddg"` is correct, not `"DuckDuckGo"`.
|
||||
|
||||
The decision to ship stock Firefox with policy-installed extensions over an ESR/unbranded build with hash-pinned add-ons is recorded as ADR 0005.
|
||||
|
||||
The final acceptance criterion is marked `[-]` rather than `[x]`: it is the irreducible manual confirmation the spec calls out (a browser cannot self-test headless), deferred to the operator on the live machine after switching, not dropped work.
|
||||
Every automatable check — the whole-Host toplevel build, and eval probes for the aggregator fan-out, the three force-installed ids, the DuckDuckGo default, the hidden commercial engines, the Stylix Firefox target, and the mime handlers — passes.
|
||||
|
||||
### Follow-ups from the manual confirmation
|
||||
|
||||
The manual launch surfaced two runtime problems the build could not, both since fixed and verified by deploying the home generation.
|
||||
|
||||
Home-manager activation was failing outright: Firefox writes `~/.config/mozilla/firefox/profiles.ini` itself on first launch, and home-manager refuses to clobber the pre-existing file, so the whole generation failed and the declarative profile never deployed — search and theming were absent while the policy-driven extensions and mime handlers, which do not touch the profile, still worked.
|
||||
Fixed by setting `force = true` on the generated `profiles.ini` home.file entry so home-manager owns it and deploys the `default` profile.
|
||||
|
||||
The base Stylix Firefox target themes only fonts and the reader view, not the toolbar and tabs, so the chrome did not look Nord.
|
||||
Fixed by enabling `stylix.targets.firefox.colorTheme`, which recolours the chrome from the shared scheme through the Stylix-managed Firefox Color add-on, with `profiles.default.extensions.force = true` to acknowledge the managed extension-settings store.
|
||||
ADR 0005 is updated to scope its no-pinned-add-on rule to the functional extensions and record the signed theming add-on as a deliberate exception.
|
||||
10
CLAUDE.md
10
CLAUDE.md
@@ -57,8 +57,9 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla
|
||||
Editing a skill in place fails; its source is `modules/agents/claude-code/skills/<name>/` here, applied by a rebuild.
|
||||
Creating a new file under `~/.claude/skills/` succeeds silently and is the trap — it stays outside the repo and reaches no other machine.
|
||||
Copying out of that tree needs `cp -rL` plus `chmod -R u+w`: a plain `cp -r` copies the symlinks, putting store paths into the destination, and dereferenced files keep the store's read-only mode.
|
||||
- `home-manager.users.<user>.home.file` is keyed by **absolute** path, not by a path relative to the home directory.
|
||||
Evaluating `home.file.".claude/CLAUDE.md"` fails with "does not provide attribute"; the working key is `home.file."/home/alexion/.claude/CLAUDE.md"`.
|
||||
- `home-manager.users.<user>.home.file` is keyed by whatever path string the **defining module wrote**, absolute or relative, not by one canonical form.
|
||||
A module that writes `home.file."/home/alexion/.claude/CLAUDE.md"` is reachable only at that absolute key, while `programs.firefox` writes relative keys such as `home.file.".config/mozilla/firefox/profiles.ini"` reachable only at the relative form.
|
||||
The other form fails with "does not provide attribute", so overriding an entry (e.g. setting `.force = true` on it) requires matching the writer's exact key.
|
||||
List the real keys with `nix eval --json .#nixosConfigurations.<host>.config.home-manager.users.<user>.home.file --apply builtins.attrNames` rather than guessing one.
|
||||
A key's `.source` is the input file, whose store path differs from the deployed symlink's target (home-manager copies it to a `hm_`-prefixed path) even though the contents match.
|
||||
- nixpkgs `vimPlugins.nord-nvim` is `shaunsingh/nord.nvim` (no `require("nord").setup()`); the config wants `gbprod/nord.nvim`, which is packaged as `vimPlugins.gbprod-nord`.
|
||||
@@ -103,3 +104,8 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla
|
||||
- A `nix build` or `nix flake check` on a **dirty** tree evaluates the working-copy content of tracked files, not what is committed, and only warns `Git tree ... is dirty`.
|
||||
A green check on a dirty tree therefore proves nothing about the commit.
|
||||
To verify a commit, build once on a clean tree (nothing uncommitted), where the absence of the dirty warning confirms the build reflects `HEAD`.
|
||||
- home-manager's `programs.firefox` declarative `search` with `force = true` does **not** prune Firefox's built-in engines by omission.
|
||||
The overwrite writes `search.json.mozlz4` with only the engines listed, but Firefox reconciles its locale's app-provided engines back in for any not present in the file, so Google, Bing, and the rest reappear.
|
||||
To actually remove a builtin, list it explicitly with `<engine>.metaData.hidden = true` — an engine entry carrying only `metaData` is treated as a builtin rather than a custom engine.
|
||||
Engines are referenced by their current id, which the module maps from the old display names, so the default is `default = "ddg"`, not `"DuckDuckGo"` (the latter only warns and migrates).
|
||||
Prove the result by decoding the built file: `mozlz4a -d <search.json.mozlz4>` shows the `_metaData.hidden` flags and `defaultEngineId`.
|
||||
|
||||
@@ -10,6 +10,7 @@ in
|
||||
# any one of them while the single flag above enables the whole desktop.
|
||||
config = lib.mkIf cfg.enable {
|
||||
modules.desktop.clipboard.enable = lib.mkDefault true;
|
||||
modules.desktop.firefox.enable = lib.mkDefault true;
|
||||
modules.desktop.hyprland.enable = lib.mkDefault true;
|
||||
modules.desktop.hyprland.hyprlock.enable = lib.mkDefault true;
|
||||
modules.desktop.hyprland.hypridle.enable = lib.mkDefault true;
|
||||
|
||||
124
modules/desktop/firefox.nix
Normal file
124
modules/desktop/firefox.nix
Normal file
@@ -0,0 +1,124 @@
|
||||
{ config, lib, ... }:
|
||||
# Firefox as the desktop browser: stock mainline, hardened and de-monetized by policy.
|
||||
let
|
||||
cfg = config.modules.desktop.firefox;
|
||||
user = config.user.name;
|
||||
|
||||
# A force-installed extension, keyed at the call site by the add-on's own id.
|
||||
# Firefox fetches the signed add-on from Mozilla's site and enables it automatically.
|
||||
forceInstalled = slug: {
|
||||
install_url = "https://addons.mozilla.org/firefox/downloads/latest/${slug}/latest.xpi";
|
||||
installation_mode = "force_installed";
|
||||
};
|
||||
in
|
||||
{
|
||||
options.modules.desktop.firefox.enable = lib.mkEnableOption "Firefox as the desktop browser";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
home-manager.users.${user} = hm: {
|
||||
programs.firefox = {
|
||||
enable = true;
|
||||
|
||||
# Enterprise policies: enforced and unchangeable from the browser UI.
|
||||
policies = {
|
||||
DisableTelemetry = true;
|
||||
DisableFirefoxStudies = true;
|
||||
DisablePocket = true;
|
||||
OfferToSaveLogins = false;
|
||||
DontCheckDefaultBrowser = true;
|
||||
DisableFirefoxAccounts = true;
|
||||
|
||||
# Strip the monetized surfaces from the new-tab page.
|
||||
FirefoxHome = {
|
||||
SponsoredTopSites = false;
|
||||
SponsoredPocket = false;
|
||||
Snippets = false;
|
||||
};
|
||||
|
||||
# An ad and content blocker, the operator's password manager, and a
|
||||
# video sponsor-skipper. All three are self-contained web extensions.
|
||||
ExtensionSettings = {
|
||||
"uBlock0@raymondhill.net" = forceInstalled "ublock-origin";
|
||||
"78272b6fa58f4a1abaac99321d503a20@proton.me" = forceInstalled "proton-pass";
|
||||
"sponsorBlocker@ajay.app" = forceInstalled "sponsorblock";
|
||||
};
|
||||
};
|
||||
|
||||
profiles.default = {
|
||||
isDefault = true;
|
||||
|
||||
# The Nord chrome theme is a declared extension setting, so home-manager
|
||||
# owns the extension-settings store, overwriting runtime changes to it.
|
||||
extensions.force = true;
|
||||
|
||||
# Stylix's Nord mapping paints the selected address-bar result a
|
||||
# near-white grey, leaving its light text unreadable. Darken that one
|
||||
# highlight to the Nord selection grey, from the same scheme.
|
||||
extensions.settings."FirefoxColor@mozilla.com".settings.theme.colors.popup_highlight =
|
||||
let
|
||||
c = hm.config.lib.stylix.colors;
|
||||
in
|
||||
lib.mkForce {
|
||||
r = c."base03-rgb-r";
|
||||
g = c."base03-rgb-g";
|
||||
b = c."base03-rgb-b";
|
||||
};
|
||||
|
||||
settings = {
|
||||
# Auto-enable the sideloaded Firefox Color add-on that carries the
|
||||
# Nord chrome theme, which Firefox otherwise leaves disabled.
|
||||
# The functional extensions come through policy and are unaffected.
|
||||
"extensions.autoDisableScopes" = 0;
|
||||
|
||||
# Sponsored surfaces the policies above do not reach.
|
||||
"browser.urlbar.suggest.quicksuggest.sponsored" = false;
|
||||
"browser.newtabpage.activity-stream.showSponsored" = false;
|
||||
"browser.newtabpage.activity-stream.showSponsoredTopSites" = false;
|
||||
};
|
||||
|
||||
search = {
|
||||
# Declaring search overwrites Firefox's own engine list wholesale,
|
||||
# so engines added later in the UI do not survive a rebuild.
|
||||
force = true;
|
||||
default = "ddg";
|
||||
|
||||
# The general-purpose commercial engines, hidden to leave a lean
|
||||
# DuckDuckGo-and-Wikipedia list. They stay reachable through bangs.
|
||||
# An engine carrying only metaData is treated as a builtin.
|
||||
engines = {
|
||||
google.metaData.hidden = true;
|
||||
bing.metaData.hidden = true;
|
||||
ebay.metaData.hidden = true;
|
||||
"amazondotcom-us".metaData.hidden = true;
|
||||
};
|
||||
order = [ "ddg" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Nord chrome from the shared Stylix scheme, against the one profile.
|
||||
# colorTheme recolours the toolbar and tabs, which the target does not do
|
||||
# on its own, through the Stylix-managed Firefox Color add-on.
|
||||
stylix.targets.firefox = {
|
||||
enable = true;
|
||||
profileNames = [ "default" ];
|
||||
colorTheme.enable = true;
|
||||
};
|
||||
|
||||
# Links opened from other applications land in Firefox.
|
||||
xdg.mimeApps = {
|
||||
enable = true;
|
||||
defaultApplications = {
|
||||
"text/html" = "firefox.desktop";
|
||||
"application/xhtml+xml" = "firefox.desktop";
|
||||
"x-scheme-handler/http" = "firefox.desktop";
|
||||
"x-scheme-handler/https" = "firefox.desktop";
|
||||
};
|
||||
};
|
||||
|
||||
# Firefox writes profiles.ini itself on first launch, so home-manager is
|
||||
# told to own the file rather than fail activation refusing to clobber it.
|
||||
home.file."${hm.config.programs.firefox.configPath}/profiles.ini".force = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user