diff --git a/.zed/settings.json b/.zed/settings.json deleted file mode 100644 index c311d5e..0000000 --- a/.zed/settings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "project_name": "Bedrock", - "indent_guides": { - "background_coloring": "indent_aware", - "coloring": "fixed" - }, - "tab_size": 2 -} diff --git a/README.md b/README.md index 2f4a973..460439e 100755 --- a/README.md +++ b/README.md @@ -1,11 +1,3 @@ -# NixOS Configuration Repository - -## Session Start Protocol -Always begin by reading agents.md for workflow instructions and development standards. - -## System Installation - -1. Partition the disk: ``` sudo parted /dev/nvme0n1 -- mklabel gpt sudo parted /dev/nvme0n1 -- mkpart primary ext4 512MB 100% @@ -13,10 +5,7 @@ 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 @@ -25,30 +14,14 @@ sudo mkdir /mnt/root sudo git clone https://jsutter:b9cf9383b20dc6efe4d0a732d659709097879b67@git.symbiotrip.com/jsutter/nixos /mnt/root/nixos ``` -3. Install NixOS: +Then: ``` sudo -i cd /mnt/root/nixos nixos-install --flake .# --no-root-password --impure ``` - -4. Set user password: +Finally: ``` 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 e5d343b..d255344 100644 --- a/agents.md +++ b/agents.md @@ -1,11 +1,9 @@ -# NixOS Repository Agent Instructions - -Instructions for agents working in this repository. +# NixOS Repository Quick Reference ## 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 @@ -17,8 +15,6 @@ Instructions for agents working in this repository. - `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 @@ -27,129 +23,6 @@ Instructions for agents working in this repository. 4. Check success message: `"Done. The new configuration is /nix/store/..."` ## Important -- Server configs may contain hardcoded credentials -- Always carefully inspect the NixOS wiki before adding new applications -- Do not editorialize or pass judgement +- 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` - -## 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 - -1. **Gather Requirements** - - Ask user which server to deploy to - - Ask user for domain name - -2. **Research and Planning** - - Review NixOS wiki for packages/modules - - Build brief plan - - Identify dependencies - -3. **Implementation** - - 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** - - `nixos-rebuild build --flake .#` - - Refine until build succeeds - -5. **Remote Deployment** - - `git push origin master` - - SSH to target server - - `cd ~/src/nixos && git pull && sudo nixos-rebuild switch --flake .#` - -6. **Verification** - - Ensure service available on domain - - Check Let's Encrypt certificate: `openssl s_client -connect :443 | openssl x509 -noout -issuer` - - Test functionality - -7. **Documentation** - - Create concise doc in `context/` if major feature - - Add test script to `tests/` if applicable - -### DNS Management - -Create A record via Cloudflare API: -```bash -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') - -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 -```bash -ssh -``` - -### Make Configuration Changes -```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 -for host in host1 host2 host3; do - ssh $host 'cd ~/src/nixos && git pull && sudo nixos-rebuild switch --flake .#' & -done -wait -``` - -### Useful Commands -```bash -# Check service status -ssh 'systemctl status ' - -# View logs -ssh 'journalctl -u -f' - -# Test nginx config -ssh 'nginx -t' - -# Check ACME certs -ssh 'ls -la /var/lib/acme//' - -# Test site availability -curl -I https:// -H "Host: " -``` - -## 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/appflakes/immich/README.md b/appflakes/immich/README.md new file mode 100755 index 0000000..a345236 --- /dev/null +++ b/appflakes/immich/README.md @@ -0,0 +1,356 @@ +# 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 new file mode 100755 index 0000000..668ac53 --- /dev/null +++ b/appflakes/immich/flake.lock @@ -0,0 +1,61 @@ +{ + "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 new file mode 100755 index 0000000..2854894 --- /dev/null +++ b/appflakes/immich/flake.nix @@ -0,0 +1,209 @@ +{ + 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 new file mode 100755 index 0000000..8d999df --- /dev/null +++ b/appflakes/octofriend/README.md @@ -0,0 +1,64 @@ +# 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 new file mode 100755 index 0000000..bcd51f3 --- /dev/null +++ b/appflakes/octofriend/flake.lock @@ -0,0 +1,78 @@ +{ + "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 new file mode 100755 index 0000000..14f3347 --- /dev/null +++ b/appflakes/octofriend/flake.nix @@ -0,0 +1,92 @@ +{ + 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 diff --git a/context/README.md b/context/README.md deleted file mode 100644 index 4d13fc2..0000000 --- a/context/README.md +++ /dev/null @@ -1,73 +0,0 @@ -# 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 deleted file mode 100644 index c30fa1f..0000000 --- a/context/firefox-extension-update-2026-02-16.md +++ /dev/null @@ -1,33 +0,0 @@ -# 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 deleted file mode 100644 index 13dac66..0000000 --- a/context/firefox-initial-setup.md +++ /dev/null @@ -1,52 +0,0 @@ -# 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 deleted file mode 100644 index dde2891..0000000 --- a/context/firefox.md +++ /dev/null @@ -1,45 +0,0 @@ -# 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 48a9636..76d5bad 100755 --- a/desktop/dev.nix +++ b/desktop/dev.nix @@ -1,4 +1,4 @@ -{ config, pkgs, pkgs-unstable, lib, ... }: +{ config, pkgs, pkgs-unstable, lib, octofriend, ... }: { @@ -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/flake.lock b/flake.lock index 8dd7d52..9f1b917 100755 --- a/flake.lock +++ b/flake.lock @@ -39,11 +39,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1771208521, - "narHash": "sha256-X01Q3DgSpjeBpapoGA4rzKOn25qdKxbPnxHeMLNoHTU=", + "lastModified": 1770136044, + "narHash": "sha256-tlFqNG/uzz2++aAmn4v8J0vAkV3z7XngeIIB3rM3650=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "fa56d7d6de78f5a7f997b0ea2bc6efd5868ad9e8", + "rev": "e576e3c9cf9bad747afcddd9e34f51d18c855b4e", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 93de189..8f5fbd6 100755 --- a/flake.nix +++ b/flake.nix @@ -87,7 +87,6 @@ ./systems/warp.nix ./servers/nginx.nix ./servers/forgejo.nix - ./servers/immich.nix ]; }; skip = mkSystem { diff --git a/servers/immich.nix b/servers/immich.nix deleted file mode 100644 index cf99759..0000000 --- a/servers/immich.nix +++ /dev/null @@ -1,158 +0,0 @@ -{ 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://127.0.0.1:${toString immichPort}"; - proxyWebsockets = true; - recommendedProxySettings = true; - - # Extra websocket configuration - extraConfig = '' - 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 - }; - }; - }; - - # Note: borgbackup service handles directory creation automatically - - # 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; - }; -} diff --git a/systems/desktop.nix b/systems/desktop.nix index 547bcd6..d47fc54 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 deleted file mode 100644 index 6528fbc..0000000 --- a/tests/README.md +++ /dev/null @@ -1,76 +0,0 @@ -# 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 deleted file mode 100755 index 9925d7f..0000000 --- a/tests/test-firefox-config.sh +++ /dev/null @@ -1,477 +0,0 @@ -#!/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 a5c3712..44af55d 100755 --- a/users/jsutter.nix +++ b/users/jsutter.nix @@ -33,7 +33,6 @@ home.sessionVariables = { OPENAI_API_KEY = "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"; SYNTHETIC_L_API_KEY = "syn_5bfe68ad3826bb7872f32fcf160e959a"; - MOZ_USE_XINPUT2 = "1"; }; programs.git = { @@ -172,77 +171,16 @@ OfferToSaveLogins = false; OfferToSaveLoginsDefault = false; PasswordManagerEnabled = false; - DisableFormHistory = true; FirefoxHome = { - Search = false; + Search = true; 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"; - }; }; }; }; @@ -251,106 +189,13 @@ id = 0; name = "jsutter"; settings = { - "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"; + "general.smoothScroll" = true; }; 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\":{}}"); ''; }; };