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)