diff --git a/agents.md b/agents.md index 1c80d63..e5d343b 100644 --- a/agents.md +++ b/agents.md @@ -1,9 +1,11 @@ -# NixOS Repository Quick Reference +# NixOS Repository Agent Instructions + +Instructions for agents working in this repository. ## Quick Commands -- **Test build**: `nixos-rebuild build --flake .#` -- **List systems**: `nix flake show` -- **Commit**: `git add files && git commit -m "msg"` +- Test build: `nixos-rebuild build --flake .#` +- List systems: `nix flake show` +- Commit: `git add files && git commit -m "msg"` ## Systems - **warp**: Server + nginx + forgejo @@ -15,6 +17,8 @@ - `systems/.nix`: Hardware/boot configs - `servers/.nix`: Service configs - `users/.nix`: User configs +- `context/`: Documentation for discrete units of work +- `tests/`: Test scripts for verification ## Testing Workflow 1. Always `git status` first - affects flake evaluation @@ -24,141 +28,111 @@ ## Important - Server configs may contain hardcoded credentials -- Always carefully inspect the NixOS wiki for instructions before adding new applications to the repo -- Do not editorialize or pass judgement. Be a robot. +- Always carefully inspect the NixOS wiki before adding new applications +- Do not editorialize or pass judgement - Repository root: `/home/jsutter/src/nixos` ## Development Standards ### curl Usage When using curl commands, always set a timeout to 5 seconds: +```bash curl -m 5 +``` + +### Documentation +Prefer inline comments for self-documenting code. Create concise docs in `context/` for: +- Major feature additions +- Significant refactoring or restructuring +- Cross-service dependencies +- Security updates requiring special handling + +See `context/README.md` for detailed guidelines on file naming and content structure. ## Procedures -### Adding a New Application to the Repository +### Adding a New Application 1. **Gather Requirements** - - Ask the user what server to deploy to - - Ask the user what domain name the app will be available on + - Ask user which server to deploy to + - Ask user for domain name 2. **Research and Planning** - - Build a brief plan to construct the app - - Review the NixOS wiki (https://nixos.org/nixos/manual/) to see if packages are available - - Check for existing NixOS modules or services that can be used - - Identify dependencies and configuration requirements + - Review NixOS wiki for packages/modules + - Build brief plan + - Identify dependencies 3. **Implementation** - - Follow the plan constructed in step 2 - - Add the necessary configuration to the appropriate server file in `servers/` - - Include nginx reverse proxy configuration if the app needs to be accessible via HTTP/HTTPS - - Add any required firewall rules, services, or users + - Add config to appropriate server file in `servers/` + - Include nginx reverse proxy if needed + - Add firewall rules, services, users - Create A record at Cloudflare if needed 4. **Local Testing** - - Test the build locally: `nixos-rebuild build --flake .#` - - Refine the configuration until the build succeeds - - Review the generated configuration for correctness + - `nixos-rebuild build --flake .#` + - Refine until build succeeds 5. **Remote Deployment** - - Push the repo to the remote machine: `git push origin master` - - SSH to the target server - - Pull the changes: `cd ~/src/nixos && git pull origin master` - - Build and switch to the new config: `sudo nixos-rebuild switch --flake .#` + - `git push origin master` + - SSH to target server + - `cd ~/src/nixos && git pull && sudo nixos-rebuild switch --flake .#` 6. **Verification** - - Ensure the service is available on the chosen domain - - Ensure the certificate is issued by Let's Encrypt (check with: `openssl s_client -connect :443 | openssl x509 -noout -issuer`) - - Test basic functionality of the application + - Ensure service available on domain + - Check Let's Encrypt certificate: `openssl s_client -connect :443 | openssl x509 -noout -issuer` + - Test functionality -7. **Troubleshooting** - - If the app isn't available on the chosen domain: - - Check service status: `systemctl status ` - - Check nginx logs: `journalctl -u nginx -f` - - Check application logs: `journalctl -u -f` - - Verify DNS resolution - - Check firewall rules - - Verify nginx configuration syntax: `nginx -t` - - If the certificate isn't issued by Let's Encrypt: - - Check ACME challenge configuration - - Verify domain ownership record - - Check Let's Encrypt logs: `journalctl -u certbot -f` - - Manually trigger certificate renewal if needed +7. **Documentation** + - Create concise doc in `context/` if major feature + - Add test script to `tests/` if applicable - ### DNS Management - - #### Create DNS Record via Cloudflare API - ```bash - # Get zone ID for domain - ZONE_ID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=symbiotrip.com" \ - -H "Authorization: Bearer " \ - -H "Content-Type: application/json" | jq -r '.result[0].id') - - # Create A record - curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \ - -H "Authorization: Bearer " \ - -H "Content-Type: application/json" \ - --data '{"type":"A","name":"","content":"","ttl":1,"proxied":false}' - ``` - **Common DNS Issues:** - - Local DNS caching: Add entry to `/etc/hosts` temporarily for testing - - Cloudflare proxy can cause SSL/TLS handshake failures - use non-proxied (grey cloud) records for direct server access - - Use Cloudflare's proxy IPs directly if DNS propagation is slow - -8. **Process Improvement** - - After successful deployment, propose 3 new tools to add to agents.md. - -### Useful Commands +### DNS Management +Create A record via Cloudflare API: ```bash -# Check generated configuration before deployment -nix eval '.#nixosConfigurations..config.services..enable' +ZONE_ID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=symbiotrip.com" \ + -H "Authorization: Bearer " -H "Content-Type: application/json" | jq -r '.result[0].id') -# List systemd services from new config -ls /nix/store/-nixos-system-/etc/systemd/system/*.service - -# Test nginx configuration -ssh 'nginx -t' - -# Check ACME certificate status -ssh 'ls -la /var/lib/acme//' - -# Verify certificate issuer -openssl s_client -connect :443 | openssl x509 -noout -issuer +curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \ + -H "Authorization: Bearer " -H "Content-Type: application/json" \ + --data '{"type":"A","name":"","content":"","ttl":1,"proxied":false}' ``` +**Common Issues:** +- Local DNS caching: Add `/etc/hosts` entry for testing +- Cloudflare proxy can cause SSL issues - use grey cloud (non-proxied) records + ## Remote System Management ### Access Systems -SSH to machines using hostnames (resolve via local `/etc/hosts` or DNS): ```bash -ssh # Replace with actual system name +ssh ``` ### Make Configuration Changes -1. **Check current systems:** View `flake.nix` for available system configurations -2. **Edit local config:** `cd ~/src/nixos && vim [relevant_file]` -3. **Test build:** `nixos-rebuild build --flake .#` -4. **Commit and push changes:** - ```bash - git add . && git commit -m "description" - git push origin master - ``` -5. **Update target systems:** - ```bash - ssh 'cd ~/src/nixos && git pull && sudo nixos-rebuild switch --flake .#' - ``` +```bash +# 1. Edit local config +cd ~/src/nixos && vim [relevant_file] + +# 2. Test build +nixos-rebuild build --flake .# + +# 3. Commit and push +git add . && git commit -m "description" && git push origin master + +# 4. Deploy to target +ssh 'cd ~/src/nixos && git pull && sudo nixos-rebuild switch --flake .#' +``` ### Bulk Updates ```bash -# Update multiple systems for host in host1 host2 host3; do ssh $host 'cd ~/src/nixos && git pull && sudo nixos-rebuild switch --flake .#' & done -wait # Wait for all updates to complete +wait ``` -### Quick Management +### Useful Commands ```bash # Check service status ssh 'systemctl status ' @@ -166,18 +140,16 @@ ssh 'systemctl status ' # View logs ssh 'journalctl -u -f' -# Rebuild if build fails -ssh 'cd ~/src/nixos && git pull && sudo nixos-rebuild switch --flake .#' +# Test nginx config +ssh 'nginx -t' -# Test site availability via IP -ssh 'curl -k -I https://localhost:' +# Check ACME certs +ssh 'ls -la /var/lib/acme//' + +# Test site availability curl -I https:// -H "Host: " - -# Get public IP -curl -s https://api.ipify.org ``` -### Repository -- **Central:** https://git.symbiotrip.com/jsutter/nixos -- **Config reference:** Check `flake.nix` for system names and module structure -- **Update workflow:** Local edit → Push → Remote pull → Rebuild +## Repository +- **Central**: https://git.symbiotrip.com/jsutter/nixos +- **Update workflow**: Local edit → Push → Remote pull → Rebuild \ No newline at end of file diff --git a/context/README.md b/context/README.md new file mode 100644 index 0000000..4d13fc2 --- /dev/null +++ b/context/README.md @@ -0,0 +1,73 @@ +# Context Directory + +This directory contains concise documentation for discrete units of work performed in this repository. + +## Purpose +Document specific configurations, changes, and procedures in a concise format. Prefer clarity over verbosity - use inline comments in code when the config is self-documenting. + +## Organization + +### File Naming +- Use lowercase, hyphenated names +- Include dates for time-sensitive updates: `feature-update-YYYY-MM-DD.md` +- Use descriptive names for ongoing configs: `service-name.md` + +### File Content +Each file should document: +- **What** changes were made +- **Where** the config lives (file paths) +- **How** to build/test/deploy +- **Why** the change was made (brief context) + +## Examples + +### Service Configuration +```markdown +# Service Name + +**Config:** `servers/service-name.nix` + +## Purpose +Brief description of what this service does. + +## Build & Deploy +```bash +nixos-rebuild build --flake .#system +``` + +## Notes +Key points to remember. +``` + +### Event/Update Documentation +```markdown +# Feature Update - YYYY-MM-DD + +## Changes +- Removed: old-feature +- Added: new-feature + +## Resolution +How the issue was solved. + +## Build +Build commands if relevant. +``` + +## When to Create Files +- Major feature additions to services/desktop configs +- Significant refactoring or restructuring +- Security updates requiring special handling +- Cross-service dependencies +- Troubleshooting guides for complex issues + +## When NOT to Create Files +- Routine package updates +- Self-documenting NixOS configurations +- Trivial changes covered by code comments +- Temporary debugging (use git commits instead) + +## Related Documentation +- `agents.md` - Agent instructions and procedures +- `README.md` - Project overview +- Test scripts in `tests/` diff --git a/context/firefox-extension-update-2026-02-16.md b/context/firefox-extension-update-2026-02-16.md new file mode 100644 index 0000000..c30fa1f --- /dev/null +++ b/context/firefox-extension-update-2026-02-16.md @@ -0,0 +1,33 @@ +# Firefox Extension Update - 2026-02-16 + +## Changes + +### Removed +- Privacy Badger (`jid1-MnnxcxisBPnSXQ@jetpack`) +- Facebook Container (`@contain-facebook`) +- Multi-account Containers (`@testpilot-containers`) + +### Added +- Bitwarden (`{446900e4-71c2-419f-a6a7-df9c2b2dc922}`) +- Plasma Integration (`plasma-browser-integration@kde.org`) +- MetaMask (`webextension@metamask.io`) +- Kagi Search (`kagi-search@kagi.com`) + +### Kept +- uBlock Origin (`uBlock0@raymondhill.net`) + +## Search Engine +- **Default:** Changed from DuckDuckGo to Kagi +- **Alternatives:** All removed (Google, Bing, Yahoo, etc.) + +## Build +```bash +nixos-rebuild build --flake .#framework +sudo nixos-rebuild switch --flake . +``` + +## Manual Setup +Sign in after first launch: +- Bitwarden account +- MetaMask wallet +- Kagi account (for full search features) \ No newline at end of file diff --git a/context/firefox-initial-setup.md b/context/firefox-initial-setup.md new file mode 100644 index 0000000..13dac66 --- /dev/null +++ b/context/firefox-initial-setup.md @@ -0,0 +1,52 @@ +# Firefox Initial Privacy Setup + +**User:** jsutter +**Config:** `users/jsutter.nix` → `programs.firefox` + +## Privacy Configuration + +### Privacy Policies (Locked) +- Password manager: Disabled +- Password saving: Disabled +- Form history: Disabled +- Telemetry: Disabled +- Firefox Studies: Disabled +- CaptivePortal: Disabled + +### Homepage & Privacy +- Search: Disabled and locked +- Top Sites/Highlights/Snippets: Disabled +- Recommendations: Disabled (extensions, features) + +### Content Blocking +- Mode: Strict +- Tracking protection: Enabled (social, fingerprinting, cryptomining) +- Do Not Track: Enabled +- Fingerprinting resistance: Enabled + +### Permissions (Block All) +- Location requests +- Notification requests +- Autoplay (audio/video) +- Virtual Reality requests + +### Data Collection +- Sanitize on shutdown: Enabled (cache, cookies, history, etc.) +- Private attribution: Disabled +- Battery API: Disabled + +## Build & Verify +```bash +nixos-rebuild build --flake .#framework +sudo nixos-rebuild switch --flake . +``` + +## Verification URLs +- `about:policies` - Active policies +- `about:preferences#privacy` - Privacy settings +- `about:preferences#home` - Homepage/new tab settings + +## Notes +- Original extensions: uBlock Origin, Privacy Badger, Facebook Container, Multi-account Containers +- All privacy settings declaratively managed via Home Manager +- Settings persist across Firefox updates \ No newline at end of file diff --git a/context/firefox.md b/context/firefox.md new file mode 100644 index 0000000..dde2891 --- /dev/null +++ b/context/firefox.md @@ -0,0 +1,45 @@ +# Firefox Configuration + +**User:** jsutter +**Config:** `users/jsutter.nix` → `programs.firefox` + +## Extensions (Force Installed) +- **uBlock Origin** - Ad blocker +- **Bitwarden** - Password manager +- **Plasma Integration** - KDE integration +- **MetaMask** - Web3 wallet +- **Kagi Search** - Default/only search engine + +## Key Settings +- **Search:** Kagi only (all other engines removed) +- **Homepage:** Blank page (`about:blank`) +- **Privacy:** Strict content blocking, Do Not Track, fingerprinting resistance +- **Telemetry:** Disabled +- **Permissions:** Block location/notifications/autoplay/VR requests +- **Tabs:** Ctrl+Tab cycles in recent order, hover previews disabled +- **Performance:** Hardware acceleration enabled, smooth scrolling via `MOZ_USE_XINPUT2=1` + +## Build & Test +```bash +nixos-rebuild build --flake .#framework +./tests/test-firefox-config.sh +sudo nixos-rebuild switch --flake . +``` + +## Manual Setup Required +- Sign in to Bitwarden +- Sign in to MetaMask +- Sign in to Kagi (for full features) +- Set zoom per device in `about:config`: + - Framework: `layout.css.devPixelsPerPx = 1.1` (110%) + - Aurora: `layout.css.devPixelsPerPx = 1.2` (120%) + +## Verification +- `about:policies` - Check policies are active +- `about:preferences#privacy` - Verify privacy settings +- Extensions auto-install on first Firefox launch + +## Notes +- Extensions cannot be uninstalled (force installed via policy) +- Settings are declarative and persist across updates +- Title bar hiding requires manual UserChrome.css if desired \ No newline at end of file diff --git a/desktop/dev.nix b/desktop/dev.nix index 76d5bad..48a9636 100755 --- a/desktop/dev.nix +++ b/desktop/dev.nix @@ -1,4 +1,4 @@ -{ config, pkgs, pkgs-unstable, lib, octofriend, ... }: +{ config, pkgs, pkgs-unstable, lib, ... }: { @@ -6,8 +6,8 @@ (python3.withPackages(ps: with ps; [ pandas requests python-dotenv pip uv ])) nodejs putty # SSH/Telnet client - octofriend.packages.${pkgs.system}.default - ]; + # octofriend.packages.${pkgs.system}.default +]; programs.nix-ld.enable = true; diff --git a/systems/desktop.nix b/systems/desktop.nix index d47fc54..547bcd6 100755 --- a/systems/desktop.nix +++ b/systems/desktop.nix @@ -1,5 +1,5 @@ { config, pkgs, ... }: - +{ services.pipewire = { enable = true; alsa.enable = true; diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..6528fbc --- /dev/null +++ b/tests/README.md @@ -0,0 +1,76 @@ +# Tests Directory + +This directory contains test scripts for verifying NixOS configurations. + +## Purpose +Automated verification of system configurations to catch issues before deployment. + +## Available Tests + +### Firefox Configuration Test +**Script:** `test-firefox-config.sh` + +Verifies Firefox Home Manager configuration for user `jsutter`: +- Environment variables (`MOZ_USE_XINPUT2`) +- Policy file existence and validity +- Extension policies +- Profile preferences +- Privacy settings + +**Usage:** +```bash +./test-firefox-config.sh +``` + +**Requirements:** +- Firefox installed +- Valid Home Manager configuration +- JSON parsing tool (`jq` or Python) + +## Running Tests + +### All Tests +```bash +tests/test-firefox-config.sh +``` + +### Specific Test +```bash +./tests/test-firefox-config.sh +``` + +## Test Conventions + +### Shell Scripts +- Use `set -e` for error handling +- Define colored output for readability +- Track pass/fail/skip counts +- Return non-zero on failure +- Use helper functions (`print_pass`, `print_fail`, etc.) + +### Test Coverage +Tests should verify: +- Configuration builds successfully +- Policies are active (`about:policies`) +- Settings are enforced +- Extensions are force-installed +- User preferences are applied + +## Adding New Tests + +1. Create test script in `tests/` +2. Make executable: `chmod +x tests/new-test.sh` +3. Document in this README +4. Follow naming convention: `test-.sh` + +## Notes + +- Many tests require services to be deployed first +- Some Firefox tests require Firefox to have been run once +- Automated tests complement manual verification +- Update test scripts when configurations change + +## Related Documentation + +- `context/` - Configuration documentation +- `agents.md` - Agent procedures \ No newline at end of file diff --git a/tests/test-firefox-config.sh b/tests/test-firefox-config.sh new file mode 100755 index 0000000..9925d7f --- /dev/null +++ b/tests/test-firefox-config.sh @@ -0,0 +1,477 @@ +#!/usr/bin/env bash +# Firefox Configuration Test Script +# Tests the Firefox Home Manager configuration for user jsutter + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Test counters +PASSED=0 +FAILED=0 +SKIPPED=0 +TOTAL=0 + +# Helper functions +print_header() { + echo "" + echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${BLUE} $1${NC}" + echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +} + +print_test() { + echo -e "\n${YELLOW}[TEST]${NC} $1" + ((TOTAL++)) +} + +print_pass() { + echo -e "${GREEN} ✓ PASS${NC}: $1" + ((PASSED++)) +} + +print_fail() { + echo -e "${RED} ✗ FAIL${NC}: $1" + ((FAILED++)) +} + +print_skip() { + echo -e "${YELLOW} ⊘ SKIP${NC}: $1" + ((SKIPPED++)) +} + +print_info() { + echo -e " ℹ ${NC}$1" +} + +print_report() { + local description="$1" + local pass_count="$2" + local fail_count="$3" + + if [[ $fail_count -eq 0 ]]; then + print_pass "$description (passed $pass_count/$pass_count)" + else + local total=$((pass_count + fail_count)) + print_fail "$description (passed $pass_count/$total)" + fi +} + +# Check if a command exists +command_exists() { + command -v "$1" &> /dev/null +} + +# Check if a preference matches expected value +check_pref() { + local profile="$1" + local pref="$2" + local expected="$3" + local actual=$(grep "\"${pref}\"" "$profile/prefs.js" 2>/dev/null | head -1 | sed 's/.*user_pref[^,]*,\s*//;s/);.*$//' 2>/dev/null || echo "") + + if [[ "$actual" == *"$expected"* ]]; then + print_pass "$pref = $expected" + return 0 + else + print_fail "$pref = $expected (got: $actual)" + return 1 + fi +} + +# Get Firefox profile directory +get_firefox_profile() { + local firefox_dir="$HOME/.mozilla/firefox" + + if [[ -d "$firefox_dir" ]]; then + local profile=$(grep -E "Default=1" "$firefox_dir/profiles.ini" 2>/dev/null | head -1 | cut -d= -f2 -s) + if [[ -n "$profile" ]] && [[ -d "$firefox_dir/$profile" ]]; then + echo "$firefox_dir/$profile" + return 0 + fi + + # Try to find jsutter profile + profile=$(find "$firefox_dir" -maxdepth 1 -type d -name "*jsutter*" 2>/dev/null | head -1) + if [[ -n "$profile" ]]; then + echo "$profile" + return 0 + fi + + # Get first available profile + profile=$(ls -1 "$firefox_dir"/*.default* 2>/dev/null | head -1) + if [[ -n "$profile" ]]; then + echo "$profile" + return 0 + fi + fi + + echo "" + return 1 +} + +# Get Firefox executable +get_firefox_exec() { + if command_exists firefox-esr &>/dev/null; then + echo firefox-esr + elif command_exists firefox &>/dev/null; then + echo firefox + elif [[ -f "/run/current-system/sw/bin/firefox" ]]; then + echo /run/current-system/sw/bin/firefox + else + echo "" + fi +} + +print_header "Firefox Configuration Test Script" +echo "Testing configuration for user: $(whoami)" +echo "Date: $(date '+%Y-%m-%d %H:%M:%S')" + +# ============================================ +# Environment Variables +# ============================================ +print_header "1. Environment Variables" + +print_test "Check MOZ_USE_XINPUT2 environment variable" +if [[ -z "$MOZ_USE_XINPUT2" ]]; then + print_fail "MOZ_USE_XINPUT2 not set" + print_info "This should be set in Home Manager sessionVariables" +else + if [[ "$MOZ_USE_XINPUT2" == "1" ]]; then + print_pass "MOZ_USE_XINPUT2 = 1" + else + print_fail "MOZ_USE_XINPUT2 = $MOZ_USE_XINPUT2 (expected: 1)" + fi +fi + +print_test "Check session variables in shell config" +if [[ -f "$HOME/.zshrc" ]] || [[ -f "$HOME/.bashrc" ]]; then + print_info "Shell config files found" +else + print_skip "No shell config file found" +fi + +# ============================================ +# Firefox Installation +# ============================================ +print_header "2. Firefox Installation" + +FIREFOX=$(get_firefox_exec) +if [[ -n "$FIREFOX" ]]; then + print_test "Firefox executable found" + print_pass "Firefox at: $FIREFOX" + + print_test "Firefox version" + VERSION=$($FIREFOX --version 2>/dev/null || echo "unknown") + print_pass "Firefox version: $VERSION" +else + print_fail "Firefox executable not found" + print_info "Check if Firefox is installed via nixos-rebuild switch" +fi + +# ============================================ +# Policy Configuration +# ============================================ +print_header "3. Enterprise Policies" + +POLICY_FILE="/run/current-system/sw/lib/firefox/distribution/policies.json" +print_test "Check policy file exists" +if [[ -f "$POLICY_FILE" ]]; then + print_pass "Policy file found: $POLICY_FILE" +else + print_fail "Policy file not found" + print_info "Activate the configuration with: nixos-rebuild switch" +fi + +if [[ -f "$POLICY_FILE" ]]; then + print_test "Check policy file is valid JSON" + if jq empty "$POLICY_FILE" 2>/dev/null; then + print_pass "Policy file is valid JSON" + else + print_fail "Policy file is not valid JSON" + fi + + print_test "Check extension policies" + EXTENSIONS=$(jq -r '.policies.ExtensionSettings | keys[]' "$POLICY_FILE" 2>/dev/null || echo "") + EXPECTED_EXTENSIONS=( + "uBlock0@raymondhill.net" + "{446900e4-71c2-419f-a6a7-df9c2b2dc922}" + "plasma-browser-integration@kde.org" + "webextension@metamask.io" + "kagi-search@kagi.com" + ) + + all_found=true + for ext in "${EXPECTED_EXTENSIONS[@]}"; do + if echo "$EXTENSIONS" | grep -q "$ext"; then + print_info "Extension policy found: $ext" + else + print_fail "Extension policy missing: $ext" + all_found=false + fi + done + if $all_found; then + print_pass "All extension policies configured" + fi + + print_test "Check privacy settings policies" + PRIVACY_OK=true + + if jq -e '.policies.OfferToSaveLogins == false' "$POLICY_FILE" &>/dev/null; then + print_info "Password saving disabled" + else + print_fail "Password saving not disabled" + PRIVACY_OK=false + fi + + if jq -e '.policies.PasswordManagerEnabled == false' "$POLICY_FILE" &>/dev/null; then + print_info "Password manager disabled" + else + print_fail "Password manager not disabled" + PRIVACY_OK=false + fi + + if jq -e '.policies.DisableTelemetry == true' "$POLICY_FILE" &>/dev/null; then + print_info "Telemetry disabled" + else + print_fail "Telemetry not disabled" + PRIVACY_OK=false + fi + + if jq -e '.policies.DisableFirefoxStudies == true' "$POLICY_FILE" &>/dev/null; then + print_info "Firefox Studies disabled" + else + print_fail "Firefox Studies not disabled" + PRIVACY_OK=false + fi + + if $PRIVACY_OK; then + print_pass "Privacy settings policies configured correctly" + fi + + print_test "Check homepage/new tab policies" + HOME_OK=true + + if jq -e '.policies.FirefoxHome.Search == false' "$POLICY_FILE" &>/dev/null; then + print_info "Search on home page disabled" + else + print_fail "Search on home page not disabled" + HOME_OK=false + fi + + if jq -e '.policies.FirefoxHome.TopSites == false' "$POLICY_FILE" &>/dev/null; then + print_info "Top Sites disabled" + else + print_fail "Top Sites not disabled" + HOME_OK=false + fi + + if jq -e '.policies.FirefoxHome.Pocket == false' "$POLICY_FILE" &>/dev/null; then + print_info "Pocket disabled" + else + print_fail "Pocket not disabled" + HOME_OK=false + fi + + if $HOME_OK; then + print_pass "Homepage/new tab policies configured correctly" + fi +fi + +# ============================================ +# User Profile Configuration +# ============================================ +print_header "4. Profile Configuration" + +PROFILE=$(get_firefox_profile) +if [[ -n "$PROFILE" ]]; then + print_test "Firefox profile directory found" + print_pass "Profile: $PROFILE" + + # Check for user.js + USER_JS="$PROFILE/user.js" + if [[ -f "$USER_JS" ]]; then + print_test "user.js file exists" + print_pass "user.js: $USER_JS" + + # Check key settings + print_test "Check key user.js preferences" + + cat > /tmp/firefox_check.sh << 'EOF' +#!/bin/bash +PROFILE="$1" +check_pref() { + local pref="$1" + local expected="$2" + local actual=$(grep "\"${pref}\"" "$PROFILE/user.js" 2>/dev/null | head -1 | sed 's/.*user_pref[^,]*,\s*//;s/);.*$//' 2>/dev/null || echo "") + + if [[ "$actual" == *"$expected"* ]]; then + echo "PASS: $pref" + return 0 + else + echo "FAIL: $pref (got: $actual)" + return 1 + fi +} + +check_pref "browser.ctrlTab.recentlyUsedOrder" "true" +check_pref "browser.tabs.hoverPreview.enabled" "false" +check_pref "browser.link.preview.enabled" "false" +check_pref "browser.newtabpage.enabled" "false" +check_pref "browser.startup.homepage" "about:blank" +check_pref "browser.search.suggest.enabled" "false" +check_pref "signon.rememberSignons" "false" +check_pref "media.videocontrols.picture-in-picture.allow-multiple" "false" +check_pref "browser.contentblocking.category" "strict" +check_pref "extensions.pocket.enabled" "false" +check_pref "privacy.sanitize.sanitizeOnShutdown" "true" +check_pref "toolkit.legacyUserProfileCustomizations.stylesheets" "true" +check_pref "privacy.userContext.enabled" "true" +EOF + chmod +x /tmp/firefox_check.sh + output=$(/tmp/firefox_check.sh "$PROFILE") + + pass_count=$(echo "$output" | grep -c "PASS" || true) + fail_count=$(echo "$output" | grep -c "FAIL" || true) + + echo "$output" | while read line; do + if [[ "$line" == "PASS:"* ]]; then + print_pass "${line#PASS: }" + else + print_fail "${line#FAIL: }" + fi + done + + print_report "user.js key settings" "$pass_count" "$fail_count" + else + print_fail "user.js not found (Firefox needs to be run once to generate)" + fi + + # Check for extensions + print_test "Check installed extensions" + INSTALLED_EXTENSIONS=$(ls -1 "$PROFILE/extensions" 2>/dev/null || echo "") + + if [[ -n "$INSTALLED_EXTENSIONS" ]]; then + print_info "Found $(echo "$INSTALLED_EXTENSIONS" | wc -l) extension(s):" + echo "$INSTALLED_EXTENSIONS" | while read ext; do + print_info " - $ext" + done + print_pass "Extensions directory exists" + else + print_skip "No extensions installed yet (start Firefox to install via policy)" + fi +else + print_fail "Firefox profile not found" + print_info "Start Firefox once to create a profile" + print_info "Profile location is typically: ~/.mozilla/firefox/*.default*" +fi + +# ============================================ +# Manual Verification Checklist +# ============================================ +print_header "5. Manual Verification Checklist" + +cat << 'MANUAL' + +The following items require manual verification in Firefox: + + [ ] 1. Open Firefox and check all 4 extensions are installed: + - uBlock Origin + - Privacy Badger + - Facebook Container + - Multi-account Containers + + [ ] 2. Open about:preferences#privacy and verify: + - Content blocking is set to "Strict" + - "Clear history when Firefox closes" is checked + - "Remember passwords" is unchecked + - "Autoplay" is set to "Block audio and video" + + [ ] 3. Open about:policies and verify: + - All policies are "Active" + - Extensions are listed as "Force Installed" + + [ ] 4. Test tab behavior: + - Press Ctrl+Tab and verify it cycles through recent tabs + - Hover over a tab and verify no image preview appears + + [ ] 5. Test new tab/homepage: + - Open a new tab (Ctrl+T) - should be blank + - Open a new window (Ctrl+N) - should be blank + - No search bar, shortcuts, or widgets should appear + + [ ] 6. Test search: + - Type in the address bar and verify no suggestions appear + - Search results should come from Kagi + - No other search engines should be available + - Sign in to Kagi account via Kagi extension for full features + + [ ] 7. Test smooth scrolling: + - Scroll on a long page - should feel smooth and responsive + + [ ] 8. Test permissions: + - Visit a site that requests location - should auto-block + - Visit a site that requests notifications - should auto-block + - Play a video with audio - should auto-block + +MANUAL + +print_info "Manual verification above requires user interaction" + +# ============================================ +# Troubleshooting Information +# ============================================ +print_header "6. Troubleshooting Information" + +echo "Useful Firefox URLs:" +print_info "about:policies - View active policies" +print_info "about:preferences - Firefox settings" +print_info "about:config - Advanced preferences" +print_info "about:support - Troubleshooting information" +print_info "about:addons - View/manage extensions" +print_info "about:preferences#privacy - Privacy settings" + +echo "" +print_info "Configuration files:" +print_info " Policies: $POLICY_FILE" +print_info " Profile: $PROFILE" +print_info " user.js: $USER_JS" +if [[ -n "$PROFILE" ]]; then + print_info " search.json.mozlz4: $PROFILE/search.json.mozlz4" +fi + +echo "" +print_info "To rebuild the configuration:" +print_info " nixos-rebuild switch" + +echo "" +print_info "To restart Firefox:" +print_info " pkill firefox" + +# ============================================ +# Summary +# ============================================ +print_header "Test Summary" +echo "Total tests: $TOTAL" +echo -e "${GREEN}Passed: $PASSED${NC}" +echo -e "${RED}Failed: $FAILED${NC}" +echo -e "${YELLOW}Skipped: $SKIPPED${NC}" + +if [[ $FAILED -eq 0 ]]; then + echo "" + echo -e "${GREEN}✓ All automated tests passed!${NC}" + echo "" + echo "Please run Firefox once to install extensions via policy," + echo "then complete the manual verification checklist above." + echo "" + echo "Note: Kagi search is the default and only search engine." + echo " Sign in to your Kagi account via the Kagi extension." + exit 0 +else + echo "" + echo -e "${RED}✗ Some tests failed. Review the output above for details.${NC}" + exit 1 +fi diff --git a/users/jsutter.nix b/users/jsutter.nix index 44af55d..a5c3712 100755 --- a/users/jsutter.nix +++ b/users/jsutter.nix @@ -33,6 +33,7 @@ home.sessionVariables = { OPENAI_API_KEY = "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"; SYNTHETIC_L_API_KEY = "syn_5bfe68ad3826bb7872f32fcf160e959a"; + MOZ_USE_XINPUT2 = "1"; }; programs.git = { @@ -171,16 +172,77 @@ OfferToSaveLogins = false; OfferToSaveLoginsDefault = false; PasswordManagerEnabled = false; + DisableFormHistory = true; FirefoxHome = { - Search = true; + Search = false; Pocket = false; Snippets = false; TopSites = false; Highlights = false; + Locked = { + Search = false; + Pocket = false; + Snippets = false; + TopSites = false; + Highlights = false; + }; }; UserMessaging = { ExtensionRecommendations = false; + FeatureRecommendations = false; SkipOnboarding = true; + MoreFromMozilla = false; + WhatsNew = false; + }; + FirefoxSuggest = { + WebSuggestions = false; + SponsoredSuggestions = false; + ImprovSuggest = false; + Locked = { + WebSuggestions = false; + SponsoredSuggestions = false; + ImprovSuggest = false; + }; + }; + Permissions = { + Location = { + BlockNewRequests = true; + Locked = true; + }; + Notifications = { + BlockNewRequests = true; + Locked = true; + }; + Autoplay = { + Default = "block-audio-video"; + Locked = true; + }; + VirtualReality = { + BlockNewRequests = true; + Locked = true; + }; + }; + ExtensionSettings = { + "uBlock0@raymondhill.net" = { + installation_mode = "force_installed"; + install_url = "https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi"; + }; + "{446900e4-71c2-419f-a6a7-df9c2b2dc922}" = { + installation_mode = "force_installed"; + install_url = "https://addons.mozilla.org/firefox/downloads/latest/bitwarden-password-manager/latest.xpi"; + }; + "plasma-browser-integration@kde.org" = { + installation_mode = "force_installed"; + install_url = "https://addons.mozilla.org/firefox/downloads/latest/plasma-integration/latest.xpi"; + }; + "webextension@metamask.io" = { + installation_mode = "force_installed"; + install_url = "https://addons.mozilla.org/firefox/downloads/latest/ether-metamask/latest.xpi"; + }; + "kagi-search@kagi.com" = { + installation_mode = "force_installed"; + install_url = "https://addons.mozilla.org/firefox/downloads/latest/kagi-for-firefox/latest.xpi"; + }; }; }; }; @@ -189,13 +251,106 @@ id = 0; name = "jsutter"; settings = { - "general.smoothScroll" = true; + "browser.ctrlTab.recentlyUsedOrder" = true; + "browser.urlbar.quicksuggest.enabled" = false; + "browser.urlbar.suggest.quicksuggest.sponsored" = false; + "browser.urlbar.suggest.quicksuggest.nonsponsored" = false; + "browser.link.preview.enabled" = false; + "browser.search.suggest.enabled" = false; + "browser.search.suggest.enabled.private" = false; + "extensions.pocket.enabled" = false; + "extensions.pocket.showHome" = false; + "browser.contentblocking.category" = "strict"; + "privacy.sanitize.sanitizeOnShutdown" = true; + "privacy.clearOnShutdown.cache" = true; + "privacy.clearOnShutdown.cookies" = true; + "privacy.clearOnShutdown.downloads" = true; + "privacy.clearOnShutdown.formdata" = true; + "privacy.clearOnShutdown.history" = true; + "privacy.clearOnShutdown.sessions" = true; + "signon.rememberSignons" = false; + "dom.private-attribution.submission.enabled" = false; + "dom.battery.enabled" = false; + "browser.uitour.enabled" = false; + "browser.urlbar.trimURLs" = true; + "layout.css.prefers-color-scheme.content-override" = 0; + "browser.tabs.hoverPreview.enabled" = false; + "browser.tabs.hoverPreview.showThumbnails" = false; + "media.videocontrols.picture-in-picture.allow-multiple" = false; + "media.hardware-video-decoding.force-enabled" = true; + "widget.gtk.overlay-scrollbars.enabled" = false; + "browser.toolbars.bookmarks.visibility" = "newtab"; + "browser.toolbars.toolbarbuttons.intended.icon-size" = "small"; + "browser.newtabpage.enabled" = false; + "browser.startup.homepage" = "about:blank"; + "browser.newtabpage.activity-stream.default.sites" = ""; + "browser.search.region" = "US"; + "browser.search.isUS" = true; + "layers.acceleration.force-enabled" = true; + "gfx.webrender.all" = true; + "gfx.webrender.enabled" = true; + "svg.context-properties.content.enabled" = true; + "browser.zoom.full" = true; + "ui.key.menuAccessKeyFocuses" = false; + }; + search = { + default = "Kagi"; + engines = { + "Kagi" = { + urls = [{ template = "https://kagi.com/search?q={searchTerms}"; }]; + metaData.alias = "@kagi"; + icon = "https://kagi.com/favicon.ico"; + }; + }; + force = true; + privateDefault = "Kagi"; }; extraConfig = '' user_pref("toolkit.legacyUserProfileCustomizations.stylesheets", true); user_pref("full-screen-api.ignore-widgets", true); user_pref("media.ffmpeg.vaapi.enabled", true); user_pref("media.rdd-vpx.enabled", true); + user_pref("media.av1.enabled", true); + user_pref("media.navigator.video.use_rtt", true); + user_pref("browser.display.use_document_fonts", 1); + user_pref("browser.display.use_system_colors", false); + user_pref("browser.menu.showCharacterEncoding", false); + user_pref("browser.newtabpage.activity-stream.feeds.section.topstories.options", ""); + user_pref("browser.newtabpage.activity-stream.feeds.topsites", false); + user_pref("browser.newtabpage.activity-stream.section.highlights.includePocket", false); + user_pref("browser.newtabpage.activity-stream.section.highlights.includeVisited", false); + user_pref("browser.newtabpage.activity-stream.section.highlights.includeBookmarks", false); + user_pref("browser.newtabpage.activity-stream.section.highlights.includeDownloads", false); + user_pref("browser.newtabpage.activity-stream.section.highlights.includePocket", false); + user_pref("app.shield.optoutstudies.enabled", false); + user_pref("datareporting.healthreport.uploadEnabled", false); + user_pref("datareporting.policy.dataSubmissionEnabled", false); + user_pref("experiments.activeExperiment", false); + user_pref("experiments.enabled", false); + user_pref("experiments.supported", false); + user_pref("network.cookie.cookieBehavior", 1); + user_pref("network.dns.disableIPv6", true); + user_pref("privacy.donottrackheader.enabled", true); + user_pref("privacy.resistFingerprinting", true); + user_pref("privacy.trackingprotection.enabled", true); + user_pref("privacy.trackingprotection.socialtracking.enabled", true); + user_pref("privacy.trackingprotection.fingerprinting.enabled", true); + user_pref("privacy.trackingprotection.cryptomining.enabled", true); + user_pref("privacy.userContext.enabled", true); + user_pref("privacy.userContext.longPressBehavior", 2); + user_pref("browser.urlbar.groupLabels.enabled", false); + user_pref("browser.search.widget.inNavBar", false); + user_pref("browser.search.hiddenOneOffs", "Google,Bing,DuckDuckGo,Amazon,Wikipedia,Yahoo,DDG"); + user_pref("browser.search.separatePrivateDefault.ui.enabled", false); + user_pref("browser.search.suggest.enabled", false); + user_pref("browser.search.suggest.enabled.private", false); + user_pref("browser.search.order", ""); + user_pref("browser.search.order.1", ""); + user_pref("browser.search.order.2", ""); + user_pref("browser.search.order.3", ""); + user_pref("browser.search.isUS", true); + user_pref("browser.search.region", "US"); + user_pref("browser.uiCustomization.state", "{\"placements\":{\"widget:[addon]bar-open\",\"nav-bar\":[\"back-button\",\"forward-button\",\"stop-reload-button\",\"urlbar-container\",\"downloads-button\",\"ublock0_raymondhill_net-browser-action\",\"_446900e4-71c2-419f-a6a7-df9c2b2dc922-browser-action\",\"plasma-browser-integration_kde_org-browser-action\",\"webextension_metamask_io-browser-action\",\"kagi-search_kagi_com-browser-action\"],\"toolbar-menubar\":[\"menubar-items\"],\"TabsToolbar\":[\"tabbrowser-tabs\",\"new-tab-button\",\"alltabs-button\"],\"PersonalToolbar\":[\"personal-bookmarks\"]},\"seen\":[\"developer-button\",\"ublock0_raymondhill_net-browser-action\",\"_446900e4-71c2-419f-a6a7-df9c2b2dc922-browser-action\",\"plasma-browser-integration_kde_org-browser-action\",\"webextension_metamask_io-browser-action\",\"kagi-search_kagi_com-browser-action\"],\"dirtyAreaCache\":[\"nav-bar\",\"toolbar-menubar\",\"TabsToolbar\",\"PersonalToolbar\"],\"currentVersion\":18,\"newElementState\":{}}"); ''; }; };