From 824ad97ef9557009bbc96fa6042d8c647fdedb75 Mon Sep 17 00:00:00 2001 From: m3tm3re Date: Tue, 30 Dec 2025 15:02:58 +0100 Subject: [PATCH] docs: add hierarchical AGENTS.md knowledge base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update root AGENTS.md with regenerative content (144 lines, telegraphic) - Add pkgs/AGENTS.md for package registry conventions (33 lines) - Add docs/AGENTS.md for documentation structure (34 lines) - All files follow telegraphic style, no redundancy with parent - Preserves existing modules/home-manager/AGENTS.md Hierarchy: ./AGENTS.md (root) ├── pkgs/AGENTS.md ├── docs/AGENTS.md └── modules/home-manager/AGENTS.md (existing) --- .gitignore | 3 - AGENTS.md | 141 ++++------ CONTRIBUTING.md | 450 ------------------------------- QUICKSTART_PORT_MANAGEMENT.md | 230 ---------------- README.md | 491 ++-------------------------------- flake.nix | 1 + modules/nixos/default.nix | 1 + modules/nixos/mem0.nix | 363 +++++++++++++++++++++++++ pkgs/AGENTS.md | 33 +++ pkgs/default.nix | 1 + pkgs/mem0/default.nix | 99 +++++++ pkgs/mem0/server.py | 301 +++++++++++++++++++++ 12 files changed, 863 insertions(+), 1251 deletions(-) delete mode 100644 CONTRIBUTING.md delete mode 100644 QUICKSTART_PORT_MANAGEMENT.md create mode 100644 modules/nixos/mem0.nix create mode 100644 pkgs/AGENTS.md create mode 100644 pkgs/mem0/default.nix create mode 100644 pkgs/mem0/server.py diff --git a/.gitignore b/.gitignore index 2d12203..9afc6f5 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,3 @@ test-result/ # Local configuration (if you want to keep local overrides) local.nix flake.lock.bak - -# Documentation -docs/ diff --git a/AGENTS.md b/AGENTS.md index 6002b46..2067741 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,14 +1,14 @@ # m3ta-nixpkgs Knowledge Base -**Generated:** 2025-12-29 -**Commit:** 9092e6d +**Generated:** 2025-12-30 +**Commit:** c5e1610 **Branch:** master -## Overview +## OVERVIEW Personal Nix flake: custom packages, overlays, NixOS/Home Manager modules, dev shells. Flakes-only (no channels). -## Structure +## STRUCTURE ``` . @@ -24,7 +24,7 @@ Personal Nix flake: custom packages, overlays, NixOS/Home Manager modules, dev s └── examples/ # Usage examples ``` -## Where to Look +## WHERE TO LOOK | Task | Location | Notes | |------|----------|-------| @@ -35,23 +35,7 @@ Personal Nix flake: custom packages, overlays, NixOS/Home Manager modules, dev s | Add dev shell | `shells/.nix` | Register in `shells/default.nix` | | Use port management | `config.m3ta.ports.get "service"` | Host-specific via `hostOverrides` | -## Commands - -```bash -nix flake check # Validate flake -nix fmt # Format (nixpkgs-fmt) -nix build .# # Build package -nix flake show # List outputs -nix develop # Enter dev shell -nix develop .#python # Python shell -nix develop .#devops # DevOps shell - -# In dev shell only: -statix check . # Lint -deadnix . # Find dead code -``` - -## Code Style +## CONVENTIONS **Formatter**: `nix fmt` before commit (nixpkgs-fmt) @@ -93,80 +77,47 @@ meta = with lib; { }; ``` -## Package Patterns +## PACKAGE PATTERNS -**Rust** (code2prompt): -```nix -rustPlatform.buildRustPackage rec { - cargoLock.lockFile = src + "/Cargo.lock"; -} +**Rust**: `rustPlatform.buildRustPackage rec { cargoLock.lockFile = src + "/Cargo.lock"; }` + +**Shell**: `writeShellScriptBin "name" ''script''` or `mkDerivation` with custom `installPhase` + +**AppImage**: `appimageTools.wrapType2 { ... }` + +**Custom fetcher**: `fetchFromGitea { domain = "code.m3ta.dev"; owner = "m3tam3re"; ... }` + +## MODULE PATTERNS + +**Simple**: `options.cli.name = { enable = mkEnableOption "..."; }; config = mkIf cfg.enable { ... };` + +**Multiple**: `config = mkMerge [ (mkIf cfg.x.enable { ... }) (mkIf cfg.y.enable { ... }) ];` + +**Shared lib**: `portsLib = import ../../lib/ports.nix { inherit lib; }; portHelpers = portsLib.mkPortHelpers { ... };` + +## PORT MANAGEMENT + +Central port management: `config.m3ta.ports.get "service"` with host-specific via `hostOverrides` + +Generated: `/etc/m3ta/ports.json` (NixOS), `~/.config/m3ta/ports.json` (HM) + +## COMMANDS + +```bash +nix flake check # Validate flake +nix fmt # Format (nixpkgs-fmt) +nix build .# # Build package +nix flake show # List outputs +nix develop # Enter dev shell +nix develop .#python # Python shell +nix develop .#devops # DevOps shell + +# In dev shell only: +statix check . # Lint +deadnix . # Find dead code ``` -**Shell scripts** (launch-webapp): -```nix -writeShellScriptBin "name" ''script'' -# Or mkDerivation with custom installPhase -``` - -**AppImage** (msty-studio): -```nix -appimageTools.wrapType2 { ... } -``` - -**Custom fetcher** (zellij-ps): -```nix -fetchFromGitea { - domain = "code.m3ta.dev"; - owner = "m3tam3re"; - ... -} -``` - -## Module Patterns - -**Simple enable** (zellij-ps.nix): -```nix -options.cli.zellij-ps = { - enable = mkEnableOption "..."; - someOption = mkOption { type = types.str; default = "..."; }; -}; -config = mkIf cfg.enable { home.packages = [...]; }; -``` - -**Multiple conditionals** (editors.nix): -```nix -config = mkMerge [ - (mkIf cfg.neovim.enable { ... }) - (mkIf cfg.zed.enable { ... }) - (mkIf (cfg.neovim.enable || cfg.zed.enable) { ... }) -]; -``` - -**Shared library** (ports modules): -```nix -portsLib = import ../../lib/ports.nix { inherit lib; }; -portHelpers = portsLib.mkPortHelpers { ports = cfg.definitions; ... }; -``` - -## Port Management - -Central port management with host-specific overrides: - -```nix -m3ta.ports = { - enable = true; - definitions = { nginx = 80; grafana = 3000; }; - hostOverrides.laptop = { nginx = 8080; }; - currentHost = config.networking.hostName; # NixOS auto -}; - -# Usage -services.nginx.port = config.m3ta.ports.get "nginx"; -``` - -**Generated files**: `/etc/m3ta/ports.json` (NixOS), `~/.config/m3ta/ports.json` (HM) - -## Anti-Patterns +## ANTI-PATTERNS | Don't | Do Instead | |-------|------------| @@ -176,7 +127,7 @@ services.nginx.port = config.m3ta.ports.get "nginx"; | Skip meta fields | Include all: description, homepage, license, platforms, mainProgram | | `with pkgs;` in modules | Explicit `pkgs.package` or `with pkgs; [ ... ]` in lists only | -## Commit Format +## COMMIT FORMAT ``` type: brief description @@ -184,7 +135,7 @@ type: brief description Types: `feat`, `fix`, `docs`, `style`, `refactor`, `chore` -## Gotchas +## NOTES - **Hash fetching**: Use `lib.fakeHash` initially, build to get real hash - **HM modules**: Category subdirs (`cli/`, `coding/`) have own `default.nix` aggregators diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index c9868c0..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,450 +0,0 @@ -# Contributing to m3ta-nixpkgs - -Thank you for your interest in contributing to m3ta-nixpkgs! This guide will help you get started with adding packages, modules, and improvements to this repository. - -## Table of Contents - -- [Getting Started](#getting-started) -- [Adding a New Package](#adding-a-new-package) -- [Adding a NixOS Module](#adding-a-nixos-module) -- [Adding a Home Manager Module](#adding-a-home-manager-module) -- [Code Style Guidelines](#code-style-guidelines) -- [Testing Your Changes](#testing-your-changes) -- [Submitting Changes](#submitting-changes) - -## Getting Started - -### Prerequisites - -- Nix with flakes enabled -- Git -- Basic understanding of Nix expressions - -### Enable Flakes - -Add to `~/.config/nix/nix.conf` or `/etc/nix/nix.conf`: - -``` -experimental-features = nix-command flakes -``` - -### Clone and Setup - -```bash -git clone https://code.m3ta.dev/m3tam3re/nixpkgs -cd m3ta-nixpkgs -nix develop # Enter development environment -``` - -## Adding a New Package - -### Step 1: Create Package Directory - -```bash -mkdir pkgs/my-package -``` - -### Step 2: Write the Package Expression - -Create `pkgs/my-package/default.nix` using the template: - -```bash -cp templates/package/default.nix pkgs/my-package/default.nix -``` - -Edit the file to match your package: - -```nix -{ - lib, - stdenv, - fetchFromGitHub, -}: -stdenv.mkDerivation rec { - pname = "my-package"; - version = "1.0.0"; - - src = fetchFromGitHub { - owner = "owner"; - repo = "repo"; - rev = "v${version}"; - hash = lib.fakeHash; # Use this initially - }; - - meta = with lib; { - description = "A short description"; - homepage = "https://github.com/owner/repo"; - license = licenses.mit; - platforms = platforms.linux; - mainProgram = "my-package"; - }; -} -``` - -### Step 3: Get the Correct Hash - -```bash -nix build .#my-package -# The error message will show the correct hash -# Replace lib.fakeHash with the actual hash -``` - -### Step 4: Register the Package - -Add to `pkgs/default.nix`: - -```nix -{pkgs, ...}: { - # ... existing packages ... - my-package = pkgs.callPackage ./my-package {}; -} -``` - -### Step 5: Test the Package - -```bash -# Build the package -nix build .#my-package - -# Run the package -nix run .#my-package - -# Check if it works -./result/bin/my-package --version -``` - -## Adding a NixOS Module - -### Step 1: Create Module File - -```bash -cp templates/nixos-module/default.nix modules/nixos/my-module.nix -``` - -### Step 2: Implement the Module - -Edit `modules/nixos/my-module.nix`: - -```nix -{ - config, - lib, - pkgs, - ... -}: -with lib; let - cfg = config.m3ta.myModule; -in { - options.m3ta.myModule = { - enable = mkEnableOption "my custom module"; - # Add your options here - }; - - config = mkIf cfg.enable { - # Add your configuration here - }; -} -``` - -### Step 3: Register the Module - -Add to `modules/nixos/default.nix`: - -```nix -{ - config, - lib, - pkgs, - ... -}: { - imports = [ - ./my-module.nix # Add this line - ]; -} -``` - -### Step 4: Test the Module - -Create a test configuration or add to your existing NixOS configuration: - -```nix -{ - inputs.m3ta-nixpkgs.url = "path:/path/to/m3ta-nixpkgs"; - - outputs = {nixpkgs, m3ta-nixpkgs, ...}: { - nixosConfigurations.test = nixpkgs.lib.nixosSystem { - modules = [ - m3ta-nixpkgs.nixosModules.default - { - m3ta.myModule.enable = true; - } - ]; - }; - }; -} -``` - -## Adding a Home Manager Module - -### Step 1: Create Module File - -```bash -cp templates/home-manager-module/default.nix modules/home-manager/my-module.nix -``` - -### Step 2: Implement the Module - -Edit `modules/home-manager/my-module.nix`: - -```nix -{ - config, - lib, - pkgs, - ... -}: -with lib; let - cfg = config.programs.myProgram; -in { - options.programs.myProgram = { - enable = mkEnableOption "my program"; - # Add your options here - }; - - config = mkIf cfg.enable { - # Add your configuration here - }; -} -``` - -### Step 3: Register the Module - -Add to `modules/home-manager/default.nix`: - -```nix -{ - # ... existing modules ... - myModule = import ./my-module.nix; -} -``` - -### Step 4: Test the Module - -Test with Home Manager: - -```nix -{ - inputs.m3ta-nixpkgs.url = "path:/path/to/m3ta-nixpkgs"; - - outputs = {home-manager, m3ta-nixpkgs, ...}: { - homeConfigurations.test = home-manager.lib.homeManagerConfiguration { - modules = [ - m3ta-nixpkgs.homeManagerModules.default - { - programs.myProgram.enable = true; - } - ]; - }; - }; -} -``` - -## Code Style Guidelines - -### Nix Code Style - -1. **Formatting**: Use `nixpkgs-fmt` for consistent formatting - - ```bash - nix fmt - ``` - -2. **Naming Conventions**: - - Package names: lowercase with hyphens (`my-package`) - - Module options: camelCase (`myModule`) - - Variables: camelCase (`cfg`, `myVar`) - -3. **File Structure**: - - One package per directory - - Use `default.nix` for the main expression - - Keep related files together - -4. **Comments**: - - Add comments for non-obvious code - - Document complex expressions - - Explain why, not what - -### Example Good Practice - -```nix -{ - lib, - stdenv, - fetchFromGitHub, - # Group related dependencies - # Build tools - cmake, - pkg-config, - # Libraries - openssl, - zlib, -}: -stdenv.mkDerivation rec { - pname = "example"; - version = "1.0.0"; - - src = fetchFromGitHub { - owner = "example"; - repo = pname; - rev = "v${version}"; - hash = "sha256-..."; - }; - - nativeBuildInputs = [ - cmake - pkg-config - ]; - - buildInputs = [ - openssl - zlib - ]; - - # Explain non-obvious configuration - cmakeFlags = [ - "-DENABLE_FEATURE=ON" # Required for proper functionality - ]; - - meta = with lib; { - description = "Clear, concise description"; - homepage = "https://example.com"; - license = licenses.mit; - maintainers = with maintainers; []; - platforms = platforms.linux; - mainProgram = "example"; - }; -} -``` - -## Testing Your Changes - -### 1. Check Flake Validity - -```bash -nix flake check -``` - -### 2. Build All Packages - -```bash -nix flake show # See all outputs -nix build .#package-name -``` - -### 3. Test in a Clean Environment - -```bash -# Build without any cached results -nix build .#package-name --rebuild -``` - -### 4. Test Module Integration - -Test modules in a VM: - -```bash -nixos-rebuild build-vm --flake .#test-config -``` - -### 5. Verify Metadata - -Check that package metadata is complete: - -```bash -nix eval .#packages.x86_64-linux.my-package.meta --json | jq -``` - -## Submitting Changes - -### Before Submitting - -- [ ] Code follows style guidelines -- [ ] Package builds successfully -- [ ] Tests pass (if applicable) -- [ ] Documentation is updated -- [ ] Commit messages are clear and descriptive - -- [ ] CI workflows pass (check GitHub Actions) - -### Commit Message Format - -``` -type: brief description - -Longer explanation if needed. - -- Detail 1 -- Detail 2 -``` - -Types: - -- `feat`: New feature (package, module) -- `fix`: Bug fix -- `docs`: Documentation changes -- `style`: Code style changes -- `refactor`: Code refactoring -- `chore`: Maintenance tasks - -### Examples - -``` -feat: add hyprpaper-random package - -Add a random wallpaper selector for Hyprpaper. -Includes systemd timer integration. -``` - -``` -fix: correct msty-studio dependencies - -Add missing libGL dependency that caused runtime errors. -``` - -``` -docs: update README with usage instructions - -Add detailed instructions for using packages and modules. -``` - -## Best Practices - -### Security - -- Never include API keys, passwords, or secrets -- Use `lib.fakeHash` initially, then update with correct hash -- Review dependencies for known vulnerabilities - -### Performance - -- Use `callPackage` for better caching -- Avoid unnecessary `import` statements -- Use overlays efficiently - -### Maintainability - -- Keep packages focused and simple -- Document complex logic -- Follow nixpkgs conventions -- Update regularly - -## Getting Help - -- Check [Nix Pills](https://nixos.org/guides/nix-pills/) -- Read [Nixpkgs Manual](https://nixos.org/manual/nixpkgs/stable/) -- Ask in [NixOS Discourse](https://discourse.nixos.org/) -- Join [NixOS Matrix](https://matrix.to/#/#nix:nixos.org) - -## License - -By contributing, you agree that your contributions will be licensed under the same license as the project. diff --git a/QUICKSTART_PORT_MANAGEMENT.md b/QUICKSTART_PORT_MANAGEMENT.md deleted file mode 100644 index 58da127..0000000 --- a/QUICKSTART_PORT_MANAGEMENT.md +++ /dev/null @@ -1,230 +0,0 @@ -# Quick Start Guide - Port Management Module - -Get started with centralized port management in 5 minutes! - -## Installation - -### Step 1: Add to your flake inputs - -```nix -{ - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - m3ta-nixpkgs.url = "git+https://code.m3ta.dev/m3tam3re/nixpkgs"; - }; -} -``` - -### Step 2: Choose your configuration type - -## For NixOS Systems - -### Basic Setup - -```nix -{ - imports = [ inputs.m3ta-nixpkgs.nixosModules.default ]; - - m3ta.ports = { - enable = true; - - definitions = { - nginx = 80; - ssh = 22; - grafana = 3000; - }; - - hostOverrides = { - laptop = { nginx = 8080; ssh = 2222; }; - server = {}; # Uses defaults - }; - }; -} -``` - -### Using Ports - -```nix -# In any NixOS service configuration: -services.nginx.defaultHTTPListenPort = config.m3ta.ports.get "nginx"; -services.openssh.ports = [ (config.m3ta.ports.get "ssh") ]; - -# Firewall rules: -networking.firewall.allowedTCPPorts = [ - (config.m3ta.ports.get "ssh") - (config.m3ta.ports.get "nginx") -]; -``` - -## For Home Manager - -### Basic Setup - -```nix -{ - imports = [ inputs.m3ta-nixpkgs.homeManagerModules.default ]; - - m3ta.ports = { - enable = true; - - definitions = { - vite-dev = 5173; - jupyter = 8888; - local-api = 8000; - }; - - hostOverrides = { - laptop = { vite-dev = 5174; }; - desktop = { jupyter = 9999; }; - }; - - currentHost = "laptop"; # Set your hostname - generateEnvVars = true; # Creates PORT_* environment variables - }; -} -``` - -### Using Ports - -```nix -# Shell aliases: -programs.bash.shellAliases = { - dev = "PORT=${toString (config.m3ta.ports.get "vite-dev")} npm run dev"; -}; - -# Environment variables: -home.sessionVariables = { - DEV_URL = "http://localhost:${toString (config.m3ta.ports.get "vite-dev")}"; -}; - -# Config files: -home.file.".config/myapp/config.json".text = builtins.toJSON { - port = config.m3ta.ports.get "vite-dev"; -}; -``` - -## Common Patterns - -### Multi-Host Configuration - -```nix -# shared-ports.nix -{ - ports = { - nginx = 80; - ssh = 22; - grafana = 3000; - }; - - hostOverrides = { - laptop = { nginx = 8080; ssh = 2222; }; - server = {}; - }; -} -``` - -```nix -# In your flake.nix -let - sharedPorts = import ./shared-ports.nix; -in { - nixosConfigurations.laptop = nixpkgs.lib.nixosSystem { - modules = [ - m3ta-nixpkgs.nixosModules.default - { - m3ta.ports = { - enable = true; - definitions = sharedPorts.ports; - hostOverrides = sharedPorts.hostOverrides; - currentHost = "laptop"; - }; - } - ]; - }; -} -``` - -### Proxy Configuration - -```nix -services.nginx.virtualHosts."example.com" = { - locations."/grafana/" = { - proxyPass = "http://127.0.0.1:${toString (config.m3ta.ports.get "grafana")}"; - }; - locations."/prometheus/" = { - proxyPass = "http://127.0.0.1:${toString (config.m3ta.ports.get "prometheus")}"; - }; -}; -``` - -### Container Services - -```nix -virtualisation.oci-containers.containers.grafana = { - image = "grafana/grafana:latest"; - ports = [ - "${toString (config.m3ta.ports.get "grafana")}:3000" - ]; -}; -``` - -## Available Functions - -| Function | Description | Example | -| ----------------------------------------------- | -------------------------- | ----------------------------------------------- | -| `config.m3ta.ports.get "service"` | Get port for current host | `config.m3ta.ports.get "nginx"` | -| `config.m3ta.ports.getForHost "host" "service"` | Get port for specific host | `config.m3ta.ports.getForHost "laptop" "nginx"` | -| `config.m3ta.ports.all` | All ports (merged) | `config.m3ta.ports.all` | -| `config.m3ta.ports.services` | List service names | `config.m3ta.ports.services` | - -## Debugging - -### View all configured ports - -```bash -# NixOS -cat /etc/m3ta/ports.json | jq - -# Home Manager -cat ~/.config/m3ta/ports.json | jq -``` - -### Check what port is being used - -```nix -# Add to your config temporarily -environment.etc."debug-ports.txt".text = '' - nginx: ${toString (config.m3ta.ports.get "nginx")} - ssh: ${toString (config.m3ta.ports.get "ssh")} - all: ${builtins.toJSON config.m3ta.ports.all} -''; -``` - -## Implementation Details - -The port management module is implemented across several files: - -- `lib/ports.nix`: Core utilities for port management -- `modules/nixos/ports.nix`: NixOS module implementation -- `modules/home-manager/ports.nix`: Home Manager module implementation - -For detailed implementation and source code, see these files in the repository. - -## Common Issues - -**"Service not defined" error** - -- Make sure the service is in your `definitions` block - -**Wrong port being used** - -- Check your `currentHost` matches your actual hostname -- Verify overrides in `/etc/m3ta/ports.json` or `~/.config/m3ta/ports.json` - -**Type errors** - -- Ports must be integers: `nginx = 80;` not `nginx = "80";` - -## Need Help? - -Open an issue at: https://code.m3ta.dev/m3tam3re/nixpkgs diff --git a/README.md b/README.md index 5a45939..0fd57ae 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,30 @@ # m3ta-nixpkgs -My personal Nix repository containing custom packages, overlays, NixOS modules, and Home Manager modules. +Personal Nix flake repository: custom packages, overlays, NixOS modules, and Home Manager modules. ## Features - 🎁 **Custom Packages**: Collection of personal Nix packages - 🔄 **Overlays**: Package modifications and enhancements -- 🐚 **Development Shells**: Pre-configured environments for Python and DevOps +- 🐚 **Development Shells**: Pre-configured environments (Python, DevOps) - ⚙️ **NixOS Modules**: System-level configuration modules - 🏠 **Home Manager Modules**: User-level configuration modules - 📚 **Library Functions**: Helper utilities for configuration management - ❄️ **Flakes Only**: Modern Nix flakes support (no channels) -## Repository Structure +## Quick Links -``` -m3ta-nixpkgs/ -├── flake.nix # Main flake configuration -├── pkgs/ # Custom packages -│ ├── default.nix # Package registry -│ ├── code2prompt/ -│ ├── hyprpaper-random/ -│ ├── launch-webapp/ -│ ├── msty-studio/ -│ ├── pomodoro-timer/ -│ ├── tuxedo-backlight/ -│ └── zellij-ps/ -├── shells/ # Development shells -│ ├── default.nix # Shell registry (default, python, devops) -│ ├── python.nix # Python development environment -│ └── devops.nix # DevOps/infrastructure tools -├── overlays/ # Overlays -│ ├── default.nix -│ └── mods/ # Package modifications -│ ├── default.nix -│ └── n8n.nix -├── modules/ -│ ├── nixos/ # NixOS modules -│ │ ├── default.nix -│ │ └── ports.nix # Port management module -│ └── home-manager/ # Home Manager modules -│ ├── default.nix -│ ├── ports.nix # Port management module -│ └── zellij-ps.nix -├── lib/ # Library functions -│ ├── default.nix # Library entry point -│ └── ports.nix # Port management utilities -├── examples/ # Usage examples -│ ├── home-manager-standalone.nix -│ ├── nixos-configuration.nix -└── templates/ # Templates for new packages/modules -``` +- 📖 [Full Documentation](./docs) +- 🚀 [Quick Start Guide](./docs/QUICKSTART.md) +- 📚 [Architecture](./docs/ARCHITECTURE.md) +- 🤝 [Contributing](./docs/CONTRIBUTING.md) +- 📦 [Packages](./docs/packages/) +- ⚙️ [Modules](./docs/modules/) +- 📖 [Guides](./docs/guides/) -## Usage +## Installation -### Adding to Your Flake - -Add this repository to your flake inputs: +### Add to Your Flake ```nix { @@ -67,446 +35,22 @@ Add this repository to your flake inputs: } ``` -### Using Packages in NixOS Configuration - -#### Method 1: Using the Overlay - -```nix -{ - nixpkgs.overlays = [ - inputs.m3ta-nixpkgs.overlays.default - ]; - - environment.systemPackages = with pkgs; [ - code2prompt - hyprpaper-random - msty-studio - # ... any other custom packages - ]; -} -``` - -#### Method 2: Direct Package Reference - -```nix -{ - environment.systemPackages = [ - inputs.m3ta-nixpkgs.packages.${pkgs.stdenv.hostPlatform.system}.code2prompt - inputs.m3ta-nixpkgs.packages.${pkgs.stdenv.hostPlatform.system}.hyprpaper-random - ]; -} -``` - -### Using in Home Manager - -#### With Overlay - -```nix -{ - nixpkgs.overlays = [ - inputs.m3ta-nixpkgs.overlays.default - ]; - - home.packages = with pkgs; [ - zellij-ps - pomodoro-timer - ]; -} -``` - -#### With Home Manager Modules - -```nix -{ - imports = [ - inputs.m3ta-nixpkgs.homeManagerModules.default - # Or specific modules: - # inputs.m3ta-nixpkgs.homeManagerModules.zellij-ps - ]; - - # Module-specific configuration here -} -``` - -### Using NixOS Modules - -```nix -{ - imports = [ - inputs.m3ta-nixpkgs.nixosModules.default - # Or specific modules: - # inputs.m3ta-nixpkgs.nixosModules.ports - ]; - - # Configure the ports module (if enabled) - m3ta.ports = { - enable = true; - definitions = { - nginx = 80; - ssh = 22; - }; - }; -} -``` - -### Building Packages Directly +### Quick Start ```bash -# Build a specific package +# Build a package nix build git+https://code.m3ta.dev/m3tam3re/nixpkgs#code2prompt -# Run a package without installing +# Run a package nix run git+https://code.m3ta.dev/m3tam3re/nixpkgs#zellij-ps -# Install to your profile -nix profile install git+https://code.m3ta.dev/m3tam3re/nixpkgs#msty-studio - # List all available packages nix flake show git+https://code.m3ta.dev/m3tam3re/nixpkgs ``` -## Development Shells +## Documentation -This repository provides pre-configured development environments. All shells are accessible via `nix develop`. - -### Available Shells - -| Shell | Description | Usage | -| --------- | ---------------------------------------------- | ---------------------- | -| `default` | Nix development tools for working on this repo | `nix develop` | -| `python` | Python with common libraries and tools | `nix develop .#python` | -| `devops` | Docker, Kubernetes, Terraform, cloud CLIs | `nix develop .#devops` | - -### Quick Start - -```bash -# Enter a development environment -nix develop git+https://code.m3ta.dev/m3tam3re/nixpkgs#python -nix develop git+https://code.m3ta.dev/m3tam3re/nixpkgs#devops - -# Run a command in a shell without entering it -nix develop git+https://code.m3ta.dev/m3tam3re/nixpkgs#python --command python --version -``` - -### Using Shells in Home Manager - -Add shells to your home-manager configuration for persistent access: - -```nix -{ - inputs.m3ta-nixpkgs.url = "git+https://code.m3ta.dev/m3tam3re/nixpkgs"; - - # Make tools globally available - home.packages = with inputs.m3ta-nixpkgs.devShells.${pkgs.stdenv.hostPlatform.system}; - python.buildInputs ++ devops.buildInputs; - - # Or create aliases - programs.zsh.shellAliases = { - dev-python = "nix develop ${inputs.m3ta-nixpkgs}#python"; - dev-devops = "nix develop ${inputs.m3ta-nixpkgs}#devops"; - }; -} -``` - -### Using Shells in NixOS - -Add shells system-wide: - -```nix -{ - inputs.m3ta-nixpkgs.url = "git+https://code.m3ta.dev/m3tam3re/nixpkgs"; - - # Make tools available to all users - environment.systemPackages = - inputs.m3ta-nixpkgs.devShells.${pkgs.stdenv.hostPlatform.system}.python.buildInputs; - - # System-wide aliases - environment.shellAliases = { - dev-python = "nix develop ${inputs.m3ta-nixpkgs}#python"; - }; -} -``` - -### Project-Specific Usage with direnv - -Create `.envrc` in your project directory: - -```bash -use flake git+https://code.m3ta.dev/m3tam3re/nixpkgs#python -``` - -Then run `direnv allow`. The environment activates automatically when you enter the directory! - -### Extending Shells for Your Project - -Create a `flake.nix` in your project that extends a base shell: - -```nix -{ - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - m3ta-nixpkgs.url = "git+https://code.m3ta.dev/m3tam3re/nixpkgs"; - }; - - outputs = { nixpkgs, m3ta-nixpkgs, ... }: { - devShells.x86_64-linux.default = nixpkgs.legacyPackages.x86_64-linux.mkShell { - # Inherit all packages from base Python shell - inputsFrom = [ m3ta-nixpkgs.devShells.x86_64-linux.python ]; - - # Add project-specific packages - buildInputs = [ nixpkgs.legacyPackages.x86_64-linux.postgresql ]; - - # Project-specific environment variables - DATABASE_URL = "postgresql://localhost/mydb"; - }; - }; -} -``` - -### Shell Details - -See individual shell files for detailed package lists and configuration: - -- **Default Shell**: `shells/default.nix` - Nix development tools -- **Python Shell**: `shells/python.nix` - Python development environment -- **DevOps Shell**: `shells/devops.nix` - Infrastructure and cloud tools - -## Development - -### Setting Up Development Environment - -```bash -# Clone the repository -git clone https://code.m3ta.dev/m3tam3re/nixpkgs -cd m3ta-nixpkgs - -# Enter development shell -nix develop - -# Check flake validity -nix flake check - -# Format code -nix fmt -``` - -### Adding a New Package - -1. Create a new directory under `pkgs/`: - -```bash -mkdir pkgs/my-package -``` - -2. Create `pkgs/my-package/default.nix`: - -```nix -{ - lib, - stdenv, - fetchFromGitHub, -}: -stdenv.mkDerivation rec { - pname = "my-package"; - version = "1.0.0"; - - src = fetchFromGitHub { - owner = "owner"; - repo = "repo"; - rev = "v${version}"; - hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; - }; - - meta = with lib; { - description = "Description of my package"; - homepage = "https://github.com/owner/repo"; - license = licenses.mit; - maintainers = with maintainers; [ ]; - platforms = platforms.linux; - }; -} -``` - -3. Add to `pkgs/default.nix`: - -```nix -{ - # ... existing packages ... - my-package = pkgs.callPackage ./my-package {}; -} -``` - -### Adding a New NixOS Module - -1. Create `modules/nixos/my-module.nix` -2. Add import to `modules/nixos/default.nix` -3. Update `flake.nix` to expose it: - -```nix -nixosModules = { - default = ./modules/nixos; - my-module = ./modules/nixos/my-module.nix; -}; -``` - -### Adding a New Home Manager Module - -1. Create `modules/home-manager/my-module.nix` -2. Add to `modules/home-manager/default.nix` -3. Update `flake.nix` to expose it: - -```nix -homeManagerModules = { - default = import ./modules/home-manager; - my-module = import ./modules/home-manager/my-module.nix; -}; -``` - -## Port Management Module - -**NEW!** Centrally manage service ports across your NixOS systems and Home Manager configurations with automatic host-specific overrides. - -### Features - -- ✅ **Centralized Configuration**: Define all ports in one place -- ✅ **Host-Specific Overrides**: Different ports for different machines (laptop, server, desktop) -- ✅ **Type Safety**: Full NixOS/Home Manager type system integration -- ✅ **Auto-Generated Files**: JSON exports and environment variables -- ✅ **Easy Integration**: Works with any service configuration -- ✅ **Conflict Prevention**: Avoid port conflicts across different hosts - -### Quick Start - NixOS Module - -```nix -{ - imports = [ inputs.m3ta-nixpkgs.nixosModules.default ]; - - m3ta.ports = { - enable = true; - - definitions = { - nginx = 80; - grafana = 3000; - ssh = 22; - prometheus = 9090; - }; - - hostOverrides = { - laptop = { - nginx = 8080; # Use non-privileged port - ssh = 2222; - }; - }; - - # Automatically uses system hostname - currentHost = config.networking.hostName; - }; - - # Use ports in your configuration - services.nginx.defaultHTTPListenPort = config.m3ta.ports.get "nginx"; - services.openssh.ports = [ (config.m3ta.ports.get "ssh") ]; - - # Firewall with dynamic ports - networking.firewall.allowedTCPPorts = [ - (config.m3ta.ports.get "ssh") - (config.m3ta.ports.get "nginx") - ]; -} -``` - -### Quick Start - Home Manager Module - -```nix -{ - imports = [ inputs.m3ta-nixpkgs.homeManagerModules.default ]; - - m3ta.ports = { - enable = true; - - definitions = { - vite-dev = 5173; - jupyter = 8888; - local-api = 8000; - }; - - hostOverrides = { - laptop = { - vite-dev = 5174; - jupyter = 9999; - }; - }; - - currentHost = "laptop"; - - # Auto-generate PORT_* environment variables - generateEnvVars = true; - }; - - # Use ports in your configuration - programs.bash.shellAliases = { - dev = "PORT=${toString (config.m3ta.ports.get "vite-dev")} npm run dev"; - jupyter = "jupyter lab --port=${toString (config.m3ta.ports.get "jupyter")}"; - }; - - home.sessionVariables = { - DEV_SERVER = "http://localhost:${toString (config.m3ta.ports.get "vite-dev")}"; - }; -} -``` - -### Available Module Functions - -- `config.m3ta.ports.get "service"` - Get port for service on current host -- `config.m3ta.ports.getForHost "host" "service"` - Get port for specific host -- `config.m3ta.ports.all` - All ports for current host (merged defaults + overrides) -- `config.m3ta.ports.allForHost "host"` - All ports for specific host -- `config.m3ta.ports.services` - List all defined service names - -### Auto-Generated Files - -**NixOS**: `/etc/m3ta/ports.json` -**Home Manager**: `~/.config/m3ta/ports.json` - -These files contain all port configuration for easy inspection and external tool integration. - -### Using the Library Directly - -For advanced use cases, you can use the underlying library functions without the module: - -```nix -{ - outputs = { nixpkgs, m3ta-nixpkgs, ... }: { - nixosConfigurations.laptop = nixpkgs.lib.nixosSystem { - modules = [ - ({ inputs, system, config, ... }: let - m3taLib = inputs.m3ta-nixpkgs.lib.${system}; - - myPorts = { - ports = { nginx = 80; grafana = 3000; }; - hostPorts = { laptop = { nginx = 8080; }; }; - }; - - portHelpers = m3taLib.ports.mkPortHelpers myPorts; - hostname = config.networking.hostName; - in { - services.nginx.port = portHelpers.getPort "nginx" hostname; - }) - ]; - }; - }; -} -``` - -### Documentation - -See library documentation: - -- `lib/ports.nix` - Library source code with inline documentation - -Example configurations: - -- `examples/nixos-configuration.nix` - NixOS configuration example -- `examples/home-manager-standalone.nix` - Home Manager configuration example +For detailed usage, module documentation, package references, and contribution guidelines, see the [full documentation](./docs). ## Available Packages @@ -515,6 +59,7 @@ Example configurations: | `code2prompt` | Convert code to prompts | | `hyprpaper-random` | Random wallpaper setter for Hyprpaper | | `launch-webapp` | Launch web applications | +| `mem0` | AI memory assistant with vector storage | | `msty-studio` | Msty Studio application | | `pomodoro-timer` | Pomodoro timer utility | | `tuxedo-backlight` | Backlight control for Tuxedo laptops | diff --git a/flake.nix b/flake.nix index a272cea..fb1aca3 100644 --- a/flake.nix +++ b/flake.nix @@ -58,6 +58,7 @@ default = ./modules/nixos; # Individual modules for selective imports ports = ./modules/nixos/ports.nix; + mem0 = ./modules/nixos/mem0.nix; }; # Home Manager modules - for user-level configuration diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index 6f78dce..2583ab0 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -11,6 +11,7 @@ # Add your custom modules here as imports or inline definitions imports = [ + ./mem0.nix ./ports.nix # Example: ./my-service.nix # Add more module files here as you create them diff --git a/modules/nixos/mem0.nix b/modules/nixos/mem0.nix new file mode 100644 index 0000000..31bc177 --- /dev/null +++ b/modules/nixos/mem0.nix @@ -0,0 +1,363 @@ +# NixOS Module for Mem0 REST API Server +# +# This module provides a systemd service for the Mem0 REST API server, +# allowing you to run mem0 as a system service with configurable vector storage. +# +# Usage in your NixOS configuration: +# +# # In your flake.nix or configuration.nix: +# imports = [ inputs.m3ta-nixpkgs.nixosModules.default ]; +# +# m3ta.mem0 = { +# enable = true; +# port = 8000; +# host = "127.0.0.1"; +# +# # LLM Configuration +# llm = { +# provider = "openai"; +# apiKeyFile = "/run/secrets/openai-api-key"; # Use agenix or sops-nix +# model = "gpt-4"; +# }; +# +# # Vector Storage Configuration +# vectorStore = { +# provider = "qdrant"; # or "chroma", "pinecone", etc. +# config = { +# host = "localhost"; +# port = 6333; +# }; +# }; +# +# # Optional: Environment variables +# environmentFile = "/etc/mem0/environment"; +# }; +# +# Using with m3ta.ports (recommended): +# +# m3ta.ports = { +# enable = true; +# definitions = { mem0 = 8000; }; +# hostOverrides.laptop = { mem0 = 8080; }; +# currentHost = config.networking.hostName; +# }; +# +# m3ta.mem0 = { +# enable = true; +# port = config.m3ta.ports.get "mem0"; # Automatically uses host-specific port +# }; +{ + config, + lib, + pkgs, + ... +}: +with lib; let + cfg = config.m3ta.mem0; + + # Python environment with mem0 + pythonEnv = pkgs.python3.withPackages (ps: + with ps; [ + cfg.package + ]); + + # Convert vector store config to environment variables + vectorStoreEnv = + if cfg.vectorStore.provider == "qdrant" + then { + MEM0_VECTOR_PROVIDER = "qdrant"; + QDRANT_HOST = cfg.vectorStore.config.host or "localhost"; + QDRANT_PORT = toString (cfg.vectorStore.config.port or 6333); + QDRANT_COLLECTION = cfg.vectorStore.config.collection_name or "mem0_memories"; + } + else if cfg.vectorStore.provider == "pgvector" + then { + MEM0_VECTOR_PROVIDER = "pgvector"; + POSTGRES_HOST = cfg.vectorStore.config.host or "localhost"; + POSTGRES_PORT = toString (cfg.vectorStore.config.port or 5432); + POSTGRES_DB = cfg.vectorStore.config.dbname or "postgres"; + POSTGRES_USER = cfg.vectorStore.config.user or "postgres"; + POSTGRES_PASSWORD = cfg.vectorStore.config.password or "postgres"; + POSTGRES_COLLECTION = cfg.vectorStore.config.collection_name or "mem0_memories"; + } + else if cfg.vectorStore.provider == "chroma" + then { + MEM0_VECTOR_PROVIDER = "chroma"; + CHROMA_HOST = cfg.vectorStore.config.host or "localhost"; + CHROMA_PORT = toString (cfg.vectorStore.config.port or 8000); + CHROMA_COLLECTION = cfg.vectorStore.config.collection_name or "mem0_memories"; + } + else {}; + + # Start script that sets up environment and runs the server + startScript = pkgs.writeShellScript "mem0-start" '' + set -e + + # Load environment file if specified + ${optionalString (cfg.environmentFile != null) '' + if [ -f "${cfg.environmentFile}" ]; then + set -a + source "${cfg.environmentFile}" + set +a + fi + ''} + + # Load API key from file if specified + ${optionalString (cfg.llm.apiKeyFile != null) '' + if [ -f "${cfg.llm.apiKeyFile}" ]; then + export OPENAI_API_KEY="$(cat ${cfg.llm.apiKeyFile})" + fi + ''} + + # Create state directory + mkdir -p ${cfg.stateDir} + cd ${cfg.stateDir} + + # Run the server + exec ${pythonEnv}/bin/mem0-server + ''; +in { + options.m3ta.mem0 = { + enable = mkEnableOption "Mem0 REST API server"; + + package = mkOption { + type = types.package; + default = pkgs.mem0; + defaultText = literalExpression "pkgs.mem0"; + description = "The mem0 package to use."; + }; + + host = mkOption { + type = types.str; + default = "127.0.0.1"; + description = "Host address to bind the server to."; + }; + + port = mkOption { + type = types.port; + default = 8000; + description = "Port to run the REST API server on."; + }; + + workers = mkOption { + type = types.int; + default = 1; + description = "Number of worker processes."; + }; + + logLevel = mkOption { + type = types.enum ["critical" "error" "warning" "info" "debug" "trace"]; + default = "info"; + description = "Logging level for the server."; + }; + + stateDir = mkOption { + type = types.path; + default = "/var/lib/mem0"; + description = "Directory to store mem0 data and state."; + }; + + user = mkOption { + type = types.str; + default = "mem0"; + description = "User account under which mem0 runs."; + }; + + group = mkOption { + type = types.str; + default = "mem0"; + description = "Group under which mem0 runs."; + }; + + environmentFile = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Environment file containing additional configuration. + This file should contain KEY=value pairs, one per line. + Useful for secrets that shouldn't be in the Nix store. + ''; + example = "/etc/mem0/environment"; + }; + + # LLM Configuration + llm = { + provider = mkOption { + type = types.enum ["openai" "anthropic" "azure" "groq" "together" "ollama" "litellm"]; + default = "openai"; + description = "LLM provider to use for memory operations."; + }; + + model = mkOption { + type = types.str; + default = "gpt-4o-mini"; + description = "Model name to use for the LLM."; + }; + + apiKeyFile = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Path to file containing the API key for the LLM provider. + The file should contain only the API key. + This is more secure than putting the key in the Nix store. + ''; + example = "/run/secrets/openai-api-key"; + }; + + temperature = mkOption { + type = types.nullOr types.float; + default = null; + description = "Temperature parameter for LLM generation."; + }; + + maxTokens = mkOption { + type = types.nullOr types.int; + default = null; + description = "Maximum tokens for LLM generation."; + }; + + extraConfig = mkOption { + type = types.attrs; + default = {}; + description = "Additional LLM configuration options."; + example = { + top_p = 1.0; + frequency_penalty = 0.0; + }; + }; + }; + + # Vector Store Configuration + vectorStore = { + provider = mkOption { + type = types.enum [ + "qdrant" + "chroma" + "pinecone" + "weaviate" + "faiss" + "pgvector" + "redis" + "elasticsearch" + "milvus" + ]; + default = "qdrant"; + description = "Vector database provider to use."; + }; + + config = mkOption { + type = types.attrs; + default = {}; + description = '' + Configuration for the vector store. + The structure depends on the provider. + ''; + example = literalExpression '' + { + host = "localhost"; + port = 6333; + collection_name = "mem0_memories"; + } + ''; + }; + }; + + # Embedder Configuration + embedder = { + provider = mkOption { + type = types.nullOr (types.enum ["openai" "huggingface" "ollama" "vertexai"]); + default = null; + description = "Embedding model provider. If null, uses default."; + }; + + model = mkOption { + type = types.nullOr types.str; + default = null; + description = "Embedding model name to use."; + example = "text-embedding-3-small"; + }; + + config = mkOption { + type = types.attrs; + default = {}; + description = "Configuration for the embedder."; + example = { + model = "text-embedding-3-small"; + }; + }; + }; + }; + + config = mkIf cfg.enable { + # Create user and group + users.users.${cfg.user} = { + isSystemUser = true; + group = cfg.group; + description = "Mem0 service user"; + home = cfg.stateDir; + createHome = true; + }; + + users.groups.${cfg.group} = {}; + + # Systemd service + systemd.services.mem0 = { + description = "Mem0 REST API Server"; + after = ["network.target"]; + wantedBy = ["multi-user.target"]; + + serviceConfig = { + Type = "simple"; + User = cfg.user; + Group = cfg.group; + ExecStart = startScript; + Restart = "on-failure"; + RestartSec = "5s"; + + # Security hardening + NoNewPrivileges = true; + PrivateTmp = true; + ProtectSystem = "strict"; + ProtectHome = true; + ReadWritePaths = [cfg.stateDir]; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + RestrictRealtime = true; + RestrictNamespaces = true; + LockPersonality = true; + MemoryDenyWriteExecute = false; # Python needs this + RestrictAddressFamilies = ["AF_UNIX" "AF_INET" "AF_INET6"]; + }; + + environment = + { + PYTHONUNBUFFERED = "1"; + MEM0_HOST = cfg.host; + MEM0_PORT = toString cfg.port; + MEM0_LLM_PROVIDER = cfg.llm.provider; + MEM0_LLM_MODEL = cfg.llm.model; + MEM0_HISTORY_DB_PATH = "${cfg.stateDir}/history.db"; + MEM0_WORKERS = toString cfg.workers; + MEM0_LOG_LEVEL = cfg.logLevel; + } + // optionalAttrs (cfg.llm.temperature != null) { + MEM0_LLM_TEMPERATURE = toString cfg.llm.temperature; + } + // optionalAttrs (cfg.llm.extraConfig != {}) { + MEM0_LLM_EXTRA_CONFIG = builtins.toJSON cfg.llm.extraConfig; + } + // optionalAttrs (cfg.embedder.provider != null) { + MEM0_EMBEDDER_PROVIDER = cfg.embedder.provider; + } + // optionalAttrs (cfg.embedder.model != null) { + MEM0_EMBEDDER_MODEL = cfg.embedder.model; + } + // vectorStoreEnv; + }; + + # Open firewall port if binding to non-localhost + networking.firewall.allowedTCPPorts = mkIf (cfg.host != "127.0.0.1" && cfg.host != "localhost") [cfg.port]; + }; +} diff --git a/pkgs/AGENTS.md b/pkgs/AGENTS.md new file mode 100644 index 0000000..d442442 --- /dev/null +++ b/pkgs/AGENTS.md @@ -0,0 +1,33 @@ +# pkgs/ AGENTS.md + +## OVERVIEW +Custom package registry using `callPackage` pattern for flake-wide availability. + +## STRUCTURE +- `default.nix`: Central registry (entry point for overlays) +- `code2prompt/`: Rust package +- `hyprpaper-random/`: Bash script +- `launch-webapp/`: Webapp wrapper +- `mem0/`: Python package + custom `server.py` +- `msty-studio/`: AppImage wrapper +- `pomodoro-timer/`: Timer utility +- `tuxedo-backlight/`: Hardware control +- `zellij-ps/`: Gitea-hosted package + +## WHERE TO LOOK +- **Register new pkg**: Add entry to `pkgs/default.nix` attribute set +- **Modify pkg**: Edit `pkgs//default.nix` (version, hash, deps) +- **Check visibility**: `nix flake show` (uses `pkgs/default.nix` via `overlays/default.nix`) +- **Add scripts**: Place alongside `default.nix` in package folder (e.g., `mem0/server.py`) + +## CONVENTIONS +- **CallPackage**: Always use `pkgs.callPackage ./dir {}` in registry +- **Dir == Attr**: Package directory name MUST match its registry attribute +- **Path literals**: Reference local assets using `./file` within derivations +- **Self-contained**: Keep all package-specific files in their own directory + +## ANTI-PATTERNS +- **Orphaned dirs**: Creating `pkgs/new-pkg/` without updating `pkgs/default.nix` +- **Direct flake imports**: Importing packages in `flake.nix` instead of through the registry +- **Implicit deps**: Not declaring dependencies in the package function arguments +- **Non-derivations**: Placing NixOS/HM modules here (use `modules/` instead) diff --git a/pkgs/default.nix b/pkgs/default.nix index a5e198b..b11832b 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -4,6 +4,7 @@ code2prompt = pkgs.callPackage ./code2prompt {}; hyprpaper-random = pkgs.callPackage ./hyprpaper-random {}; launch-webapp = pkgs.callPackage ./launch-webapp {}; + mem0 = pkgs.callPackage ./mem0 {}; msty-studio = pkgs.callPackage ./msty-studio {}; pomodoro-timer = pkgs.callPackage ./pomodoro-timer {}; tuxedo-backlight = pkgs.callPackage ./tuxedo-backlight {}; diff --git a/pkgs/mem0/default.nix b/pkgs/mem0/default.nix new file mode 100644 index 0000000..9674394 --- /dev/null +++ b/pkgs/mem0/default.nix @@ -0,0 +1,99 @@ +{ + lib, + python3, + fetchFromGitHub, +}: +python3.pkgs.buildPythonPackage rec { + pname = "mem0ai"; + version = "1.0.0"; + pyproject = true; + + src = fetchFromGitHub { + owner = "mem0ai"; + repo = "mem0"; + rev = "v${version}"; + hash = "sha256-DluzIhwL/GanqtRSMW7ax+OVc2kZjbwQ0lpPCQFnD58="; + }; + + # Relax Python dependency version constraints + # mem0 has strict version pins that may not match nixpkgs versions + pythonRelaxDeps = true; + + build-system = with python3.pkgs; [ + hatchling + ]; + + dependencies = with python3.pkgs; [ + litellm + qdrant-client + pydantic + openai + posthog + pytz + sqlalchemy + protobuf + uvicorn + fastapi + ]; + + optional-dependencies = with python3.pkgs; { + graph = [ + # Note: some graph dependencies may not be available in nixpkgs + # neo4j is available, others will need to be packaged separately + ]; + vector_stores = [ + # chromadb # available in nixpkgs + # pinecone-client # may need packaging + # weaviate-client # may need packaging + # faiss # available as faiss-cpu + psycopg + pymongo + pymysql + redis + elasticsearch + ]; + llms = [ + groq + openai + # together # may need packaging + # litellm # may need packaging + # ollama # may need packaging + # google-generativeai # may need packaging + ]; + extras = [ + boto3 + # langchain-community # may need packaging + # sentence-transformers # may need packaging + elasticsearch + # fastembed # may need packaging + ]; + }; + + # Skip tests for now since they require additional test dependencies + doCheck = false; + + # Disable imports check because mem0 tries to create directories at import time + # which fails in the Nix sandbox (/homeless-shelter) + pythonImportsCheck = []; + + postInstall = '' + install -Dm755 ${./server.py} $out/bin/mem0-server + ''; + + meta = with lib; { + description = "Long-term memory layer for AI agents with REST API support"; + longDescription = '' + Mem0 provides a sophisticated memory layer for AI applications, offering: + - Memory management for AI agents (add, search, update, delete) + - REST API server for easy integration + - Support for multiple vector storage backends (Qdrant, Chroma, etc.) + - Graph memory capabilities + - Multi-modal support + - Configurable LLM and embedding models + ''; + homepage = "https://github.com/mem0ai/mem0"; + changelog = "https://github.com/mem0ai/mem0/releases/tag/v${version}"; + license = licenses.asl20; + platforms = platforms.linux; + }; +} diff --git a/pkgs/mem0/server.py b/pkgs/mem0/server.py new file mode 100644 index 0000000..ff0d9b0 --- /dev/null +++ b/pkgs/mem0/server.py @@ -0,0 +1,301 @@ +#!/usr/bin/env python3 +""" +Mem0 REST API Server +A FastAPI-based REST server for mem0 memory operations. +""" + +import logging +import os +import sys +from typing import Any, Dict, List, Optional + +from fastapi import FastAPI, HTTPException +from fastapi.responses import JSONResponse, RedirectResponse +from pydantic import BaseModel, Field + +from mem0 import Memory + +logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") + + +# Configuration from environment variables +def get_config_from_env() -> Dict[str, Any]: + """Build mem0 configuration from environment variables.""" + config = {"version": "v1.1"} + + # Vector store configuration + vector_provider = os.environ.get("MEM0_VECTOR_PROVIDER", "qdrant") + config["vector_store"] = {"provider": vector_provider} + + if vector_provider == "qdrant": + config["vector_store"]["config"] = { + "host": os.environ.get("QDRANT_HOST", "localhost"), + "port": int(os.environ.get("QDRANT_PORT", "6333")), + "collection_name": os.environ.get("QDRANT_COLLECTION", "mem0_memories"), + } + elif vector_provider == "pgvector": + config["vector_store"]["config"] = { + "host": os.environ.get("POSTGRES_HOST", "localhost"), + "port": int(os.environ.get("POSTGRES_PORT", "5432")), + "dbname": os.environ.get("POSTGRES_DB", "postgres"), + "user": os.environ.get("POSTGRES_USER", "postgres"), + "password": os.environ.get("POSTGRES_PASSWORD", "postgres"), + "collection_name": os.environ.get("POSTGRES_COLLECTION", "mem0_memories"), + } + elif vector_provider == "chroma": + config["vector_store"]["config"] = { + "host": os.environ.get("CHROMA_HOST", "localhost"), + "port": int(os.environ.get("CHROMA_PORT", "8000")), + "collection_name": os.environ.get("CHROMA_COLLECTION", "mem0_memories"), + } + + # LLM configuration + llm_provider = os.environ.get("MEM0_LLM_PROVIDER", "openai") + config["llm"] = { + "provider": llm_provider, + "config": { + "model": os.environ.get("MEM0_LLM_MODEL", "gpt-4o-mini"), + } + } + + # Temperature: only include if set (null means use provider default) + temperature = os.environ.get("MEM0_LLM_TEMPERATURE") + if temperature is not None: + config["llm"]["config"]["temperature"] = float(temperature) + + # Extra config: merge JSON env var if provided + extra_config_json = os.environ.get("MEM0_LLM_EXTRA_CONFIG") + if extra_config_json: + import json + try: + extra_config = json.loads(extra_config_json) + config["llm"]["config"].update(extra_config) + except json.JSONDecodeError: + logging.warning(f"Failed to parse MEM0_LLM_EXTRA_CONFIG: {extra_config_json}") + + # Add API key if available + if llm_provider == "openai": + api_key = os.environ.get("OPENAI_API_KEY") + if api_key: + config["llm"]["config"]["api_key"] = api_key + + # Embedder configuration + embedder_provider = os.environ.get("MEM0_EMBEDDER_PROVIDER", "openai") + config["embedder"] = { + "provider": embedder_provider, + } + + # Embedder model: only include if provider is set + if embedder_provider: + embedder_config = {} + embedder_model = os.environ.get("MEM0_EMBEDDER_MODEL") + if embedder_model: + embedder_config["model"] = embedder_model + config["embedder"]["config"] = embedder_config + + if embedder_provider == "openai": + api_key = os.environ.get("OPENAI_API_KEY") + if api_key: + config["embedder"]["config"]["api_key"] = api_key + + # History DB path + history_db_path = os.environ.get("MEM0_HISTORY_DB_PATH", "/var/lib/mem0/history.db") + config["history_db_path"] = history_db_path + + return config + + +# Initialize Memory instance +try: + config = get_config_from_env() + logging.info(f"Initializing mem0 with config: {config}") + + # Validate API key is set for OpenAI provider + if config.get("llm", {}).get("provider") == "openai": + if not config.get("llm", {}).get("config", {}).get("api_key"): + logging.error("OPENAI_API_KEY environment variable is required but not set.") + logging.error("Please set OPENAI_API_KEY environment variable or configure apiKeyFile in NixOS module.") + sys.exit(1) + + MEMORY_INSTANCE = Memory.from_config(config) + logging.info("Memory instance initialized successfully") +except Exception as e: + logging.error(f"Failed to initialize Memory: {e}") + logging.error("Please check your configuration and ensure all required services are running.") + sys.exit(1) + + +app = FastAPI( + title="Mem0 REST API", + description="A REST API for managing and searching memories for your AI Agents and Apps.", + version="1.0.0", +) + + +class Message(BaseModel): + role: str = Field(..., description="Role of the message (user or assistant).") + content: str = Field(..., description="Message content.") + + +class MemoryCreate(BaseModel): + messages: List[Message] = Field(..., description="List of messages to store.") + user_id: Optional[str] = None + agent_id: Optional[str] = None + run_id: Optional[str] = None + metadata: Optional[Dict[str, Any]] = None + + +class SearchRequest(BaseModel): + query: str = Field(..., description="Search query.") + user_id: Optional[str] = None + run_id: Optional[str] = None + agent_id: Optional[str] = None + filters: Optional[Dict[str, Any]] = None + + +@app.get("/", summary="Redirect to documentation", include_in_schema=False) +def home(): + """Redirect to the OpenAPI documentation.""" + return RedirectResponse(url="/docs") + + +@app.get("/health", summary="Health check") +def health(): + """Check if the server is running.""" + return {"status": "healthy", "service": "mem0-api"} + + +@app.post("/configure", summary="Configure Mem0") +def set_config(config: Dict[str, Any]): + """Set memory configuration.""" + global MEMORY_INSTANCE + MEMORY_INSTANCE = Memory.from_config(config) + return {"message": "Configuration set successfully"} + + +@app.post("/memories", summary="Create memories") +def add_memory(memory_create: MemoryCreate): + """Store new memories.""" + if not any([memory_create.user_id, memory_create.agent_id, memory_create.run_id]): + raise HTTPException(status_code=400, detail="At least one identifier (user_id, agent_id, run_id) is required.") + + params = {k: v for k, v in memory_create.model_dump().items() if v is not None and k != "messages"} + try: + response = MEMORY_INSTANCE.add(messages=[m.model_dump() for m in memory_create.messages], **params) + return JSONResponse(content=response) + except Exception as e: + logging.exception("Error in add_memory:") + raise HTTPException(status_code=500, detail=str(e)) + + +@app.get("/memories", summary="Get memories") +def get_all_memories( + user_id: Optional[str] = None, + run_id: Optional[str] = None, + agent_id: Optional[str] = None, +): + """Retrieve stored memories.""" + if not any([user_id, run_id, agent_id]): + raise HTTPException(status_code=400, detail="At least one identifier is required.") + try: + params = { + k: v for k, v in {"user_id": user_id, "run_id": run_id, "agent_id": agent_id}.items() if v is not None + } + return MEMORY_INSTANCE.get_all(**params) + except Exception as e: + logging.exception("Error in get_all_memories:") + raise HTTPException(status_code=500, detail=str(e)) + + +@app.get("/memories/{memory_id}", summary="Get a memory") +def get_memory(memory_id: str): + """Retrieve a specific memory by ID.""" + try: + return MEMORY_INSTANCE.get(memory_id) + except Exception as e: + logging.exception("Error in get_memory:") + raise HTTPException(status_code=500, detail=str(e)) + + +@app.post("/search", summary="Search memories") +def search_memories(search_req: SearchRequest): + """Search for memories based on a query.""" + try: + params = {k: v for k, v in search_req.model_dump().items() if v is not None and k != "query"} + return MEMORY_INSTANCE.search(query=search_req.query, **params) + except Exception as e: + logging.exception("Error in search_memories:") + raise HTTPException(status_code=500, detail=str(e)) + + +@app.put("/memories/{memory_id}", summary="Update a memory") +def update_memory(memory_id: str, updated_memory: Dict[str, Any]): + """Update an existing memory with new content.""" + try: + return MEMORY_INSTANCE.update(memory_id=memory_id, data=updated_memory) + except Exception as e: + logging.exception("Error in update_memory:") + raise HTTPException(status_code=500, detail=str(e)) + + +@app.get("/memories/{memory_id}/history", summary="Get memory history") +def memory_history(memory_id: str): + """Retrieve memory history.""" + try: + return MEMORY_INSTANCE.history(memory_id=memory_id) + except Exception as e: + logging.exception("Error in memory_history:") + raise HTTPException(status_code=500, detail=str(e)) + + +@app.delete("/memories/{memory_id}", summary="Delete a memory") +def delete_memory(memory_id: str): + """Delete a specific memory by ID.""" + try: + MEMORY_INSTANCE.delete(memory_id=memory_id) + return {"message": "Memory deleted successfully"} + except Exception as e: + logging.exception("Error in delete_memory:") + raise HTTPException(status_code=500, detail=str(e)) + + +@app.delete("/memories", summary="Delete all memories") +def delete_all_memories( + user_id: Optional[str] = None, + run_id: Optional[str] = None, + agent_id: Optional[str] = None, +): + """Delete all memories for a given identifier.""" + if not any([user_id, run_id, agent_id]): + raise HTTPException(status_code=400, detail="At least one identifier is required.") + try: + params = { + k: v for k, v in {"user_id": user_id, "run_id": run_id, "agent_id": agent_id}.items() if v is not None + } + MEMORY_INSTANCE.delete_all(**params) + return {"message": "All relevant memories deleted"} + except Exception as e: + logging.exception("Error in delete_all_memories:") + raise HTTPException(status_code=500, detail=str(e)) + + +@app.post("/reset", summary="Reset all memories") +def reset_memory(): + """Completely reset stored memories.""" + try: + MEMORY_INSTANCE.reset() + return {"message": "All memories reset"} + except Exception as e: + logging.exception("Error in reset_memory:") + raise HTTPException(status_code=500, detail=str(e)) + + +if __name__ == "__main__": + import uvicorn + + host = os.environ.get("MEM0_HOST", "127.0.0.1") + port = int(os.environ.get("MEM0_PORT", "8000")) + workers = int(os.environ.get("MEM0_WORKERS", "1")) + log_level = os.environ.get("MEM0_LOG_LEVEL", "info") + + uvicorn.run(app, host=host, port=port, workers=workers, log_level=log_level)