From 2e6f88a2d9b4c896d45cad54fdd23936e6f0a258 Mon Sep 17 00:00:00 2001 From: Julian Sutter Date: Mon, 16 Feb 2026 21:05:19 -0800 Subject: [PATCH 01/11] Add remote NixOS system management instructions to agents.md --- agents.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/agents.md b/agents.md index d255344..bfe5ee0 100644 --- a/agents.md +++ b/agents.md @@ -26,3 +26,51 @@ - Server configs may contain hardcoded credentials - use agenix or systemd credentials for production - Both warp and skip build successfully - Repository root: `/home/jsutter/src/nixos` + +## Remote System Management + +### Access Systems +SSH to machines using hostnames (resolve via local `/etc/hosts` or DNS): +```bash +ssh # Replace with actual system name +``` + +### 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 .#' + ``` + +### 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 +``` + +### Quick Management +```bash +# Check service status +ssh 'systemctl status ' + +# View logs +ssh 'journalctl -u -f' + +# Rebuild if build fails +ssh 'cd ~/src/nixos && git pull && sudo nixos-rebuild switch --flake .#' +``` + +### 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 From 06651efdb9e63df009fdf4e6b5cd58b606786eb3 Mon Sep 17 00:00:00 2001 From: Julian Sutter Date: Mon, 16 Feb 2026 21:16:00 -0800 Subject: [PATCH 02/11] Add note about checking NixOS wiki before adding new applications --- agents.md | 1 + 1 file changed, 1 insertion(+) diff --git a/agents.md b/agents.md index bfe5ee0..190b507 100644 --- a/agents.md +++ b/agents.md @@ -24,6 +24,7 @@ ## Important - Server configs may contain hardcoded credentials - use agenix or systemd credentials for production +- **Always carefully inspect the NixOS wiki for instructions before adding new applications to the repo** - Both warp and skip build successfully - Repository root: `/home/jsutter/src/nixos` From af88e29283320c31c86e48f618b91e4c70cb555d Mon Sep 17 00:00:00 2001 From: Julian Sutter Date: Mon, 16 Feb 2026 21:21:56 -0800 Subject: [PATCH 03/11] chore: Remove unused appflakes (Immich, Octofriend) --- appflakes/immich/README.md | 356 -------------------------------- appflakes/immich/flake.lock | 61 ------ appflakes/immich/flake.nix | 209 ------------------- appflakes/octofriend/README.md | 64 ------ appflakes/octofriend/flake.lock | 78 ------- appflakes/octofriend/flake.nix | 92 --------- 6 files changed, 860 deletions(-) delete mode 100755 appflakes/immich/README.md delete mode 100755 appflakes/immich/flake.lock delete mode 100755 appflakes/immich/flake.nix delete mode 100755 appflakes/octofriend/README.md delete mode 100755 appflakes/octofriend/flake.lock delete mode 100755 appflakes/octofriend/flake.nix diff --git a/appflakes/immich/README.md b/appflakes/immich/README.md deleted file mode 100755 index a345236..0000000 --- a/appflakes/immich/README.md +++ /dev/null @@ -1,356 +0,0 @@ -# Immich NixOS Module - -Self-hosted photo and video backup solution packaged as a NixOS module. - -## About Immich - -Immich is a high-performance self-hosted photo and video backup solution. This module deploys Immich using Docker Compose with automatic service management. - -**Key Features:** -- Automatic backup from mobile devices -- AI-powered face recognition -- Smart search with object and location detection -- Sharing albums with family and friends -- Timeline and map views -- Duplicate detection -- Video transcoding and playback - -## Usage - -### Basic Configuration - -Add this module to your NixOS configuration: - -```nix -{ config, pkgs, ... }: -{ - imports = [ - # Import the Immich Docker Compose module - (builtins.getFlake "path:/path/to/appflakes/immich").nixosModules.immich-docker - ]; - - services.immich-docker = { - enable = true; - domain = "immich.example.com"; - port = 2283; - dataDir = "/mnt/userdata/immich"; - }; -} -``` - -### With Flake Inputs - -If using flakes, add Immich as an input: - -```nix -{ - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - immich = { - url = "path:./appflakes/immich"; - }; - }; - - outputs = { self, nixpkgs, immich, ... }: { - nixosConfigurations.your-hostname = nixpkgs.lib.nixosSystem { - modules = [ - immich.nixosModules.immich-docker - ./configuration.nix - ]; - }; - }; -} -``` - -### Example for Warp Server - -```nix -{ config, ... }: - -{ - imports = [ - (builtins.getFlake "path:${self}/appflakes/immich").nixosModules.immich-docker - ]; - - services.immich-docker = { - enable = true; - domain = "warp.example.com"; - port = 2283; - dataDir = "/mnt/userdata/immich"; - dbPassword = "your-secure-password-here"; - typesenseApiKey = "your-typesense-key-here"; - enableWatchtower = true; - }; -} -``` - -## Configuration Options - -### `services.immich-docker.enable` (boolean) -**Default:** `false` - -Enable the Immich Docker Compose service. - -### `services.immich-docker.domain` (string) -**Default:** `"immich.local"` - -The domain name for the Immich web interface. - -### `services.immich-docker.port` (port) -**Default:** `2283` - -The port number for the Immich web interface. - -### `services.immich-docker.dataDir` (path) -**Default:** `"/mnt/userdata/immich"` - -Directory where Immich will store all data including: -- Uploaded photos and videos -- Processed images -- Database files -- Search indexes - -### `services.immich-docker.dbPassword` (string) -**Default:** `"immich"` - -PostgreSQL database password. **Change this in production!** - -### `services.immich-docker.typesenseApiKey` (string) -**Default:** `"immich-typesense-secret-key-change-this"` - -API key for the Typesense search engine. **Change this in production!** - -### `services.immich-docker.enableWatchtower` (boolean) -**Default:** `false` - -Enable automatic container updates via Watchtower. When enabled, Watchtower will update containers when new versions are available. - -## Deployment - -### Automatic Deployment - -The NixOS module handles everything automatically: - -1. Creates required directories -2. Generates docker-compose configuration -3. Configures systemd service -4. Starts all containers on boot - -### Manual Service Management - -```bash -# Start Immich -sudo systemctl start docker-compose-immich - -# Stop Immich -sudo systemctl stop docker-compose-immich - -# Restart Immich -sudo systemctl restart docker-compose-immich - -# Check status -sudo systemctl status docker-compose-immich - -# View logs -sudo journalctl -u docker-compose-immich -f -``` - -### Container Management - -```bash -# View all Immich containers -docker ps | grep immich - -# View logs for specific service -docker logs immich_server - -# Execute commands in container -docker exec -it immich_server bash -``` - -## Architecture - -This module deploys Immich as a Docker Compose stack with the following containers: - -- **immich_server**: Web API and interface -- **immich_microservices**: Background jobs and machine learning -- **immich_postgres**: PostgreSQL database with pgvector -- **immich_redis**: Redis cache -- **immich_typesense**: Search engine - -The stack is managed by systemd and starts automatically on boot. - -## Access - -Once deployed, access Immich at: - -- **Web Interface:** `http://your-domain:port` (default: `http://localhost:2283`) -- **Mobile App:** Configure using your domain and port - -## Storage Requirements - -Plan for significant storage based on your photo/video collection: - -- **High-res photos:** 5-15 MB per photo -- **Processed versions:** 2-5 MB additional per photo -- **Thumbnails:** ~500 KB per photo -- **4K videos:** 100-500 MB per minute -- **Database:** ~100-500 MB for 10,000 photos - -**Recommended:** -- Start with at least 100GB for small collections -- 500GB+ for growing collections -- 1TB+ for serious photographers - -## Security Recommendations - -1. **Change Default Passwords**: Update `dbPassword` and `typesenseApiKey` in your NixOS configuration -2. **Reverse Proxy:** Use Nginx with SSL/TLS for production -3. **Firewall:** Restrict access to port 2283 -4. **Backups:** Regularly backup the `dataDir` -5. **Updates**: Keep Immich containers updated (enable Watchtower or update the image tag in the module) -6. **Network:** Consider VPN access for remote management - -## Backup Strategy - -### Automated Backup Example - -```nix -{ config, pkgs, ... }: - -{ - services.immich-docker.enable = true; - - # Example: Regular backups using restic - services.restic.backups.immich = { - paths = [ "/mnt/userdata/immich" ]; - repository = "s3:backup-bucket/immich"; - passwordFile = "/etc/restic/password"; - timerConfig = { - OnCalendar = "daily"; - Persistent = true; - }; - }; -} -``` - -### Important Backup Locations - -Ensure these are included in backups: -- `${dataDir}/upload/` - Original photos and videos -- `${dataDir}/postgres/` - Database -- `${dataDir}/typesense/` - Search indexes - -## Troubleshooting - -### Service Won't Start - -```bash -# Check service logs -sudo journalctl -u docker-compose-immich -n 100 - -# Check Docker status -sudo systemctl status docker - -# Verify directories exist -ls -la /mnt/userdata/immich -``` - -### Database Connection Issues - -```bash -# Check PostgreSQL container -docker logs immich_postgres - -# Test database connectivity -docker exec -it immich_postgres psql -U immich -d immich -``` - -### Performance Issues - -- Ensure adequate CPU (4+ cores recommended) -- Allocate sufficient RAM (8GB+ recommended) -- Monitor Docker resource usage -- Check storage space: `df -h /mnt/userdata` - -### Port Already In Use - -If port 2283 is already in use, change the port: - -```nix -services.immich.port = 8080; # Use alternative port -``` - -## Migration - -### From Manual Docker Compose - -1. Stop existing containers -2. Backup current data directory -3. Update NixOS configuration to use this module -4. Set `dataDir` to your current data directory -5. Apply new configuration -6. Start service: `sudo systemctl start docker-compose-immich` - -## Version Updates - -### Manual Updates - -Update the Immich version by modifying the image tag in the flake: - -```nix -# In flake.nix, change: -image: ghcr.io/immich-app/immich-server:v1.122.2 - -# To: -image: ghcr.io/immich-app/immich-server:v1.123.0 -``` - -Then rebuild the NixOS configuration. - -### Automatic Updates - -Enable Watchtower for automatic updates: - -```nix -services.immich = { - enable = true; - enableWatchtower = true; # Enables automatic container updates -}; -``` - -## Resources - -- [Immich Official Website](https://immich.app) -- [Immich Documentation](https://immich.app/docs) -- [Immich GitHub](https://github.com/immich-app/immich) -- [Docker Compose Reference](https://docs.docker.com/compose/) - -## Support - -For Immich-specific issues, refer to: -- [Immich GitHub Issues](https://github.com/immich-app/immich/issues) -- [Immich Discord Community](https://discord.gg/DSbk7SPrCj) - -For NixOS module issues, check: -- Module configuration and syntax -- Docker integration -- Systemd service logs - -## Version Updates - -To update Immich to a newer version, modify the image tag in the flake: - -```nix -# In appflakes/immich/flake.nix, change: -image: ghcr.io/immich-app/immich-server:v1.122.2 - -# To: -image: ghcr.io/immich-app/immich-server:v1.123.0 -``` - -Then rebuild your NixOS configuration. - -## License - -This NixOS module follows the same license as the Immich project (AGPL-3.0). \ No newline at end of file diff --git a/appflakes/immich/flake.lock b/appflakes/immich/flake.lock deleted file mode 100755 index 668ac53..0000000 --- a/appflakes/immich/flake.lock +++ /dev/null @@ -1,61 +0,0 @@ -{ - "nodes": { - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1770115704, - "narHash": "sha256-KHFT9UWOF2yRPlAnSXQJh6uVcgNcWlFqqiAZ7OVlHNc=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "e6eae2ee2110f3d31110d5c222cd395303343b08", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/appflakes/immich/flake.nix b/appflakes/immich/flake.nix deleted file mode 100755 index 2854894..0000000 --- a/appflakes/immich/flake.nix +++ /dev/null @@ -1,209 +0,0 @@ -{ - description = "Immich - Self-hosted photo and video backup solution (Docker Compose deployment)"; - - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - flake-utils.url = "github:numtide/flake-utils"; - }; - - outputs = { self, nixpkgs, flake-utils }: - let - # NixOS module to integrate Immich with the system - nixosModule = { config, lib, pkgs, ... }: - with lib; - let - cfg = config.services.immich-docker; - in - { - options.services.immich-docker = { - enable = mkEnableOption "Immich photo management server (Docker Compose deployment)"; - - domain = mkOption { - type = types.str; - default = "immich.local"; - description = "Domain name for Immich web interface"; - }; - - port = mkOption { - type = types.port; - default = 2283; - description = "Port for Immich web interface"; - }; - - dataDir = mkOption { - type = types.path; - default = "/mnt/userdata/immich"; - description = "Directory for Immich data"; - }; - - dbPassword = mkOption { - type = types.str; - default = "immich"; - description = "PostgreSQL database password"; - }; - - typesenseApiKey = mkOption { - type = types.str; - default = "immich-typesense-secret-key-change-this"; - description = "Typesense search API key"; - }; - - enableWatchtower = mkOption { - type = types.bool; - default = false; - description = "Enable automatic container updates via Watchtower"; - }; - }; - - config = mkIf cfg.enable { - # Check for conflicts with the built-in Immich module - assertions = [ - { - assertion = !(config.services.immich.enable or false); - message = "services.immich-docker conflicts with services.immich. Please disable the default Nixpkgs Immich module by setting services.immich.enable = false."; - } - ]; - - # Ensure Docker is available - virtualisation.docker.enable = mkDefault true; - - # Install docker-compose - environment.systemPackages = with pkgs; [ - docker-compose - ]; - - # Create required directories - systemd.tmpfiles.rules = [ - "d /etc/compose/immich 0755 root root -" - "d ${cfg.dataDir}/upload 0755 root root -" - "d ${cfg.dataDir}/library 0755 root root -" - "d ${cfg.dataDir}/postgres 0755 root root -" - "d ${cfg.dataDir}/typesense 0755 root root -" - ]; - - # Generate docker-compose configuration - environment.etc."compose/immich/docker-compose.yml".text = '' - version: "3.8" - - services: - immich_server: - image: ghcr.io/immich-app/immich-server:v1.122.2 - container_name: immich_server - ports: - - "${toString cfg.port}:3000" - environment: - - DB_HOSTNAME=immich_postgres - - DB_USERNAME=immich - - DB_PASSWORD=${cfg.dbPassword} - - DB_DATABASE_NAME=immich - - REDIS_HOSTNAME=immich_redis - - TYPESENSE_HOST=immich_typesense - - IMMICH_WEB_URL=http://localhost:${toString cfg.port} - - DISABLE_REVERSE_GEOCODING=false - volumes: - - ${cfg.dataDir}/upload:/usr/src/app/upload - - ${cfg.dataDir}/library:/usr/src/app/library - - /etc/localtime:/etc/localtime:ro - depends_on: - - immich_postgres - - immich_redis - - immich_typesense - restart: unless-stopped - ${if cfg.enableWatchtower then ''labels: - com.centurylinklabs.watchtower.enable: "true"'' else ""} - - immich_microservices: - image: ghcr.io/immich-app/immich-server:v1.122.2 - container_name: immich_microservices - command: ["start-microservices.sh"] - environment: - - DB_HOSTNAME=immich_postgres - - DB_USERNAME=immich - - DB_PASSWORD=${cfg.dbPassword} - - DB_DATABASE_NAME=immich - - REDIS_HOSTNAME=immich_redis - - TYPESENSE_HOST=immich_typesense - - DISABLE_REVERSE_GEOCODING=false - volumes: - - ${cfg.dataDir}/upload:/usr/src/app/upload - - ${cfg.dataDir}/library:/usr/src/app/library - - /etc/localtime:/etc/localtime:ro - depends_on: - - immich_postgres - - immich_redis - - immich_typesense - restart: unless-stopped - ${if cfg.enableWatchtower then ''labels: - com.centurylinklabs.watchtower.enable: "true"'' else ""} - - immich_postgres: - image: tensorchord/pgvecto-rs:pg14-v0.2.0 - container_name: immich_postgres - environment: - - POSTGRES_USER=immich - - POSTGRES_PASSWORD=${cfg.dbPassword} - - POSTGRES_DB=immich - volumes: - - ${cfg.dataDir}/postgres:/var/lib/postgresql/data - restart: unless-stopped - ${if cfg.enableWatchtower then ''labels: - com.centurylinklabs.watchtower.enable: "true"'' else ""} - - immich_redis: - image: redis:7-alpine - container_name: immich_redis - restart: unless-stopped - ${if cfg.enableWatchtower then ''labels: - com.centurylinklabs.watchtower.enable: "true"'' else ""} - - immich_typesense: - image: typesense/typesense:0.24.0 - container_name: immich_typesense - environment: - - TYPESENSE_API_KEY=${cfg.typesenseApiKey} - - TYPESENSE_DATA_DIR=/data - volumes: - - ${cfg.dataDir}/typesense:/data - restart: unless-stopped - ${if cfg.enableWatchtower then ''labels: - com.centurylinklabs.watchtower.enable: "true"'' else ""} - - networks: - default: - name: immich_network - ''; - - # Systemd service to manage Immich docker-compose stack - systemd.services.docker-compose-immich = { - description = "Immich Docker Compose Stack"; - after = [ "docker.service" "network-online.target" ]; - wants = [ "network-online.target" ]; - requiredBy = [ "multi-user.target" ]; - path = [ pkgs.docker-compose pkgs.docker ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = "yes"; - WorkingDirectory = "/etc/compose/immich"; - ExecStart = "${pkgs.docker-compose}/bin/docker-compose up -d"; - ExecStop = "${pkgs.docker-compose}/bin/docker-compose down"; - }; - }; - - # Network firewall configuration (optional, enable if using firewall) - # networking.firewall.allowedTCPPorts = [ cfg.port ]; - }; - }; - in - { - # Provide NixOS module - nixosModules.immich-docker = nixosModule; - - # Provide the module as the default package - packages = flake-utils.lib.eachDefaultSystem (system: - { - immich-docker-module = nixosModule; - default = nixosModule; - } - ); - }; -} diff --git a/appflakes/octofriend/README.md b/appflakes/octofriend/README.md deleted file mode 100755 index 8d999df..0000000 --- a/appflakes/octofriend/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# Octofriend flake for Nix/NixOS - -This flake packages [octofriend](https://github.com/synthetic-lab/octofriend), a CLI coding assistant. - -## Building - -```bash -nix build . -``` - -## Running - -```bash -nix run . -``` - -## Installation - -Add to your NixOS configuration: -```nix -inputs.octofriend.url = "path:/home/jsutter/src/nixos/appflakes/octofriend"; - -# Then in your environment.systemPackages or home.packages: -self.inputs.octofriend.packages.${system}.default -``` - -## Configuration - -The octofriend flake includes default configuration files at `/share/octofriend/config/octofriend.json5`. However, API keys should not be stored in the repository for security reasons. - -### Default Configuration - -The included configuration provides: -- Your name as 'Jules' -- Two model configurations (GLM-4.6 and MiniMax M2) pointing to https://api.synthetic.new -- Specialized models for diff-apply and fix-json operations - -### API Keys - -To configure API keys, create a `keys.json5` file in your configuration directory: -```json5 -{ - 'https://api.synthetic.new/v1': 'your-api-key-here' -} -``` - -When using octofriend, you can either: -1. Place your keys in `~/.config/octofriend/keys.json5` (default location) -2. Set the `OCTOFRIEND_CONFIG_DIR` environment variable to a custom directory containing your files - -This ensures sensitive API keys are not committed to the repository while still providing functional default configuration. - -### Custom Configuration - -If you want to override the default configuration, you can create your own `octofriend.json5` file in your config directory. The wrapper script will check for configuration files in the following order: -1. `$OCTOFRIEND_CONFIG_DIR/octofriend.json5` (if OCTOFRIEND_CONFIG_DIR is set) -2. `~/.config/octofriend/octofriend.json5` -3. Fall back to the included default at `$OCTOFRIEND_PACKAGE_DIR/share/octofriend/config/octofriend.json5` - -## Security Note - -- The `keys.json5` file containing API keys is intentionally NOT included in this flake -- Never commit API keys to any repository -- The included configuration uses placeholder settings and should be customized with your actual model preferences and endpoints \ No newline at end of file diff --git a/appflakes/octofriend/flake.lock b/appflakes/octofriend/flake.lock deleted file mode 100755 index bcd51f3..0000000 --- a/appflakes/octofriend/flake.lock +++ /dev/null @@ -1,78 +0,0 @@ -{ - "nodes": { - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1765779637, - "narHash": "sha256-KJ2wa/BLSrTqDjbfyNx70ov/HdgNBCBBSQP3BIzKnv4=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "1306659b587dc277866c7b69eb97e5f07864d8c4", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "octofriend": { - "flake": false, - "locked": { - "lastModified": 1766013652, - "narHash": "sha256-X7t2CGNZP+OS2pbMtRAg3XIGq3HA3N3rPk5ELV1FNRQ=", - "owner": "synthetic-lab", - "repo": "octofriend", - "rev": "6d9c260743e4b645fd4b216ce1cd979d5c0dd473", - "type": "github" - }, - "original": { - "owner": "synthetic-lab", - "repo": "octofriend", - "type": "github" - } - }, - "root": { - "inputs": { - "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs", - "octofriend": "octofriend" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/appflakes/octofriend/flake.nix b/appflakes/octofriend/flake.nix deleted file mode 100755 index 14f3347..0000000 --- a/appflakes/octofriend/flake.nix +++ /dev/null @@ -1,92 +0,0 @@ -{ - description = "Octofriend (synthetic-lab/octofriend) packaged for Nix/NixOS"; - - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - flake-utils.url = "github:numtide/flake-utils"; - - # Upstream source (not a flake) - octofriend = { - url = "github:synthetic-lab/octofriend"; - flake = false; - }; - }; - - outputs = { self, nixpkgs, flake-utils, octofriend }: - flake-utils.lib.eachDefaultSystem (system: - let - pkgs = import nixpkgs { inherit system; }; - lib = pkgs.lib; - in - { - packages.default = pkgs.buildNpmPackage { - pname = "octofriend"; - version = "git-${builtins.substring 0 7 (octofriend.rev or "unknown")}"; - - src = octofriend; - - # First build will fail telling you the correct value. Paste it here. - npmDepsHash = "sha256-luAdTozvb0MTuh+xa5qhZPH8YflF9R4GtREAaR8m6Y8="; - - # Ensure a modern node (octofriend is a Node CLI). :contentReference[oaicite:1]{index=1} - nodejs = pkgs.nodejs_22; - - nativeBuildInputs = [ pkgs.makeWrapper ]; - - # If octofriend shells out to git/ssh/etc, keep PATH sane. - postInstall = '' - if [ -x "$out/bin/octofriend" ]; then - wrapProgram "$out/bin/octofriend" \ - --prefix PATH : ${lib.makeBinPath [ pkgs.git pkgs.openssh ]} \ - --set OCTOFRIEND_CONFIG_DIR "$out/share/octofriend/config" - fi - # Provide the short alias if upstream didn't already. - if [ -x "$out/bin/octofriend" ] && [ ! -e "$out/bin/octo" ]; then - ln -s "$out/bin/octofriend" "$out/bin/octo" - fi - # Install the config file - mkdir -p "$out/share/octofriend/config" - cat << 'EOF' > "$out/share/octofriend/config/octofriend.json5" -{ - yourName: 'Jules', - models: [ - { - model: 'hf:zai-org/GLM-4.6', - nickname: 'GLM-4.6 (Synthetic)', - context: 131072, - baseUrl: 'https://api.synthetic.new/v1', - }, - { - model: 'hf:MiniMaxAI/MiniMax-M2', - nickname: 'MiniMax M2 (Synthetic)', - context: 98304, - baseUrl: 'https://api.synthetic.new/v1', - }, - ], - defaultApiKeyOverrides: {}, - diffApply: { - baseUrl: 'https://api.synthetic.new/v1', - model: 'hf:syntheticlab/diff-apply', - }, - fixJson: { - baseUrl: 'https://api.synthetic.new/v1', - model: 'hf:syntheticlab/fix-json', - }, -} -EOF - ''; - - meta = with lib; { - description = "Octofriend CLI coding assistant"; - homepage = "https://github.com/synthetic-lab/octofriend"; - license = licenses.mit; - mainProgram = "octofriend"; - }; - }; - - apps.default = flake-utils.lib.mkApp { - drv = self.packages.${system}.default; - exePath = "/bin/octofriend"; - }; - }); -} \ No newline at end of file From 9777e88032150a5a6eb94b48081be7524ca56937 Mon Sep 17 00:00:00 2001 From: Julian Sutter Date: Mon, 16 Feb 2026 21:30:50 -0800 Subject: [PATCH 04/11] docs: Add session start protocol and improve README structure --- README.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 460439e..44c5f15 100755 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ +# NixOS Configuration Repository + +## Session Start Protocol +Always begin by reading agents.md for workflow instructions. + +## System Installation + +1. Partition the disk: ``` sudo parted /dev/nvme0n1 -- mklabel gpt sudo parted /dev/nvme0n1 -- mkpart primary ext4 512MB 100% @@ -5,7 +13,10 @@ sudo parted /dev/nvme0n1 -- mkpart ESP fat32 1MB 512MB sudo parted /dev/nvme0n1 -- set 2 esp on sleep 2 sudo mkfs.ext4 /dev/disk/by-partlabel/primary +``` +2. Mount the filesystems: +``` sudo mount -o rw /dev/disk/by-partlabel/primary /mnt/ sudo mkdir /mnt/boot sudo mkfs.vfat /dev/disk/by-partlabel/ESP @@ -14,13 +25,14 @@ sudo mkdir /mnt/root sudo git clone https://jsutter:b9cf9383b20dc6efe4d0a732d659709097879b67@git.symbiotrip.com/jsutter/nixos /mnt/root/nixos ``` -Then: +3. Install NixOS: ``` sudo -i cd /mnt/root/nixos nixos-install --flake .# --no-root-password --impure ``` -Finally: + +4. Set user password: ``` nixos-enter --root '/mnt' passwd jsutter From 12bb7b0eacbb2a58582810f4f72c84b45482a6e4 Mon Sep 17 00:00:00 2001 From: Julian Sutter Date: Mon, 16 Feb 2026 21:51:05 -0800 Subject: [PATCH 05/11] Add Immich server configuration --- flake.lock | 6 +- flake.nix | 1 + servers/immich.nix | 165 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 169 insertions(+), 3 deletions(-) create mode 100644 servers/immich.nix diff --git a/flake.lock b/flake.lock index 9f1b917..8dd7d52 100755 --- a/flake.lock +++ b/flake.lock @@ -39,11 +39,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1770136044, - "narHash": "sha256-tlFqNG/uzz2++aAmn4v8J0vAkV3z7XngeIIB3rM3650=", + "lastModified": 1771208521, + "narHash": "sha256-X01Q3DgSpjeBpapoGA4rzKOn25qdKxbPnxHeMLNoHTU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "e576e3c9cf9bad747afcddd9e34f51d18c855b4e", + "rev": "fa56d7d6de78f5a7f997b0ea2bc6efd5868ad9e8", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 8f5fbd6..93de189 100755 --- a/flake.nix +++ b/flake.nix @@ -87,6 +87,7 @@ ./systems/warp.nix ./servers/nginx.nix ./servers/forgejo.nix + ./servers/immich.nix ]; }; skip = mkSystem { diff --git a/servers/immich.nix b/servers/immich.nix new file mode 100644 index 0000000..4d0ce8c --- /dev/null +++ b/servers/immich.nix @@ -0,0 +1,165 @@ +{ lib, pkgs, config, ... }: +let + cfg = config.services.immich; + + # External domain for Immich + fqdn = "photos.symbiotrip.com"; + + # Immich service port (default) + immichPort = 2283; + + # Immich media storage location + mediaLocation = "/var/lib/immich/media"; + + # Borg backup repository path + borgRepo = "/var/backups/immich-borg"; + +in + +{ + # DNS-based ACME certificate configuration + # Uses defaults (dnsProvider, email, credentials) from nginx.nix + security.acme.certs.${fqdn} = { + # Group needs to be set so nginx can read the certificate + group = config.services.nginx.group; + + # Inherit DNS challenge configuration from security.acme.defaults (set in nginx.nix) + # This includes: dnsProvider = "cloudflare", environmentFile with Cloudflare token, email + + # Explicitly ensure DNS mode (not HTTP-01) + webroot = null; + }; + + # Nginx reverse proxy for Immich + services.nginx.virtualHosts.${fqdn} = { + # Use DNS-based ACME certificate instead of HTTP-01 challenge + enableACME = false; + useACMEHost = fqdn; + forceSSL = true; + + extraConfig = '' + # Large upload limit for photos and videos (50GB max) + client_max_body_size 50000M; + + # Proxy headers + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # Extended timeouts for large file uploads + client_body_timeout 600s; + client_header_timeout 600s; + proxy_read_timeout 600s; + proxy_send_timeout 600s; + send_timeout 600s; + + # Logging for debugging + access_log /var/log/nginx/${fqdn}-access.log; + error_log /var/log/nginx/${fqdn}-error.log warn; + ''; + + locations."/" = { + proxyPass = "http://localhost:${toString immichPort}"; + proxyWebsockets = true; + recommendedProxySettings = true; + + # Extra websocket configuration + extraConfig = '' + # WebSocket upgrade headers + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + ''; + }; + }; + + # Immich service configuration (NixOS native) + services.immich = { + enable = true; + + # Bind to localhost only - nginx handles public HTTPS + host = "127.0.0.1"; + port = immichPort; + + # Media storage location + mediaLocation = mediaLocation; + + # Firewall - nginx handles external access + openFirewall = false; + }; + + # Hardware acceleration for video transcoding + # This leverages Intel Quick Sync Video on the NUC's Intel GPU + services.immich.accelerationDevices = null; + + # Enable graphics acceleration + hardware.graphics = { + enable = true; + enable32Bit = true; + extraPackages = with pkgs; [ + intel-media-driver # Intel Quick Sync Video support + ]; + }; + + # Add immich user to video and render groups for GPU access + users.users.immich.extraGroups = [ "video" "render" ]; + + # Ensure nginx starts after ACME certificate is available + systemd.services.nginx = { + after = [ "acme-${fqdn}.service" "acme-${fqdn}-renew.service" ]; + requires = [ "acme-${fqdn}.service" ]; + }; + + # PostgreSQL database backups for Immich + services.postgresqlBackup = { + enable = true; + databases = [ "immich" ]; + location = "/var/backups/postgresql/immich"; + startAt = "daily"; + compression = "zstd"; + }; + + # Borgbackup for Immich media files + # Manual repository initialization required: + # sudo -u borg borg init --encryption=repokey-blake2 ${borgRepo} + services.borgbackup.jobs."immich" = { + paths = [ mediaLocation ]; + repo = borgRepo; + + # Run backup daily at 3 AM + startAt = "*-*-* 03:00:00"; + + compression = "auto,zstd,6"; + encryption.mode = "none"; # Repository handles encryption + + prune = { + keep = { + within = "1d"; # Keep all backups from the last day + daily = 7; # Keep last 7 daily backups + weekly = 4; # Keep last 4 weekly backups + monthly = 6; # Keep last 6 monthly backups + }; + }; + }; + + # Create borg backup directory with proper permissions + system.activationScripts.immich-borg-dir = '' + mkdir -p ${borgRepo} + chown -R borg:borg ${borgRepo} + chmod 700 ${borgRepo} + ''; + + # Create Immich media directory with proper permissions + system.activationScripts.immich-media-dir = '' + mkdir -p ${mediaLocation} + chown -R immich:immich ${mediaLocation} + chmod 755 ${mediaLocation} + ''; + + # Systemd service to monitor disk usage + services.prometheus.exporters.node = { + enable = true; + enabledCollectors = [ "systemd" "textfile" "filesystem" ]; + port = 9100; + }; +} From 7c014f6534176bc8b9241c856c7f78149ab45974 Mon Sep 17 00:00:00 2001 From: Julian Sutter Date: Mon, 16 Feb 2026 22:05:18 -0800 Subject: [PATCH 06/11] Add development standards and procedures for app deployment - Add curl timeout requirement (5 seconds) - Add comprehensive 8-step workflow for new application deployment - Add troubleshooting procedures for domain availability and SSL certs - Add infrastructure roadmap for Borg backup server and sops-nix - Update README with session start protocol and infrastructure tasks --- README.md | 17 ++++++++++- agents.md | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 101 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 44c5f15..2f4a973 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # NixOS Configuration Repository ## Session Start Protocol -Always begin by reading agents.md for workflow instructions. +Always begin by reading agents.md for workflow instructions and development standards. ## System Installation @@ -37,3 +37,18 @@ nixos-install --flake .# --no-root-password --impure nixos-enter --root '/mnt' passwd jsutter ``` + +## Infrastructure Roadmap + +### Planned Work + +#### Borg Backup Server +- Set up a dedicated Borg backup server for automated backups +- Configure backup schedules for critical systems +- Implement retention policies and pruning rules + +#### Secrets Management with sops-nix +- Implement sops-nix for secrets management +- Move all hardcoded secrets from server configs into sops-nix +- Set up encryption keys and key rotation policies +- Document the secrets management workflow diff --git a/agents.md b/agents.md index 190b507..6c517ec 100644 --- a/agents.md +++ b/agents.md @@ -23,11 +23,93 @@ 4. Check success message: `"Done. The new configuration is /nix/store/..."` ## Important -- Server configs may contain hardcoded credentials - use agenix or systemd credentials for production -- **Always carefully inspect the NixOS wiki for instructions before adding new applications to the repo** -- Both warp and skip build successfully +- 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. - Repository root: `/home/jsutter/src/nixos` +## Development Standards + +### curl Usage +When using curl commands, always set a timeout to 5 seconds: +```bash +curl --max-time 5 +# or +curl -m 5 +``` + +## Procedures + +### Adding a New Application to the Repository + +1. **Gather Requirements** + - Ask the user what server to deploy to + - Ask the user what domain name the app will be available on + +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 + +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 + +4. **Local Testing** + - Test the build locally: `nixos-rebuild build --flake .#` + - Refine the configuration until the build succeeds + - Review the generated configuration for correctness + +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 .#` + +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 + +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 + +8. **Process Improvement** + - After successful deployment, propose 3 suggestions to add to agents.md that would help with future deployments: + 1. [Specific pattern or configuration approach discovered] + 2. [Common pitfall to avoid] + 3. [Useful command or tool discovered] + +### Infrastructure Tasks + +#### Planned Work + +1. **Borg Backup Server** + - Set up a dedicated Borg backup server for automated backups + - Configure backup schedules for critical systems + - Implement retention policies and pruning rules + +2. **Secrets Management with sops-nix** + - Implement sops-nix for secrets management + - Move all hardcoded secrets from server configs into sops-nix + - Set up encryption keys and key rotation policies + - Document the secrets management workflow + + ## Remote System Management ### Access Systems From 7db4dc3f25f777a3805df6db4711e35575cb2ee4 Mon Sep 17 00:00:00 2001 From: Julian Sutter Date: Mon, 16 Feb 2026 22:14:05 -0800 Subject: [PATCH 07/11] Add DNS management and deployment procedures to agents.md --- agents.md | 63 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/agents.md b/agents.md index 6c517ec..2f7181f 100644 --- a/agents.md +++ b/agents.md @@ -32,11 +32,7 @@ ### curl Usage When using curl commands, always set a timeout to 5 seconds: -```bash -curl --max-time 5 -# or -curl -m 5 -``` +curl -m 5 ## Procedures @@ -57,6 +53,7 @@ curl -m 5 - 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 + - Create A record at Cloudflare if needed 4. **Local Testing** - Test the build locally: `nixos-rebuild build --flake .#` @@ -88,27 +85,46 @@ curl -m 5 - Check Let's Encrypt logs: `journalctl -u certbot -f` - Manually trigger certificate renewal if needed + ### 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":true}' + ``` + **Common DNS Issues:** + - Local DNS caching: Add entry to `/etc/hosts` temporarily for testing + - Use Cloudflare's proxy IPs directly if DNS propagation is slow + 8. **Process Improvement** - - After successful deployment, propose 3 suggestions to add to agents.md that would help with future deployments: - 1. [Specific pattern or configuration approach discovered] - 2. [Common pitfall to avoid] - 3. [Useful command or tool discovered] + - After successful deployment, propose 3 new tools to add to agents.md. -### Infrastructure Tasks +### Useful Commands -#### Planned Work +```bash +# Check generated configuration before deployment +nix eval '.#nixosConfigurations..config.services..enable' -1. **Borg Backup Server** - - Set up a dedicated Borg backup server for automated backups - - Configure backup schedules for critical systems - - Implement retention policies and pruning rules +# List systemd services from new config +ls /nix/store/-nixos-system-/etc/systemd/system/*.service -2. **Secrets Management with sops-nix** - - Implement sops-nix for secrets management - - Move all hardcoded secrets from server configs into sops-nix - - Set up encryption keys and key rotation policies - - Document the secrets management workflow +# 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 +``` ## Remote System Management @@ -151,6 +167,13 @@ ssh 'journalctl -u -f' # Rebuild if build fails ssh 'cd ~/src/nixos && git pull && sudo nixos-rebuild switch --flake .#' + +# Test site availability via IP +ssh 'curl -k -I https://localhost:' +curl -I https:// -H "Host: " + +# Get public IP +curl -s https://api.ipify.org ``` ### Repository From 46b60f6adeb8b203d5c69d1a601b56723a1f6a75 Mon Sep 17 00:00:00 2001 From: Julian Sutter Date: Mon, 16 Feb 2026 22:18:36 -0800 Subject: [PATCH 08/11] Update DNS procedure to use non-proxied Cloudflare records --- agents.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/agents.md b/agents.md index 2f7181f..1c80d63 100644 --- a/agents.md +++ b/agents.md @@ -98,10 +98,11 @@ curl -m 5 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":true}' + --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** From fbe5142451d74a09d30dc864b5889906991c82e1 Mon Sep 17 00:00:00 2001 From: Julian Sutter Date: Mon, 16 Feb 2026 22:23:43 -0800 Subject: [PATCH 09/11] Test build for immich on warp --- .zed/settings.json | 8 ++++++++ servers/immich.nix | 9 +-------- 2 files changed, 9 insertions(+), 8 deletions(-) create mode 100644 .zed/settings.json diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 0000000..c311d5e --- /dev/null +++ b/.zed/settings.json @@ -0,0 +1,8 @@ +{ + "project_name": "Bedrock", + "indent_guides": { + "background_coloring": "indent_aware", + "coloring": "fixed" + }, + "tab_size": 2 +} diff --git a/servers/immich.nix b/servers/immich.nix index 4d0ce8c..5639aed 100644 --- a/servers/immich.nix +++ b/servers/immich.nix @@ -65,8 +65,6 @@ in # Extra websocket configuration extraConfig = '' - # WebSocket upgrade headers - proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; ''; @@ -142,12 +140,7 @@ in }; }; - # Create borg backup directory with proper permissions - system.activationScripts.immich-borg-dir = '' - mkdir -p ${borgRepo} - chown -R borg:borg ${borgRepo} - chmod 700 ${borgRepo} - ''; + # Note: borgbackup service handles directory creation automatically # Create Immich media directory with proper permissions system.activationScripts.immich-media-dir = '' From 67581adde6497427538f8ca95bbc60fe3f8ae3c6 Mon Sep 17 00:00:00 2001 From: Julian Sutter Date: Mon, 16 Feb 2026 22:25:41 -0800 Subject: [PATCH 10/11] Fix nginx upstream to use IPv4 for immich --- servers/immich.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/servers/immich.nix b/servers/immich.nix index 5639aed..cf99759 100644 --- a/servers/immich.nix +++ b/servers/immich.nix @@ -59,7 +59,7 @@ in ''; locations."/" = { - proxyPass = "http://localhost:${toString immichPort}"; + proxyPass = "http://127.0.0.1:${toString immichPort}"; proxyWebsockets = true; recommendedProxySettings = true; From 449510c746c481c6d42041344ab994a445eedadc Mon Sep 17 00:00:00 2001 From: Julian Sutter Date: Mon, 16 Feb 2026 23:08:48 -0800 Subject: [PATCH 11/11] Reorganize Firefox configuration and repository documentation - Configure Firefox with privacy settings and extensions (Bitwarden, Plasma Integration, MetaMask, Kagi Search, uBlock Origin) - Set Kagi as default/only search engine - Add MOZ_USE_XINPUT2=1 for smooth scrolling - Create context/ directory for concise unit documentation - Create tests/ directory for test scripts - Move test-firefox-config.sh to tests/ - Update agents.md with documentation workflow guidelines - Fix syntax errors in desktop.nix and dev.nix --- agents.md | 182 +++---- context/README.md | 73 +++ .../firefox-extension-update-2026-02-16.md | 33 ++ context/firefox-initial-setup.md | 52 ++ context/firefox.md | 45 ++ desktop/dev.nix | 6 +- systems/desktop.nix | 2 +- tests/README.md | 76 +++ tests/test-firefox-config.sh | 477 ++++++++++++++++++ users/jsutter.nix | 159 +++++- 10 files changed, 994 insertions(+), 111 deletions(-) create mode 100644 context/README.md create mode 100644 context/firefox-extension-update-2026-02-16.md create mode 100644 context/firefox-initial-setup.md create mode 100644 context/firefox.md create mode 100644 tests/README.md create mode 100755 tests/test-firefox-config.sh 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\":{}}"); ''; }; };