litten: fix caddy and other minor updates

This commit is contained in:
Brian Lee 2025-06-03 13:01:08 -07:00
parent 7a6ac25c40
commit d314869337
10 changed files with 226 additions and 323 deletions

2
.gitignore vendored
View File

@ -1,6 +1,6 @@
.stfolder
secrets.nix
variables.nix
squirtle.satstack.dev/variables.nix
archive/
glance/config.yaml
result

View File

@ -1,60 +0,0 @@
# Litten
* n8n - [cli commands](https://docs.n8n.io/hosting/cli-commands/#credentials)
## Caddy with namecheap-dns
Some special handling is necessary for Caddy
```sh
#!/usr/bin/env bash
# This script demonstrates how to construct a Go pseudo-version for a commit from the
# "caddy-dns/namecheap" repository using curl, jq, and date.
#
# The pseudoversion format is:
# 0.0.0-YYYYMMDDHHMMSS-<short-commit-hash>
#
# Steps:
#
# 1. Query the GitHub API for the commit metadata. (The URL is:
# https://api.github.com/repos/caddy-dns/namecheap/commits/HEAD
# You can replace HEAD with a specific commit SHA if needed.)
#
# 2. Extract the commit date. The GitHub API returns the date in ISO 8601 format, e.g.,
# "2025-02-15T18:47:56Z".
#
# 3. Convert that date to UTC timestamp in the format YYYYMMDDHHMMSS.
#
# 4. Extract the full commit SHA and take its first 12 characters.
#
# 5. Combine these values in the form: "0.0.0-<timestamp>-<short-hash>", resulting in a pseudo-version,
# like: "0.0.0-20250215184756-635a4c34fd25"
#
# Example:
# Given:
# Commit date = "2025-02-15T18:47:56Z"
# Commit SHA = "635a4c34fd25d48df62f8e9d5485ac9026796a83"
# The pseudo-version will be:
# 0.0.0-20250215184756-635a4c34fd25
# Set the commit reference (or use a specific commit SHA instead of "HEAD")
COMMIT_REF="HEAD"
# Fetch commit metadata from GitHub API
COMMIT_JSON=$(curl -s "https://api.github.com/repos/caddy-dns/namecheap/commits/$COMMIT_REF")
# Extract the commit date (author date)
COMMIT_DATE=$(echo "$COMMIT_JSON" | jq -r '.commit.author.date')
# Convert the date to UTC (should already be in UTC if it ends with 'Z') and format it as YYYYMMDDHHMMSS
TIMESTAMP=$(date -u -d "$COMMIT_DATE" +"%Y%m%d%H%M%S")
# Extract the full commit hash and get the first 12 characters
FULL_HASH=$(echo "$COMMIT_JSON" | jq -r '.sha')
SHORT_HASH=${FULL_HASH:0:12}
# Construct the pseudo-version string
PSEUDO_VERSION="0.0.0-${TIMESTAMP}-${SHORT_HASH}"
echo "Pseudo-Version: $PSEUDO_VERSION"
```

View File

@ -1,51 +1,31 @@
{ config, pkgs, lib, ... }:
let
host_name = "litten";
host_fqdn = "${host_name}.brenise.dev";
secrets = import ./secrets.nix;
# Add nixpkgs-unstable channel with the following command:
# nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs-unstable && nix-channel --update
# 09/15/24: Use nixpkgs-unstable for logseq, see https://github.com/NixOS/nixpkgs/pull/340427
unstable = import <nixpkgs-unstable> { config = config.nixpkgs.config; };
# https://github.com/NixOS/nixpkgs/pull/358586
caddyWithPlugins = unstable.caddy.withPlugins {
# commit: 7095083a353829fc83632c34e8988fd8eb72f43d (01/14/2024)
plugins = ["github.com/caddy-dns/namecheap@v0.0.0-20240114194457-7095083a3538"];
hash = "sha256-Wu4j6XBNHX+ckCBmqTMC7ECLnOc3D3IJ04sJF14NHJo="; # WARNING: This will change every update
};
importedVars = import ./variables.nix { inherit config pkgs lib; };
in
with importedVars;
{
# nix.settings.experimental-features = [ "nix-command" "flakes" ];
# nix.shellHook = ''
# if [ -z "$IN_NIX_SHELL" ]; then
# exec fish
# fi
# '';
nixpkgs.config = {
allowUnfreePredicate = pkg:
builtins.elem (lib.getName pkg) [
"claude-code"
# "claude-code"
"obsidian"
"packer"
"reaper"
"n8n"
# "n8n"
"vscode"
"terraform"
"zoom"
"charlatan3"
"steam"
"steam-original"
"steam-unwrapped"
"steam-run"
"tokenizer.json" # goose-cli
# "charlatan3"
# "steam"
# "steam-original"
# "steam-unwrapped"
# "steam-run"
# "tokenizer.json" # goose-cli
];
};
nixpkgs.overlays = [
(self: super: {
n8n = unstable.n8n;
# n8n = unstable.n8n;
ansible = super.ansible.overrideAttrs (oldAttrs: {
propagatedBuildInputs = oldAttrs.propagatedBuildInputs ++ [ super.python312Packages.jmespath ];
@ -138,33 +118,34 @@ in
];
packages = with pkgs; [
# unstable.goose-cli
mariadb
notify-client
gh
solaar
binutils
brave
chromium
coreutils # base64
element-desktop
firefox
haruna
gimp
gnumake
home-manager
jellyfin-media-player
kate # kwrite
kdenlive
libsForQt5.kcalc
kdePackages.kate #kwrite
kdePackages.kdenlive
kdePackages.kcalc
# libsForQt5.kcalc
# unstable.logseq # warning: https://github.com/logseq/logseq/issues/10851#issuecomment-2402925912
# unstable.ghostty
moonlight-qt
nmap
obs-studio
obsidian
pavucontrol
# qbittorrent
# pavucontrol
qbittorrent
rclone
rtorrent
# rtorrent
sq
synergy
# synergy
tenacity
thunderbird
tor-browser
@ -203,14 +184,6 @@ in
];
};
claude = {
isNormalUser = true;
packages = with pkgs; [
unstable.claude-code
unstable.aider-chat
];
};
glance = {
isSystemUser = true;
group = "glance";
@ -226,8 +199,9 @@ in
environment = {
systemPackages = with pkgs; [
# GPU tools
arp-scan
binutils
coreutils
cryptsetup
curl
difftastic
@ -281,15 +255,17 @@ in
}
'';
plasma5.excludePackages = with pkgs.libsForQt5; [
# vscode on Wayland
sessionVariables.NIXOS_OZONE_WL = "1";
# TODO: https://github.com/nix-community/plasma-manager
plasma6.excludePackages = with pkgs.kdePackages; [
plasma-browser-integration
];
etc."gitconfig".text = ''
[init]
defaultBranch = main
'';
# vscode on Wayland
#sessionVariables.NIXOS_OZONE_WL = "1";
};
programs = {
@ -333,7 +309,6 @@ in
enableSSHSupport = true;
};
kdeconnect.enable = true;
steam.enable = true;
chromium = {
enable = true;
extraOpts = {
@ -358,10 +333,6 @@ in
}
];
};
# pki.certificateFiles = [
# "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
# # "/etc/ssl/certs/dotted-turbans.pem"
# ];
};
services = {
@ -376,10 +347,10 @@ in
alsa.enable = true; # for tenacity
# jack.enable = true; # this might be useful for reaper, but enabling it breaks stereo audio for some reason
};
avahi = { # for resolving start9 hostname
enable = true;
nssmdns4 = true; # Enable NSS support for mDNS
};
# avahi = { # for resolving start9 hostname
# enable = true;
# nssmdns4 = true; # Enable NSS support for mDNS
# };
syncthing = {
enable = true;
@ -408,7 +379,8 @@ in
enable = true;
package = caddyWithPlugins;
logFormat = "output discard";
# logFormat = "output discard";
logFormat = "level DEBUG";
extraConfig = let
tlsConfig = ''
tls {
@ -420,9 +392,9 @@ in
}
'';
in ''
${host_fqdn} { # open-webui
${host_fqdn} {
${tlsConfig}
reverse_proxy http://127.0.0.1:8080
reverse_proxy http://127.0.0.1:8034 # open-webui
# handle /files/* {
# root /mnt/usb/
@ -431,29 +403,29 @@ in
}
${host_fqdn}:4430 { # node_exporter
${host_fqdn}:4430 {
${tlsConfig}
reverse_proxy http://127.0.0.1:8030
reverse_proxy http://127.0.0.1:8030 # node_exporter
}
${host_fqdn}:4431 { # litellm
${host_fqdn}:4432 {
${tlsConfig}
reverse_proxy http://127.0.0.1:8031
reverse_proxy http://127.0.0.1:8032 # glance
}
${host_fqdn}:4432 { # glance
${host_fqdn}:4434 {
${tlsConfig}
reverse_proxy http://127.0.0.1:8032
reverse_proxy http://127.0.0.1:8034 # open-webui
}
${host_fqdn}:4433 { # n8n
${host_fqdn}:4435 {
${tlsConfig}
reverse_proxy http://127.0.0.1:8033
reverse_proxy http://127.0.0.1:8035 # mealie
}
${host_fqdn}:4435 { # mealie
${host_fqdn}:4436 {
${tlsConfig}
reverse_proxy http://127.0.0.1:8035
reverse_proxy http://127.0.0.1:8036 # litellm
}
:9999 {
@ -462,24 +434,107 @@ in
'';
};
displayManager = {
sddm.enable = true;
#defaultSession = "plasmawayland";
};
xserver = {
open-webui = {
enable = true;
desktopManager.plasma5.enable = true;
port = 8034;
};
litellm = {
enable = true;
port = 8036;
environmentFile = litellmEnvFile;
settings = {
litellm_settings = {
set_verbose = true;
};
model_list = [
{
model_name = "grok-3";
litellm_params = {
model = "xai/grok-3-latest";
api_key = "os.environ/XAI_API_KEY"; # LiteLLM reads this env var
};
}
{
model_name = "deepseek-r1-distill-llama-70b";
litellm_params = {
model = "groq/deepseek-r1-distill-llama-70b";
api_key = "os.environ/GROK_API_KEY"; # LiteLLM reads this env var
};
}
{
model_name = "claude-3.5-sonnet";
litellm_params = {
model = "anthropic/claude-3-5-sonnet-latest";
api_key = "os.environ/ANTHROPIC_API_KEY"; # LiteLLM reads this env var
};
}
{
model_name = "gemini-2.0-flash";
litellm_params = {
model = "gemini/gemini-2.0-flash-exp";
api_key = "os.environ/GOOGLE_API_KEY"; # LiteLLM reads this env var
safety_settings = [
{ category = "HARM_CATEGORY_HARASSMENT"; threshold = "BLOCK_NONE"; }
{ category = "HARM_CATEGORY_HATE_SPEECH"; threshold = "BLOCK_NONE"; }
{ category = "HARM_CATEGORY_SEXUALLY_EXPLICIT"; threshold = "BLOCK_NONE"; }
{ category = "HARM_CATEGORY_DANGEROUS_CONTENT"; threshold = "BLOCK_NONE"; }
];
};
}
];
};
};
mealie = {
enable = true;
port = 8035;
listenAddress = "127.0.0.1";
package = unstable.mealie;
# listenAddress = "127.0.0.1";
listenAddress = "0.0.0.0";
# package = unstable.mealie;
};
n8n.enable = true;
# n8n.enable = true;
strongswan = {
enable = false;
secrets = [
"/etc/nixos/ipsec.secrets"
];
connections = {
"aws-vpn" = {
type = "tunnel";
authby = "secret";
left = "%defaultroute";
leftsubnet = "0.0.0.0/0";
right = "${secrets.aws-vpn.tunnel-address}";
rightsubnet = "10.0.64.0/24"; # subnet-0f2b39fd2ed6a88d7 "private-lambda-us-west-2d"
ike = "aes256-sha2_256-modp2048!";
esp = "aes256-sha2_256!";
ikelifetime = "8h";
keylife = "1h";
keyingtries = "3";
auto = "start";
installpolicy = "yes"; # Install IPsec policies
routing = "yes"; # Install routes
dpdaction = "restart"; # Restart connection if peer is unreachable
dpddelay = "30s"; # Dead peer detection delay
closeaction = "restart"; # Restart connection if it drops
};
};
setup = {
charondebug = "esp 2, ike 2, cfg 2, net 2, enc 2";
};
};
desktopManager.plasma6.enable = true;
displayManager = {
sddm.enable = true;
sddm.wayland.enable = true;
#defaultSession = "plasmawayland";
};
xserver.enable = true;
};
systemd = {
@ -496,18 +551,6 @@ in
};
};
services.open-webui = {
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
serviceConfig = {
Type = "forking";
User = "blee";
WorkingDirectory = "/opt/open-webui";
Environment = "NIX_PATH=nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos:nixos-config=/etc/nixos/configuration.nix:/nix/var/nix/profiles/per-user/root/channels";
};
script = "${pkgs.nix}/bin/nix-shell";
};
services.ena-vpn = {
description = "ENA Corp VPN";
# wantedBy = [ "multi-user.target" ]; # 2fa required, avoid startup
@ -525,12 +568,17 @@ in
OPENAI_API_KEY = "${secrets.mealie.openai-api-key}";
};
services.n8n.environment = {
N8N_EDITOR_BASE_URL = "https://${host_fqdn}:4433";
N8N_HIRING_BANNER_ENABLED = "false";
N8N_METRICS = "true";
N8N_PORT = "8033";
};
# services.n8n.environment = {
# N8N_EDITOR_BASE_URL = "https://${host_fqdn}:4433";
# N8N_DIAGNOSTICS_ENABLED = "false"; # Also disables "Ask AI" in the code node
# N8N_HIRING_BANNER_ENABLED = "false";
# N8N_METRICS = "true";
# N8N_PORT = "8033";
# };
tmpfiles.rules = [
"d /srv/src 0775 root users - -"
];
};
@ -549,6 +597,12 @@ in
mode = "0600";
};
"ipsec.secrets" = {
text = ''
${secrets.aws-vpn.tunnel-address} : PSK "${secrets.aws-vpn.psk}"
'';
mode = "0600";
};
};
virtualisation.docker = {

View File

@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Directory containing Cursor AppImages
CURSOR_DIR="/home/blee/apps/cursor"
# Find the most recent Cursor AppImage
LATEST_APPIMAGE=$(ls -t "$CURSOR_DIR"/Cursor-*.AppImage | head -n 1)
# Execute the AppImage if found
if [ -n "$LATEST_APPIMAGE" ]; then
exec "$LATEST_APPIMAGE"
else
echo "No Cursor AppImage found in $CURSOR_DIR"
exit 1
fi

View File

@ -1,40 +0,0 @@
# Makefile for managing virtual environments
VENV_OPEN_WEBUI=.venv_open_webui
VENV_LITELLM=.venv_litellm
ACTIVATE_OPEN_WEBUI=$(VENV_OPEN_WEBUI)/bin/activate.fish
ACTIVATE_LITELLM=$(VENV_LITELLM)/bin/activate.fish
.PHONY: install install_open_webui install_litellm update update_open_webui update_litellm
install_open_webui:
@echo "Creating open-webui environment..."
python -m venv $(VENV_OPEN_WEBUI)
source $(ACTIVATE_OPEN_WEBUI) && \
pip install -U pip open-webui && \
deactivate
install_litellm:
@echo "Creating litellm environment..."
python -m venv $(VENV_LITELLM)
source $(ACTIVATE_LITELLM) && \
pip install -U pip litellm[proxy] && \
deactivate
install: install_open_webui install_litellm
@echo "All environments created and packages installed."
update_open_webui:
@echo "Updating open-webui environment..."
source $(ACTIVATE_OPEN_WEBUI) && \
pip install -U open-webui && \
deactivate
update_litellm:
@echo "Updating litellm environment..."
source $(ACTIVATE_LITELLM) && \
pip install -U litellm[proxy] && \
deactivate
update: update_open_webui update_litellm
@echo "All environments updated."

View File

@ -1,24 +0,0 @@
# open-webui
Installation:
```sh
mkdir -p /opt/open-webui
cp -v opt/open-webui/{Makefile,run-open-webui.sh, shell.nix} /opt/open-webui/
cd /opt/open-webui
make install
```
Update:
```sh
cd /opt/open-webui
make update
```
Running:
```sh
cd /opt/open-webui
nix-shell
```

View File

@ -1,54 +0,0 @@
#general_settings: {}
#litellm_settings: {}
litellm_settings:
set_verbose: True
# https://docs.litellm.ai/docs/providers/
model_list:
# https://docs.x.ai/docs/overview#featured-models
- model_name: grok-3
litellm_params:
model: xai/grok-3-latest
api_key: os.environ/XAI_API_KEY
# https://console.groq.com/docs/models
- model_name: deepseek-r1-distill-llama-70b
litellm_params:
model: groq/deepseek-r1-distill-llama-70b
api_key: os.environ/GROK_API_KEY
# https://docs.anthropic.com/en/docs/about-claude/models
- model_name: claude-3.5-sonnet
litellm_params:
model: anthropic/claude-3-5-sonnet-latest
api_key: os.environ/ANTHROPIC_API_KEY
# https://ai.google.dev/gemini-api/docs/models/gemini#gemini-2.0-flash
- model_name: gemini-2.0-flash
litellm_params:
model: gemini/gemini-2.0-flash-exp
api_key: os.environ/GOOGLE_API_KEY
safety_settings:
- category: HARM_CATEGORY_HARASSMENT
threshold: BLOCK_NONE
- category: HARM_CATEGORY_HATE_SPEECH
threshold: BLOCK_NONE
- category: HARM_CATEGORY_SEXUALLY_EXPLICIT
threshold: BLOCK_NONE
- category: HARM_CATEGORY_DANGEROUS_CONTENT
threshold: BLOCK_NONE
# Note: no payment method added yet, identity not disclosed
# https://docs.mistral.ai/getting-started/models/models_overview/
# - model_name: mistral-large
# litellm_params:
# model: mistral/mistral-large-latest
# api_key: os.environ/MISTRAL_API_KEY
#router_settings: {}
#router_settings: {}

View File

@ -1,31 +0,0 @@
#!/usr/bin/env bash
# Exit on error, undefined variables, and print commands
set -e
# Default values (can be overridden by command-line arguments)
SESSION_NAME="${1:-textgen}"
LITELLM_PORT="${2:-8031}"
if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
echo "Session '$SESSION_NAME' already exists. No action taken."
exit 0
fi
# Check if session already exists
if ! tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
# Create new detached session
tmux new-session -d -s "$SESSION_NAME"
# Start the open-webui server in its own virtual environment
tmux send-keys -t "$SESSION_NAME" "source .venv_open_webui/bin/activate.fish && open-webui serve" C-m
# Start litellm in a new pane with its own virtual environment
tmux split-window -v -t "$SESSION_NAME"
tmux send-keys -t "$SESSION_NAME" "source .venv_litellm/bin/activate.fish && source .env && litellm --telemetry False --config ./litellm.yaml --host 127.0.0.1 --port $LITELLM_PORT" C-m
echo "Session '$SESSION_NAME' created and configured. To connect, type: tmux attach -t $SESSION_NAME"
else
echo "Session '$SESSION_NAME' already exists. Exiting."
exit 1
fi

View File

@ -1,11 +0,0 @@
{ pkgs ? import <nixpkgs> {} }:
(pkgs.buildFHSEnv {
name = "open-webui-fhs-env";
targetPkgs = pkgs: with pkgs; [
tmux
# bash
# python311
];
runScript = "./run-open-webui.sh";
}).env

View File

@ -0,0 +1,54 @@
{ config, pkgs, lib, ... }:
let
host_name = "litten";
host_fqdn = "${host_name}.brenise.dev";
unstable = import <nixpkgs-unstable> { config = config.nixpkgs.config; };
secrets = import ./secrets.nix;
# https://nixos.wiki/wiki/Fish
interactiveShellInit = ''
if [[ $(${pkgs.procps}/bin/ps --no-header --pid=$PPID --format=comm) != "fish" && -z ''${BASH_EXECUTION_STRING} ]]
then
shopt -q login_shell && LOGIN_OPTION='--login' || LOGIN_OPTION=""
exec ${pkgs.fish}/bin/fish $LOGIN_OPTION
fi
'';
# Caddy+namecheap plugin builds fail on v2.10.0+ due to libdns changes: https://github.com/caddy-dns/namecheap/issues/14
# Pin caddy v2.9.1 and go 1.23
pinnedPkgs = fetchTarball {
# This is the commit merged from the caddy: 2.9.1 PR 375655
url = "https://github.com/NixOS/nixpkgs/archive/4ae25041b2c187c0f696b6fc39c196677fb41112.tar.gz";
sha256 = "sha256:12009w2iph89i18bc646y2qhskv6gmq9fmchrcjrw1ibxadnkv4z";
};
caddy_pinned = import pinnedPkgs { config = config.nixpkgs.config; };
caddyWithPlugins = caddy_pinned.caddy.withPlugins {
plugins = ["github.com/caddy-dns/namecheap@v0.0.0-20250228023406-ef9fadb67785"];
hash = "sha256-WDA/qSMwMqLWaohNSCtc/lWFdTJKeONapLconR87cUI="; # This will change every update
};
litellmEnvContent = ''
LITELLM_MASTER_KEY=${secrets.litellmApiKeys.LITELLM_MASTER_KEY}
XAI_API_KEY=${secrets.litellmApiKeys.XAI_API_KEY}
GROK_API_KEY=${secrets.litellmApiKeys.GROK_API_KEY}
ANTHROPIC_API_KEY=${secrets.litellmApiKeys.ANTHROPIC_API_KEY}
GOOGLE_API_KEY=${secrets.litellmApiKeys.GOOGLE_API_KEY}
'';
litellmEnvFile = pkgs.writeText "litellm-env" litellmEnvContent;
in
{
inherit
host_name
host_fqdn
unstable
secrets
interactiveShellInit
litellmEnvContent
litellmEnvFile
caddyWithPlugins;
}