summaryrefslogtreecommitdiff
path: root/Omni/Dev/Beryllium
diff options
context:
space:
mode:
Diffstat (limited to 'Omni/Dev/Beryllium')
-rw-r--r--Omni/Dev/Beryllium/AVA.md111
-rw-r--r--Omni/Dev/Beryllium/Ava.nix81
-rw-r--r--Omni/Dev/Beryllium/Configuration.nix28
-rw-r--r--Omni/Dev/Beryllium/Hardware.nix1
-rw-r--r--Omni/Dev/Beryllium/Live.nix135
-rwxr-xr-xOmni/Dev/Beryllium/migrate-ava.sh102
6 files changed, 441 insertions, 17 deletions
diff --git a/Omni/Dev/Beryllium/AVA.md b/Omni/Dev/Beryllium/AVA.md
new file mode 100644
index 0000000..620592b
--- /dev/null
+++ b/Omni/Dev/Beryllium/AVA.md
@@ -0,0 +1,111 @@
+# Ava Deployment on Beryllium
+
+Ava runs as a systemd service under the `ava` user.
+
+## Architecture
+
+```
+/home/ava/ # Ava's home directory (AVA_DATA_ROOT)
+├── omni/ # Clone of the omni repo
+├── skills/ # Ava's skills directory
+│ ├── shared/ # Skills available to all users
+│ └── <username>/ # User-specific skills
+├── outreach/ # Outreach approval queue
+│ ├── pending/
+│ ├── approved/
+│ ├── rejected/
+│ └── sent/
+├── users/ # Per-user scratch space
+│ └── <username>/
+└── .local/share/omni/
+ └── memory.db # SQLite memory database
+```
+
+## Configuration
+
+The service is configured in `Ava.nix` and requires these environment variables in `/run/secrets/ava.env`:
+
+```bash
+TELEGRAM_BOT_TOKEN=xxx
+OPENROUTER_API_KEY=xxx
+KAGI_API_KEY=xxx # optional
+ALLOWED_TELEGRAM_USER_IDS=xxx,yyy # or * for all
+```
+
+## Commands
+
+```bash
+# View logs
+journalctl -u ava -f
+
+# Restart service
+sudo systemctl restart ava
+
+# Check status
+sudo systemctl status ava
+
+# Stop/Start
+sudo systemctl stop ava
+sudo systemctl start ava
+```
+
+## SSH Access
+
+The Ava private key is at `~/.ssh/ava_ed25519`. Use it to SSH as ava:
+
+```bash
+ssh -i ~/.ssh/ava_ed25519 ava@beryl.bensima.com
+```
+
+Ben can also access ava's workspace via his own SSH key since ava is in the git group.
+
+## Git Setup
+
+Ava has its own clone of the omni repo at `/home/ava/omni`. To fetch changes from ben:
+
+```bash
+# As ava:
+cd /home/ava/omni
+git fetch origin
+git pull origin main
+```
+
+Ben can also push directly to ava's repo if needed:
+
+```bash
+# From /home/ben/omni:
+git remote add ava /home/ava/omni
+git push ava main
+```
+
+## Redeploy
+
+To redeploy Ava with code changes:
+
+```bash
+# 1. Rebuild the NixOS config
+push.sh Omni/Dev/Beryllium.nix
+
+# 2. Or just restart the service if only env changes
+sudo systemctl restart ava
+```
+
+## Migration from tmux
+
+If migrating from the old tmux-based deployment:
+
+1. Deploy the NixOS config with the new ava user
+2. Run the migration script: `sudo ./Omni/Dev/Beryllium/migrate-ava.sh`
+3. Create `/run/secrets/ava.env` with the required secrets
+4. Stop the tmux ava process
+5. Start the systemd service: `sudo systemctl start ava`
+6. Enable on boot: `sudo systemctl enable ava`
+
+## Environment Variable: AVA_DATA_ROOT
+
+The `AVA_DATA_ROOT` environment variable controls where Ava stores its data:
+
+- **Development** (unset): Uses `_/var/ava/` (relative to repo)
+- **Production**: Set to `/home/ava` via the systemd service
+
+This allows the same codebase to run in both environments without changes.
diff --git a/Omni/Dev/Beryllium/Ava.nix b/Omni/Dev/Beryllium/Ava.nix
new file mode 100644
index 0000000..f0765cd
--- /dev/null
+++ b/Omni/Dev/Beryllium/Ava.nix
@@ -0,0 +1,81 @@
+{...}: let
+ bild = import ../../Bild.nix {};
+ avaPkg = bild.run ../../Ava.hs;
+
+ # Python environment for Ava's python_exec tool
+ avaPython = bild.python.pythonWith (p: [
+ p.requests # HTTP requests
+ p.beautifulsoup4 # HTML/XML parsing
+ p.lxml # Fast XML/HTML parser
+ p.pandas # Data analysis
+ p.numpy # Numerical computing
+ p.pyyaml # YAML parsing
+ p.python-dateutil # Date utilities
+ ]);
+
+ # Wrap ava binary with tools in PATH
+ avaWithTools = bild.stdenv.mkDerivation {
+ name = "ava-wrapped";
+ buildInputs = [bild.makeWrapper];
+ phases = ["installPhase"];
+ installPhase = ''
+ mkdir -p $out/bin
+ makeWrapper ${avaPkg}/bin/ava $out/bin/ava \
+ --prefix PATH : ${bild.lib.makeBinPath [
+ avaPython
+ bild.pkgs.jq
+ bild.pkgs.ripgrep
+ bild.pkgs.coreutils
+ bild.pkgs.git
+ bild.pkgs.sqlite
+ ]}
+ '';
+ };
+in {
+ systemd.services.ava = {
+ description = "Ava Telegram assistant";
+ after = ["network-online.target" "ollama.service"];
+ wants = ["network-online.target" "ollama.service"];
+ wantedBy = ["multi-user.target"];
+
+ serviceConfig = {
+ Type = "simple";
+ User = "ava";
+ Group = "users";
+ WorkingDirectory = "/home/ava/omni";
+
+ Environment = [
+ "AVA_DATA_ROOT=/home/ava"
+ "HOME=/home/ava"
+ "OLLAMA_URL=http://localhost:11434"
+ ];
+
+ EnvironmentFile = "/run/secrets/ava.env";
+
+ ExecStart = "${avaWithTools}/bin/ava";
+
+ StandardOutput = "journal";
+ StandardError = "journal";
+
+ Restart = "on-failure";
+ RestartSec = 5;
+
+ TimeoutStopSec = 90;
+ KillMode = "mixed";
+ KillSignal = "SIGTERM";
+ };
+ };
+
+ systemd.tmpfiles.rules = [
+ "d /home/ava 0755 ava users -"
+ "d /home/ava/omni 0755 ava users -"
+ "d /home/ava/skills 0755 ava users -"
+ "d /home/ava/outreach 0755 ava users -"
+ "d /home/ava/outreach/pending 0755 ava users -"
+ "d /home/ava/outreach/approved 0755 ava users -"
+ "d /home/ava/outreach/rejected 0755 ava users -"
+ "d /home/ava/outreach/sent 0755 ava users -"
+ "d /home/ava/users 0755 ava users -"
+ "d /home/ava/.local/share/omni 0755 ava users -"
+ ];
+}
diff --git a/Omni/Dev/Beryllium/Configuration.nix b/Omni/Dev/Beryllium/Configuration.nix
index 8fa783b..3e39fe4 100644
--- a/Omni/Dev/Beryllium/Configuration.nix
+++ b/Omni/Dev/Beryllium/Configuration.nix
@@ -14,9 +14,6 @@ in {
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
- boot.kernelModules = ["v4l2loopback"];
- boot.extraModulePackages = [pkgs.linuxPackages.v4l2loopback];
-
# Enable networking
networking.networkmanager.enable = true;
@@ -52,24 +49,21 @@ in {
services.xserver = {
layout = "us";
xkbVariant = "";
+ extraConfig = ''
+ Section "InputClass"
+ Identifier "Kensington Expert Mouse"
+ MatchProduct "Kensington Expert Mouse"
+ Option "ButtonMapping" "1 8 2 4 5 6 7 3 9"
+ Option "NaturalScrolling" "true"
+ Option "ScrollMethod" "button"
+ Option "ScrollButton" "3"
+ EndSection
+ '';
};
# Enable CUPS to print documents.
services.printing.enable = true;
- # Enable sound with pipewire.
- hardware.pulseaudio.enable = false;
- security.rtkit.enable = true;
- services.pipewire = {
- enable = true;
- alsa.enable = true;
- alsa.support32Bit = true;
- pulse.enable = true;
- # If you want to use JACK applications, uncomment this
- jack.enable = true;
- wireplumber.enable = true;
- };
-
hardware.opengl.enable = true;
hardware.opengl.driSupport32Bit = true;
services.xserver.videoDrivers = ["nvidia"];
@@ -100,6 +94,8 @@ in {
services.eternal-terminal.enable = true;
+ services.pcscd.enable = true;
+
environment.systemPackages = with pkgs; [
v4l-utils
linuxPackages.v4l2loopback
diff --git a/Omni/Dev/Beryllium/Hardware.nix b/Omni/Dev/Beryllium/Hardware.nix
index 5a8b583..c9632f5 100644
--- a/Omni/Dev/Beryllium/Hardware.nix
+++ b/Omni/Dev/Beryllium/Hardware.nix
@@ -12,7 +12,6 @@
boot.initrd.availableKernelModules = ["xhci_pci" "ahci" "nvme" "usbhid" "usb_storage" "sd_mod"];
boot.initrd.kernelModules = [];
boot.kernelModules = ["kvm-amd"];
- boot.extraModulePackages = [];
fileSystems."/" = {
device = "/dev/disk/by-uuid/f96eaa16-d0e2-4230-aece-131ce7b630da";
diff --git a/Omni/Dev/Beryllium/Live.nix b/Omni/Dev/Beryllium/Live.nix
new file mode 100644
index 0000000..a44452f
--- /dev/null
+++ b/Omni/Dev/Beryllium/Live.nix
@@ -0,0 +1,135 @@
+{pkgs, ...}: {
+ programs.obs-studio = {
+ enable = true;
+ enableVirtualCamera = true;
+ plugins = with pkgs.obs-studio-plugins; [
+ obs-pipewire-audio-capture
+ ];
+ };
+
+ # Enable sound with pipewire.
+ hardware.pulseaudio.enable = false;
+ security.rtkit.enable = true;
+
+ # Latency optimization
+ boot.kernelParams = ["threadirqs"];
+ boot.kernel.sysctl."vm.swappiness" = 10;
+
+ environment.systemPackages = with pkgs; [
+ obs-do # cli for controlling obs
+ pamixer # cli volume control
+ patchage # another connection manager
+ pwvucontrol # gui for quick adjustments
+ qpwgraph # better than helvum
+ supercollider-with-plugins
+ ];
+
+ # Virtual sinks for routing audio
+ services.pipewire = {
+ enable = true;
+ alsa.enable = true;
+ alsa.support32Bit = true;
+ pulse.enable = true;
+ jack.enable = true;
+ wireplumber.enable = true;
+
+ extraConfig.pipewire = {
+ "10-loopback" = {
+ # loopback my mic into my headphones so i can hear myself, this creates
+ # an auditory space that encourages focus and thinking
+
+ "context.properties" = {
+ "default.clock.rate" = 48000;
+ "default.clock.quantum" = 128; # lower for less latency
+ "default.clock.min-quantum" = 32;
+ "default.clock.max-quantum" = 8192;
+ };
+
+ "context.exec" = [
+ {
+ "path" = "${pkgs.writeShellScript "setup-mic-monitor" ''
+ sleep 1
+ ${pkgs.pipewire}/bin/pw-link \
+ "alsa_input.usb-Antlion_Audio_Antlion_USB_Microphone-00.pro-input-0:capture_AUX0" \
+ "input.mic-monitor:input_FL"
+
+ ${pkgs.pipewire}/bin/pw-link \
+ "alsa_input.usb-Antlion_Audio_Antlion_USB_Microphone-00.pro-input-0:capture_AUX0" \
+ "input.mic-monitor:input_FR"
+ ''}";
+ }
+ ];
+
+ "context.modules" = [
+ {
+ name = "libpipewire-module-loopback";
+ args = {
+ "node.name" = "mic-monitor";
+ "node.description" = "Microphone Monitor";
+ "capture.props" = {
+ "target.object" = "alsa_input.usb-Antlion_Audio_Antlion_USB_Microphone-00.pro-input-0";
+ "channelmix.normalize" = true;
+ "audio.channels" = 1;
+ "audio.position" = ["FR" "FL"];
+ };
+ "playback.props" = {
+ "target.object" = "alsa_output.usb-Focusrite_Scarlett_Solo_USB-00.HiFi__Line1__sink";
+ "node.passive" = true;
+ "channelmix.normalize" = true;
+ "audio.channels" = 2;
+ "audio.position" = ["FR" "FL"];
+ };
+ };
+ }
+ ];
+ };
+
+ "10-combined" = {
+ "context.modules" = [
+ {
+ name = "libpipewire-module-loopback";
+ args = {
+ "node.name" = "combined-audio";
+ "node.description" = "Combined Mic+Desktop Audio";
+ "capture.props" = {
+ "media.class" = "Audio/Sink";
+ "audio.class" = 2;
+ "audio.position" = ["FL" "FR"];
+ "channelmix.normalize" = true;
+ };
+ "playback.props" = {
+ "media.class" = "Audio/Source";
+ "audio.channels" = 2;
+ "audio.position" = ["FL" "FR"];
+ "channelmix.normalize" = true;
+ };
+ };
+ }
+ ];
+
+ "context.exec" = [
+ {
+ "path" = "${pkgs.writeShellScript "setup-audio-routing" ''
+ sleep 1
+ ${pkgs.pipewire}/bin/pw-link \
+ "alsa_input.usb-Antlion_Audio_Antlion_USB_Microphone-00.pro-input-0:capture_AUX0" \
+ "input.combined-audio:playback_FL"
+
+ ${pkgs.pipewire}/bin/pw-link \
+ "alsa_input.usb-Antlion_Audio_Antlion_USB_Microphone-00.pro-input-0:capture_AUX0" \
+ "input.combined-audio:playback_FR"
+
+ ${pkgs.pipewire}/bin/pw-link \
+ "input.combined-audio:monitor_FL" \
+ "alsa_output.usb-Focusrite_Scarlett_Solo_USB-00.HiFi__Line1__sink:playback_FL"
+
+ ${pkgs.pipewire}/bin/pw-link \
+ "input.combined-audio:monitor_FR" \
+ "alsa_output.usb-Focusrite_Scarlett_Solo_USB-00.HiFi__Line1__sink:playback_FR"
+ ''}";
+ }
+ ];
+ };
+ };
+ };
+}
diff --git a/Omni/Dev/Beryllium/migrate-ava.sh b/Omni/Dev/Beryllium/migrate-ava.sh
new file mode 100755
index 0000000..91d2740
--- /dev/null
+++ b/Omni/Dev/Beryllium/migrate-ava.sh
@@ -0,0 +1,102 @@
+#!/usr/bin/env bash
+# Migration script: move Ava data from _/var/ava to /home/ava
+#
+# Run this ONCE after deploying the NixOS config with the new ava user.
+#
+# Usage:
+# sudo ./migrate-ava.sh
+#
+# This script:
+# 1. Copies existing data from _/var/ava to /home/ava
+# 2. Copies memory.db from ben's .local to ava's .local
+# 3. Clones the omni repo into /home/ava/omni
+# 4. Sets proper ownership
+
+set -euo pipefail
+
+GRN='\033[0;32m'
+YLW='\033[0;33m'
+RED='\033[0;31m'
+NC='\033[0m'
+
+OMNI_REPO="/home/ben/omni"
+AVA_HOME="/home/ava"
+OLD_DATA_ROOT="$OMNI_REPO/_/var/ava"
+
+echo -e "${YLW}=== Ava Migration Script ===${NC}"
+
+# Check we're running as root
+if [[ $EUID -ne 0 ]]; then
+ echo -e "${RED}Error: This script must be run as root${NC}"
+ exit 1
+fi
+
+# Check ava user exists
+if ! id ava &>/dev/null; then
+ echo -e "${RED}Error: ava user does not exist. Deploy the NixOS config first.${NC}"
+ exit 1
+fi
+
+# Create directory structure (tmpfiles should handle this, but just in case)
+echo -e "${YLW}Creating directory structure...${NC}"
+mkdir -p "$AVA_HOME"/{skills,outreach,users,omni,.local/share/omni}
+mkdir -p "$AVA_HOME"/outreach/{pending,approved,rejected,sent}
+
+# Copy skills
+if [[ -d "$OLD_DATA_ROOT/skills" ]]; then
+ echo -e "${YLW}Copying skills...${NC}"
+ rsync -av --progress "$OLD_DATA_ROOT/skills/" "$AVA_HOME/skills/"
+else
+ echo -e "${YLW}No skills to migrate${NC}"
+fi
+
+# Copy outreach
+if [[ -d "$OLD_DATA_ROOT/outreach" ]]; then
+ echo -e "${YLW}Copying outreach data...${NC}"
+ rsync -av --progress "$OLD_DATA_ROOT/outreach/" "$AVA_HOME/outreach/"
+else
+ echo -e "${YLW}No outreach data to migrate${NC}"
+fi
+
+# Copy memory.db if it exists
+BEN_MEMORY="/home/ben/.local/share/omni/memory.db"
+AVA_MEMORY="$AVA_HOME/.local/share/omni/memory.db"
+if [[ -f "$BEN_MEMORY" ]]; then
+ echo -e "${YLW}Copying memory database...${NC}"
+ cp -v "$BEN_MEMORY" "$AVA_MEMORY"
+else
+ echo -e "${YLW}No memory.db found at $BEN_MEMORY${NC}"
+fi
+
+# Clone or update the omni repo
+if [[ -d "$AVA_HOME/omni/.git" ]]; then
+ echo -e "${YLW}Omni repo already exists, updating...${NC}"
+ cd "$AVA_HOME/omni"
+ sudo -u ava git fetch origin
+else
+ echo -e "${YLW}Cloning omni repo...${NC}"
+ sudo -u ava git clone "$OMNI_REPO" "$AVA_HOME/omni"
+fi
+
+# Set ownership
+echo -e "${YLW}Setting ownership...${NC}"
+chown -R ava:users "$AVA_HOME"
+
+# Show summary
+echo ""
+echo -e "${GRN}=== Migration Complete ===${NC}"
+echo ""
+echo "Directory structure:"
+ls -la "$AVA_HOME"
+echo ""
+echo "Next steps:"
+echo "1. Create /run/secrets/ava.env with:"
+echo " TELEGRAM_BOT_TOKEN=xxx"
+echo " OPENROUTER_API_KEY=xxx"
+echo " KAGI_API_KEY=xxx (optional)"
+echo " ALLOWED_TELEGRAM_USER_IDS=xxx (or * for all)"
+echo ""
+echo "2. Stop the tmux Ava process"
+echo "3. Start the systemd service: sudo systemctl start ava"
+echo "4. Watch logs: journalctl -u ava -f"
+echo "5. Enable on boot: sudo systemctl enable ava"