diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..d8cac13 --- /dev/null +++ b/.envrc @@ -0,0 +1,3 @@ +# Automatically load the Nix development shell when entering this directory +# Requires direnv: https://direnv.net/ +use flake diff --git a/.gitignore b/.gitignore index 541bba4..e630a8a 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,7 @@ management-dashboard-web-app/users.txt # Jupyter Notebooks *.ipynb +# Nix +result +result-* +.direnv/ \ No newline at end of file diff --git a/FLAKE_SETUP.md b/FLAKE_SETUP.md new file mode 100644 index 0000000..39bc6c8 --- /dev/null +++ b/FLAKE_SETUP.md @@ -0,0 +1,264 @@ +# USDA Vision - Nix Flake Setup + +This directory now has a Nix flake for building and developing the USDA Vision system, with ragenix for managing secrets. + +## Quick Start + +### Development Environment + +Enter the development shell with all tools: + +```bash +cd usda-vision +nix develop +``` + +This gives you: +- Docker & Docker Compose +- Node.js 20 with npm/pnpm +- Python 3.11 with pip/virtualenv +- Supabase CLI +- Camera SDK (automatically in `LD_LIBRARY_PATH`) +- ragenix for secrets management +- All standard utilities (jq, yq, rsync, etc.) + +### Building + +Build the package: + +```bash +nix build +# Or explicitly: +nix build .#usda-vision +``` + +Build just the camera SDK: + +```bash +nix build .#camera-sdk +``` + +## Secrets Management with ragenix + +### Initial Setup + +1. **Generate or use an age key**: + +```bash +# Option 1: Generate a new age key +mkdir -p ~/.config/age +age-keygen -o ~/.config/age/keys.txt + +# Option 2: Use your SSH key +ssh-to-age < ~/.ssh/id_ed25519.pub +# Copy the output to secrets/secrets.nix +``` + +2. **Add your public key** to [secrets/secrets.nix](secrets/secrets.nix): + +```nix +{ + publicKeys = [ + "age1your_public_key_here" + # or + "ssh-ed25519 AAAA... user@host" + ]; +} +``` + +3. **Create encrypted environment files**: + +```bash +nix develop # Enter dev shell first +ragenix -e secrets/env.age +``` + +This opens your `$EDITOR` to edit the encrypted file. Add your environment variables: + +```bash +# Web environment (Vite) +VITE_SUPABASE_URL=http://exp-dash:54321 +VITE_SUPABASE_ANON_KEY=your-anon-key-here +# ... etc +``` + +For Azure OAuth: + +```bash +ragenix -e secrets/env.azure.age +``` + +### Using Secrets in Development + +In the development shell, you can: + +```bash +# Edit secrets +ragenix -e secrets/env.age + +# View decrypted content (careful in shared screens!) +age -d -i ~/.config/age/keys.txt secrets/env.age + +# Re-encrypt all secrets after adding a new public key +ragenix -r +``` + +### Using Secrets in Production (NixOS) + +The flake includes a NixOS module that handles secrets automatically: + +```nix +# In your NixOS configuration +{ + inputs.usda-vision.url = "path:/path/to/usda-vision"; + + # ... in your module: + imports = [ inputs.usda-vision.nixosModules.default ]; + + services.usda-vision = { + enable = true; + secretsFile = config.age.secrets.usda-vision-env.path; + }; + + # Configure ragenix/agenix to decrypt the secrets + age.secrets.usda-vision-env = { + file = inputs.usda-vision + "/secrets/env.age"; + mode = "0644"; + owner = "root"; + }; +} +``` + +## Project Structure + +``` +usda-vision/ +├── flake.nix # Flake definition with outputs +├── package.nix # Main application build +├── camera-sdk.nix # Camera SDK build +├── secrets.nix # ragenix configuration +├── secrets/ +│ ├── secrets.nix # Public keys +│ ├── env.age # Encrypted .env (safe to commit) +│ ├── env.azure.age # Encrypted Azure config (safe to commit) +│ └── README.md # Secrets documentation +└── ... (rest of the app) +``` + +## Migration from Old Setup + +### Old Workflow +- Manual `.env` file management +- Secrets in plaintext (git-ignored) +- Build defined in parent `default.nix` + +### New Workflow +- Encrypted `.age` files in git +- Secrets managed with ragenix +- Self-contained flake in `usda-vision/` +- Development shell with all tools + +### Migration Steps + +1. **Encrypt existing `.env` files**: + +```bash +cd usda-vision +nix develop + +# Setup your age key first (see above) + +# Encrypt the main .env +ragenix -e secrets/env.age +# Paste contents of old .env file, save and exit + +# Encrypt Azure config +ragenix -e secrets/env.azure.age +# Paste contents of old .env.azure file, save and exit +``` + +2. **Delete unencrypted files** (they're git-ignored but still local): + +```bash +rm .env .env.azure management-dashboard-web-app/.env +``` + +3. **Commit encrypted secrets**: + +```bash +git add secrets/env.age secrets/env.azure.age secrets/secrets.nix +git commit -m "Add encrypted secrets with ragenix" +``` + +## Benefits + +### Security +- ✅ Secrets encrypted at rest +- ✅ Safe to commit to git +- ✅ Key-based access control +- ✅ Audit trail (git history) + +### Development +- ✅ Reproducible environment +- ✅ All tools included +- ✅ No manual setup +- ✅ Version-locked dependencies + +### Deployment +- ✅ Declarative secrets management +- ✅ Automatic decryption on NixOS +- ✅ No manual key distribution +- ✅ Clean integration with existing infrastructure + +## Common Tasks + +### Add a new developer + +1. They generate an age key or use their SSH key +2. They send you their public key +3. You add it to `secrets/secrets.nix` +4. Re-encrypt all secrets: `ragenix -r` +5. Commit and push + +### Rotate a secret + +1. Edit the encrypted file: `ragenix -e secrets/env.age` +2. Update the value +3. Save and exit +4. Commit: `git commit secrets/env.age -m "Rotate API key"` + +### Build without flakes + +If you need to build on a system without flakes enabled: + +```bash +nix-build -E '(import (fetchTarball https://github.com/edolstra/flake-compat/archive/master.tar.gz) { src = ./.; }).defaultNix.default' +``` + +## Troubleshooting + +### "error: getting status of '...': No such file or directory" + +Make sure you're in the `usda-vision` directory when running `nix develop` or `nix build`. + +### "cannot decrypt: no valid identity" + +Your age private key isn't found. Check: +- `~/.config/age/keys.txt` exists +- Your public key is in `secrets/secrets.nix` +- You've run `ragenix -r` after adding your key + +### "experimental feature 'flakes' not enabled" + +Add to `~/.config/nix/nix.conf`: +``` +experimental-features = nix-command flakes +``` + +Or run with: `nix --experimental-features 'nix-command flakes' develop` + +## Further Reading + +- [Nix Flakes](https://nixos.wiki/wiki/Flakes) +- [ragenix](https://github.com/yaxitech/ragenix) +- [age encryption](https://github.com/FiloSottile/age) diff --git a/SETUP_COMPLETE.md b/SETUP_COMPLETE.md new file mode 100644 index 0000000..136c900 --- /dev/null +++ b/SETUP_COMPLETE.md @@ -0,0 +1,241 @@ +# USDA Vision - Flake Migration Complete ✅ + +## Summary + +Your USDA Vision repository now has: + +1. **Self-contained Nix flake** (`flake.nix`) + - Independent build system + - Development environment + - NixOS module for deployment + +2. **Encrypted secrets management** (ragenix) + - `.age` files safe to commit to git + - Key-based access control + - No more plaintext `.env` files + +3. **Modular build** (package.nix, camera-sdk.nix) + - Cleaner organization + - Easier to maintain + - Reusable components + +4. **Updated parent** (../default.nix) + - Now references the flake + - Removed 200+ lines of inline derivations + +## Files Added + +### Core Flake Files +- ✅ `flake.nix` - Main flake definition with outputs +- ✅ `package.nix` - Application build logic +- ✅ `camera-sdk.nix` - Camera SDK build logic +- ✅ `secrets.nix` - ragenix configuration + +### Secrets Infrastructure +- ✅ `secrets/secrets.nix` - Public key list +- ✅ `secrets/README.md` - Secrets documentation +- ✅ `secrets/.gitignore` - Protect plaintext files + +### Documentation & Helpers +- ✅ `FLAKE_SETUP.md` - Complete setup guide +- ✅ `setup-dev.sh` - Interactive setup script +- ✅ `.envrc` - direnv integration (optional) + +### Parent Directory +- ✅ `NIX_FLAKE_MIGRATION.md` - Migration summary + +## Next Steps + +### 1. Commit the Flake Files + +The flake needs to be in git to work: + +```bash +cd /home/engr-ugaif/usda-dash-config/usda-vision + +# Add all new flake files +git add flake.nix package.nix camera-sdk.nix secrets.nix +git add secrets/secrets.nix secrets/README.md secrets/.gitignore +git add FLAKE_SETUP.md setup-dev.sh .envrc .gitignore + +# Commit +git commit -m "Add Nix flake with ragenix secrets management + +- Self-contained flake build system +- Development shell with all tools +- ragenix for encrypted secrets +- Modular package definitions +" +``` + +### 2. Set Up Your Age Key + +```bash +cd /home/engr-ugaif/usda-dash-config/usda-vision + +# Option A: Use the interactive setup script +./setup-dev.sh + +# Option B: Manual setup +mkdir -p ~/.config/age +age-keygen -o ~/.config/age/keys.txt +# Then add your public key to secrets/secrets.nix +``` + +### 3. Encrypt Your Secrets + +```bash +# Enter the development environment +nix develop + +# Encrypt main .env file +ragenix -e secrets/env.age +# Paste your current .env contents, save, exit + +# Encrypt Azure config +ragenix -e secrets/env.azure.age +# Paste your current .env.azure contents, save, exit + +# Commit encrypted secrets +git add secrets/env.age secrets/env.azure.age +git commit -m "Add encrypted environment configuration" +``` + +### 4. Test the Setup + +```bash +# Test that the build works +nix build + +# Test the development shell +nix develop +# You should see a welcome message + +# Inside the dev shell, verify tools +docker-compose --version +supabase --version +ragenix --help +``` + +### 5. Update the Parent Repository + +```bash +cd /home/engr-ugaif/usda-dash-config + +# Commit the updated default.nix +git add default.nix NIX_FLAKE_MIGRATION.md +git commit -m "Update default.nix to use usda-vision flake + +- Removed inline derivations +- Now references usda-vision flake packages +- Cleaner, more maintainable code +" +``` + +### 6. Clean Up Old Files (Optional) + +After verifying everything works, you can delete the old plaintext secrets: + +```bash +cd /home/engr-ugaif/usda-dash-config/usda-vision + +# These are already git-ignored, but remove them locally +rm -f .env .env.azure management-dashboard-web-app/.env + +echo "✅ Old plaintext secrets removed" +``` + +## Verification Checklist + +- [ ] Flake files committed to git +- [ ] Age key generated at `~/.config/age/keys.txt` +- [ ] Public key added to `secrets/secrets.nix` +- [ ] Secrets encrypted and committed +- [ ] `nix build` succeeds +- [ ] `nix develop` works +- [ ] Parent `default.nix` updated and committed +- [ ] Old `.env` files deleted + +## Usage Quick Reference + +### Development + +```bash +# Enter dev environment (one-time per session) +cd usda-vision +nix develop + +# Edit secrets +ragenix -e secrets/env.age + +# Normal docker-compose workflow +docker-compose up -d +docker-compose logs -f +``` + +### Building + +```bash +# Build everything +nix build + +# Build specific packages +nix build .#usda-vision +nix build .#camera-sdk +``` + +### Secrets Management + +```bash +# Edit encrypted secret +ragenix -e secrets/env.age + +# Re-key after adding a new public key +ragenix -r + +# View decrypted (careful!) +age -d -i ~/.config/age/keys.txt secrets/env.age +``` + +## Troubleshooting + +### "cannot decrypt: no valid identity" + +Your age key isn't configured. Run: +```bash +./setup-dev.sh +``` + +### "error: flake.nix is not in git" + +Commit the flake files: +```bash +git add flake.nix package.nix camera-sdk.nix secrets.nix +git commit -m "Add flake files" +``` + +### "experimental feature 'flakes' not enabled" + +Add to `~/.config/nix/nix.conf`: +``` +experimental-features = nix-command flakes +``` + +## Documentation + +- **Full Setup Guide**: [FLAKE_SETUP.md](FLAKE_SETUP.md) +- **Secrets Guide**: [secrets/README.md](secrets/README.md) +- **Migration Summary**: [../NIX_FLAKE_MIGRATION.md](../NIX_FLAKE_MIGRATION.md) + +## Questions? + +Refer to [FLAKE_SETUP.md](FLAKE_SETUP.md) for detailed documentation, or run: + +```bash +./setup-dev.sh # Interactive setup +``` + +--- + +**Migration completed on**: 2026-01-30 +**Created by**: GitHub Copilot diff --git a/camera-sdk.nix b/camera-sdk.nix new file mode 100644 index 0000000..0d84c40 --- /dev/null +++ b/camera-sdk.nix @@ -0,0 +1,44 @@ +{ stdenv +, lib +, makeWrapper +, libusb1 +}: + +stdenv.mkDerivation { + pname = "mindvision-camera-sdk"; + version = "2.1.0.49"; + + # Use the camera_sdk directory as source + src = ./camera-management-api/camera_sdk; + + nativeBuildInputs = [ makeWrapper ]; + buildInputs = [ libusb1 ]; + + unpackPhase = '' + cp -r $src/* . + tar xzf "linuxSDK_V2.1.0.49(250108).tar.gz" + cd "linuxSDK_V2.1.0.49(250108)" + ''; + + installPhase = '' + mkdir -p $out/lib $out/include + + # Copy x64 library files (SDK has arch-specific subdirs) + if [ -d lib/x64 ]; then + cp -r lib/x64/* $out/lib/ || true + fi + + # Copy header files + if [ -d include ]; then + cp -r include/* $out/include/ || true + fi + + # Make libraries executable + chmod +x $out/lib/*.so* 2>/dev/null || true + ''; + + meta = with lib; { + description = "MindVision Camera SDK"; + platforms = platforms.linux; + }; +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..7ed4d7e --- /dev/null +++ b/flake.lock @@ -0,0 +1,239 @@ +{ + "nodes": { + "agenix": { + "inputs": { + "darwin": "darwin", + "home-manager": "home-manager", + "nixpkgs": [ + "ragenix", + "nixpkgs" + ], + "systems": "systems_2" + }, + "locked": { + "lastModified": 1761656077, + "narHash": "sha256-lsNWuj4Z+pE7s0bd2OKicOFq9bK86JE0ZGeKJbNqb94=", + "owner": "ryantm", + "repo": "agenix", + "rev": "9ba0d85de3eaa7afeab493fed622008b6e4924f5", + "type": "github" + }, + "original": { + "owner": "ryantm", + "repo": "agenix", + "type": "github" + } + }, + "crane": { + "locked": { + "lastModified": 1760924934, + "narHash": "sha256-tuuqY5aU7cUkR71sO2TraVKK2boYrdW3gCSXUkF4i44=", + "owner": "ipetkov", + "repo": "crane", + "rev": "c6b4d5308293d0d04fcfeee92705017537cad02f", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, + "darwin": { + "inputs": { + "nixpkgs": [ + "ragenix", + "agenix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1744478979, + "narHash": "sha256-dyN+teG9G82G+m+PX/aSAagkC+vUv0SgUw3XkPhQodQ=", + "owner": "lnl7", + "repo": "nix-darwin", + "rev": "43975d782b418ebf4969e9ccba82466728c2851b", + "type": "github" + }, + "original": { + "owner": "lnl7", + "ref": "master", + "repo": "nix-darwin", + "type": "github" + } + }, + "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" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_3" + }, + "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" + } + }, + "home-manager": { + "inputs": { + "nixpkgs": [ + "ragenix", + "agenix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1745494811, + "narHash": "sha256-YZCh2o9Ua1n9uCvrvi5pRxtuVNml8X2a03qIFfRKpFs=", + "owner": "nix-community", + "repo": "home-manager", + "rev": "abfad3d2958c9e6300a883bd443512c55dfeb1be", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "home-manager", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1769461804, + "narHash": "sha256-msG8SU5WsBUfVVa/9RPLaymvi5bI8edTavbIq3vRlhI=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "bfc1b8a4574108ceef22f02bafcf6611380c100d", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "ragenix": { + "inputs": { + "agenix": "agenix", + "crane": "crane", + "flake-utils": "flake-utils_2", + "nixpkgs": [ + "nixpkgs" + ], + "rust-overlay": "rust-overlay" + }, + "locked": { + "lastModified": 1761832913, + "narHash": "sha256-VCNVjjuRvrKPiYYwqhE3BAKIaReiKXGpxGp27lZ0MFM=", + "owner": "yaxitech", + "repo": "ragenix", + "rev": "83bccfdea758241999f32869fb6b36f7ac72f1ac", + "type": "github" + }, + "original": { + "owner": "yaxitech", + "repo": "ragenix", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "ragenix": "ragenix" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "ragenix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1761791894, + "narHash": "sha256-myRIDh+PxaREz+z9LzbqBJF+SnTFJwkthKDX9zMyddY=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "59c45eb69d9222a4362673141e00ff77842cd219", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_3": { + "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/flake.nix b/flake.nix new file mode 100644 index 0000000..d6dd1fc --- /dev/null +++ b/flake.nix @@ -0,0 +1,176 @@ +{ + description = "USDA Vision camera management system"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + + # For secrets management + ragenix = { + url = "github:yaxitech/ragenix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { self, nixpkgs, flake-utils, ragenix }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + inherit system; + config.allowUnfree = true; + }; + + # Import our package definition + usda-vision-package = pkgs.callPackage ./package.nix { }; + camera-sdk = pkgs.callPackage ./camera-sdk.nix { }; + + in + { + packages = { + default = usda-vision-package; + usda-vision = usda-vision-package; + camera-sdk = camera-sdk; + }; + + devShells.default = pkgs.mkShell { + name = "usda-vision-dev"; + + # Input packages for the development shell + buildInputs = with pkgs; [ + # Core development tools + git + vim + curl + wget + + # Docker for local development + docker + docker-compose + + # Supabase CLI + supabase-cli + + # Node.js for web app development + nodejs_20 + nodePackages.npm + nodePackages.pnpm + + # Python for camera API + python311 + python311Packages.pip + python311Packages.virtualenv + + # Camera SDK + camera-sdk + + # Secrets management + ragenix.packages.${system}.default + age + ssh-to-age + + # Utilities + jq + yq + rsync + gnused + gawk + ]; + + # Environment variables for development + shellHook = '' + export LD_LIBRARY_PATH="${camera-sdk}/lib:$LD_LIBRARY_PATH" + export CAMERA_SDK_PATH="${camera-sdk}" + + # Set up Python virtual environment + if [ ! -d .venv ]; then + echo "Creating Python virtual environment..." + python -m venv .venv + fi + + echo "USDA Vision Development Environment" + echo "====================================" + echo "Camera SDK: ${camera-sdk}" + echo "" + echo "Available commands:" + echo " - docker-compose: Manage containers" + echo " - supabase: Supabase CLI" + echo " - ragenix: Manage encrypted secrets" + echo " - age: Encrypt/decrypt files" + echo "" + echo "To activate Python venv: source .venv/bin/activate" + echo "To edit secrets: ragenix -e secrets/env.age" + echo "" + ''; + + # Additional environment configuration + DOCKER_BUILDKIT = "1"; + COMPOSE_DOCKER_CLI_BUILD = "1"; + }; + + # NixOS module for easy integration + nixosModules.default = { config, lib, ... }: { + options.services.usda-vision = { + enable = lib.mkEnableOption "USDA Vision camera management system"; + + secretsFile = lib.mkOption { + type = lib.types.path; + description = "Path to the ragenix-managed secrets file"; + }; + + dataDir = lib.mkOption { + type = lib.types.str; + default = "/var/lib/usda-vision"; + description = "Directory for USDA Vision application data"; + }; + }; + + config = lib.mkIf config.services.usda-vision.enable { + environment.systemPackages = [ + usda-vision-package + camera-sdk + pkgs.docker-compose + ]; + + environment.variables.LD_LIBRARY_PATH = "${camera-sdk}/lib"; + + virtualisation.docker = { + enable = true; + autoPrune.enable = true; + }; + + systemd.services.usda-vision = { + description = "USDA Vision Docker Compose Stack"; + after = [ "docker.service" "network-online.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + + preStart = '' + # Sync application code + ${pkgs.rsync}/bin/rsync -av --delete \ + --checksum \ + --exclude='node_modules' \ + --exclude='.env' \ + --exclude='__pycache__' \ + --exclude='.venv' \ + ${usda-vision-package}/opt/usda-vision/ ${config.services.usda-vision.dataDir}/ + + # Copy secrets if managed by ragenix + if [ -f "${config.services.usda-vision.secretsFile}" ]; then + cp "${config.services.usda-vision.secretsFile}" ${config.services.usda-vision.dataDir}/.env + fi + ''; + + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + WorkingDirectory = config.services.usda-vision.dataDir; + ExecStart = "${pkgs.docker-compose}/bin/docker-compose up -d --build"; + ExecStop = "${pkgs.docker-compose}/bin/docker-compose down"; + TimeoutStartSec = 300; + }; + }; + }; + }; + } + ); +} diff --git a/package.nix b/package.nix new file mode 100644 index 0000000..b0e9730 --- /dev/null +++ b/package.nix @@ -0,0 +1,131 @@ +{ lib +, stdenv +, makeWrapper +, rsync +, gnused +, gawk +, docker-compose +}: + +stdenv.mkDerivation { + pname = "usda-vision"; + version = "1.0.0"; + + # Use the directory from this repository with explicit source filtering + src = lib.cleanSourceWith { + src = ./.; + filter = path: type: + let + baseName = baseNameOf path; + in + # Exclude git, but include everything else + baseName != ".git" && + baseName != ".cursor" && + baseName != "__pycache__" && + baseName != "node_modules" && + baseName != ".venv" && + baseName != ".age" && + baseName != "flake.nix" && + baseName != "flake.lock" && + baseName != "package.nix" && + baseName != "camera-sdk.nix"; + }; + + nativeBuildInputs = [ makeWrapper rsync ]; + + # Don't run these phases, we'll do everything in installPhase + dontBuild = true; + dontConfigure = true; + + installPhase = '' + mkdir -p $out/opt/usda-vision + + # Debug: show what's in source + echo "Source directory contents:" + ls -la $src/ || true + + # Process docker-compose.yml - replace paths and configure SDK from Nix + if [ -f $src/docker-compose.yml ]; then + # Basic path replacements with sed + ${gnused}/bin/sed \ + -e 's|env_file:.*management-dashboard-web-app/\.env|env_file: /var/lib/usda-vision/.env|g' \ + -e 's|\./management-dashboard-web-app/\.env|/var/lib/usda-vision/.env|g' \ + -e 's|\./management-dashboard-web-app|/var/lib/usda-vision/management-dashboard-web-app|g' \ + -e 's|\./media-api|/var/lib/usda-vision/media-api|g' \ + -e 's|\./video-remote|/var/lib/usda-vision/video-remote|g' \ + -e 's|\./scheduling-remote|/var/lib/usda-vision/scheduling-remote|g' \ + -e 's|\./vision-system-remote|/var/lib/usda-vision/vision-system-remote|g' \ + -e 's|\./camera-management-api|/var/lib/usda-vision/camera-management-api|g' \ + $src/docker-compose.yml > $TMPDIR/docker-compose-step1.yml + + # Remove SDK installation blocks using awk for better multi-line handling + ${gawk}/bin/awk ' + /# Only install system packages if not already installed/ { skip=1 } + skip && /^ fi$/ { skip=0; next } + /# Install camera SDK if not already installed/ { skip_sdk=1 } + skip_sdk && /^ fi;$/ { skip_sdk=0; next } + !skip && !skip_sdk { print } + ' $TMPDIR/docker-compose-step1.yml > $TMPDIR/docker-compose.yml + + rm -f $TMPDIR/docker-compose-step1.yml + fi + + # Copy all application files using rsync with chmod + ${rsync}/bin/rsync -av \ + --chmod=Du+w \ + --exclude='.git' \ + --exclude='docker-compose.yml' \ + --exclude='.env' \ + --exclude='*.age' \ + --exclude='flake.nix' \ + --exclude='flake.lock' \ + --exclude='package.nix' \ + --exclude='camera-sdk.nix' \ + --exclude='management-dashboard-web-app/.env' \ + $src/ $out/opt/usda-vision/ + + # Copy the processed docker-compose.yml + if [ -f $TMPDIR/docker-compose.yml ]; then + cp $TMPDIR/docker-compose.yml $out/opt/usda-vision/docker-compose.yml + fi + + # Verify files were copied + echo "Destination directory contents:" + ls -la $out/opt/usda-vision/ || true + + # Create convenience scripts + mkdir -p $out/bin + + cat > $out/bin/usda-vision-start <<'EOF' +#!/usr/bin/env bash +cd $out/opt/usda-vision +${docker-compose}/bin/docker-compose up -d --build +EOF + + cat > $out/bin/usda-vision-stop <<'EOF' +#!/usr/bin/env bash +cd $out/opt/usda-vision +${docker-compose}/bin/docker-compose down +EOF + + cat > $out/bin/usda-vision-logs <<'EOF' +#!/usr/bin/env bash +cd $out/opt/usda-vision +${docker-compose}/bin/docker-compose logs -f "$@" +EOF + + cat > $out/bin/usda-vision-restart <<'EOF' +#!/usr/bin/env bash +cd $out/opt/usda-vision +${docker-compose}/bin/docker-compose restart "$@" +EOF + + chmod +x $out/bin/usda-vision-* + ''; + + meta = with lib; { + description = "USDA Vision camera management system"; + maintainers = [ "UGA Innovation Factory" ]; + platforms = platforms.linux; + }; +} diff --git a/secrets.nix b/secrets.nix new file mode 100644 index 0000000..efd9940 --- /dev/null +++ b/secrets.nix @@ -0,0 +1,14 @@ +# Ragenix Configuration +# This file defines which secrets to manage and their permissions + +let + # Import public keys from secrets.nix + keys = import ./secrets.nix; +in +{ + # Main environment file + "env.age".publicKeys = keys.publicKeys; + + # Azure OAuth configuration + "env.azure.age".publicKeys = keys.publicKeys; +} diff --git a/secrets/.gitignore b/secrets/.gitignore new file mode 100644 index 0000000..fae6303 --- /dev/null +++ b/secrets/.gitignore @@ -0,0 +1,11 @@ +# Ignore unencrypted secrets +*.env +!*.env.example +.env.* +!.env.*.example + +# Ignore age private keys (if accidentally placed here) +*.txt + +# Keep encrypted files +!*.age diff --git a/secrets/README.md b/secrets/README.md new file mode 100644 index 0000000..4608822 --- /dev/null +++ b/secrets/README.md @@ -0,0 +1,75 @@ +# USDA Vision Secrets Management + +This directory contains encrypted secrets managed by [ragenix](https://github.com/yaxitech/ragenix). + +## Setup + +1. **Generate an age key** (if you don't have one): + ```bash + # Generate a new age key + age-keygen -o ~/.config/age/keys.txt + + # Or convert your SSH key + ssh-to-age < ~/.ssh/id_ed25519.pub + ``` + +2. **Add your public key to `secrets.nix`**: + ```nix + { + publicKeys = [ + "age1..." # Your age public key + "ssh-ed25519 ..." # Or your SSH public key + ]; + } + ``` + +3. **Create and encrypt environment files**: + ```bash + # Create the encrypted .env file + ragenix -e secrets/env.age + + # Create the encrypted .env.azure file + ragenix -e secrets/env.azure.age + ``` + +## Usage in Development + +In the development shell: +```bash +# Edit encrypted secrets +ragenix -e secrets/env.age + +# Re-key secrets after adding a new public key +ragenix -r +``` + +## Usage in NixOS + +The flake's NixOS module automatically handles decryption: + +```nix +{ + services.usda-vision = { + enable = true; + secretsFile = config.age.secrets.usda-vision-env.path; + }; + + age.secrets.usda-vision-env = { + file = ./usda-vision/secrets/env.age; + mode = "0644"; + }; +} +``` + +## Files + +- `secrets.nix` - Public keys configuration +- `env.age` - Encrypted main .env file +- `env.azure.age` - Encrypted Azure OAuth configuration +- `README.md` - This file + +## Security Notes + +- Never commit unencrypted `.env` files +- Keep your age private key secure (`~/.config/age/keys.txt`) +- The `.age` encrypted files are safe to commit to git diff --git a/secrets/secrets.nix b/secrets/secrets.nix new file mode 100644 index 0000000..a0421d2 --- /dev/null +++ b/secrets/secrets.nix @@ -0,0 +1,14 @@ +# Public keys for secret encryption +# Add your age or SSH public keys here +{ + publicKeys = [ + # Example age public key: + # "age1qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs3ekg8p" + + # Example SSH public key (ed25519): + # "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... user@host" + + # Add your keys below: + # TODO: Add your age or SSH public keys + ]; +} diff --git a/setup-dev.sh b/setup-dev.sh new file mode 100755 index 0000000..13f0350 --- /dev/null +++ b/setup-dev.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# Quick setup script for USDA Vision development + +set -e + +echo "======================================" +echo "USDA Vision - Quick Setup" +echo "======================================" +echo "" + +# Check if we're in the right directory +if [ ! -f "flake.nix" ]; then + echo "❌ Error: Must run from usda-vision directory" + echo " cd to the directory containing flake.nix" + exit 1 +fi + +# Check for age key +if [ ! -f "$HOME/.config/age/keys.txt" ]; then + echo "📝 No age key found at ~/.config/age/keys.txt" + echo "" + read -p "Would you like to generate one? (y/n) " -n 1 -r + echo "" + if [[ $REPLY =~ ^[Yy]$ ]]; then + mkdir -p "$HOME/.config/age" + age-keygen -o "$HOME/.config/age/keys.txt" + echo "✅ Age key generated!" + echo "" + else + echo "❌ Cannot proceed without an age key" + exit 1 + fi +fi + +# Get public key +AGE_PUBLIC_KEY=$(grep "public key:" "$HOME/.config/age/keys.txt" | cut -d: -f2 | xargs) + +echo "Your age public key is:" +echo " $AGE_PUBLIC_KEY" +echo "" + +# Check if key is already in secrets.nix +if grep -q "$AGE_PUBLIC_KEY" secrets/secrets.nix 2>/dev/null; then + echo "✅ Your key is already in secrets/secrets.nix" +else + echo "⚠️ Your key is NOT in secrets/secrets.nix" + echo "" + read -p "Would you like to add it now? (y/n) " -n 1 -r + echo "" + if [[ $REPLY =~ ^[Yy]$ ]]; then + # Backup original + cp secrets/secrets.nix secrets/secrets.nix.backup + + # Add the key + sed -i "/publicKeys = \[/a\ \"$AGE_PUBLIC_KEY\"" secrets/secrets.nix + + echo "✅ Key added to secrets/secrets.nix" + echo "" + fi +fi + +echo "======================================" +echo "Setup complete! Next steps:" +echo "======================================" +echo "" +echo "1. Enter development environment:" +echo " $ nix develop" +echo "" +echo "2. Create/edit encrypted secrets:" +echo " $ ragenix -e secrets/env.age" +echo " $ ragenix -e secrets/env.azure.age" +echo "" +echo "3. Start development:" +echo " $ docker-compose up -d" +echo "" +echo "For more information, see FLAKE_SETUP.md" +echo ""