This commit is contained in:
Harald Hoyer 2026-02-24 13:25:42 +01:00
parent 27343e49bd
commit 04150f10d4
32 changed files with 475 additions and 404 deletions

View file

@ -125,7 +125,7 @@
]; ];
outputs-builder = channels: { outputs-builder = channels: {
formatter = channels.nixpkgs.nixfmt-rfc-style; formatter = channels.nixpkgs.nixfmt-tree;
defaultApp = lib.flake-utils-plus.mkApp { drv = channels.nixpkgs.home-manager; }; defaultApp = lib.flake-utils-plus.mkApp { drv = channels.nixpkgs.home-manager; };
}; };

View file

@ -1,6 +1,7 @@
{ pkgs {
, config pkgs,
, ... config,
...
}: }:
{ {
home = { home = {
@ -48,7 +49,10 @@
selection.save_to_clipboard = true; selection.save_to_clipboard = true;
mouse.bindings = [ mouse.bindings = [
{ mouse = "Middle"; action = "Paste"; } {
mouse = "Middle";
action = "Paste";
}
]; ];
# Color theme ported from iTerm 2 Smoooooth # Color theme ported from iTerm 2 Smoooooth

View file

@ -1,5 +1,6 @@
{ config {
, ... config,
...
}: }:
{ {
home = { home = {

View file

@ -1,7 +1,8 @@
{ lib {
, pkgs lib,
, config pkgs,
, ... config,
...
}: }:
{ {
home.sessionPath = [ "$HOME/bin" ]; home.sessionPath = [ "$HOME/bin" ];

View file

@ -1,5 +1,6 @@
{ config {
, ... config,
...
}: }:
{ {
home = { home = {

View file

@ -89,11 +89,13 @@ rec {
]; ];
"capture.props" = { "capture.props" = {
"node.target" = from; "node.target" = from;
} // (args."capture.props" or { }); }
// (args."capture.props" or { });
"playback.props" = { "playback.props" = {
"node.target" = to; "node.target" = to;
"monitor.channel-volumes" = true; "monitor.channel-volumes" = true;
} // (args."playback.props" or { }); }
// (args."playback.props" or { });
}; };
}; };
} }

View file

@ -1,7 +1,8 @@
{ config {
, lib config,
, pkgs lib,
, ... pkgs,
...
}: }:
with lib; with lib;
with lib.metacfg; with lib.metacfg;

View file

@ -1,8 +1,9 @@
{ options {
, config options,
, pkgs config,
, lib pkgs,
, ... lib,
...
}: }:
with lib; with lib;
@ -23,14 +24,13 @@ in
}; };
fonts = { fonts = {
packages = packages = [
[ pkgs.nerd-fonts.hack
pkgs.nerd-fonts.hack pkgs.nerd-fonts.fira-code
pkgs.nerd-fonts.fira-code pkgs.nerd-fonts.droid-sans-mono
pkgs.nerd-fonts.droid-sans-mono pkgs.nerd-fonts.jetbrains-mono
pkgs.nerd-fonts.jetbrains-mono ]
] ++ cfg.fonts;
++ cfg.fonts;
}; };
}; };
} }

View file

@ -1,7 +1,8 @@
{ lib {
, config lib,
, pkgs config,
, ... pkgs,
...
}: }:
let let
inherit (lib) mkEnableOption mkIf; inherit (lib) mkEnableOption mkIf;

View file

@ -1,6 +1,7 @@
{ lib {
, config lib,
, ... config,
...
}: }:
with lib; with lib;
with lib.metacfg; with lib.metacfg;

View file

@ -1,7 +1,8 @@
{ lib {
, config lib,
, pkgs config,
, ... pkgs,
...
}: }:
let let

View file

@ -1,7 +1,8 @@
{ lib {
, config lib,
, pkgs config,
, ... pkgs,
...
}: }:
let let
inherit (lib) mkEnableOption mkIf; inherit (lib) mkEnableOption mkIf;

View file

@ -1,8 +1,9 @@
{ options {
, config options,
, lib config,
, pkgs lib,
, ... pkgs,
...
}: }:
with lib; with lib;

View file

@ -1,7 +1,8 @@
{ config {
, lib config,
, pkgs lib,
, ... pkgs,
...
}: }:
with lib; with lib;
with lib.metacfg; with lib.metacfg;

View file

@ -1,6 +1,7 @@
{ config {
, lib config,
, ... lib,
...
}: }:
with lib; with lib;
with lib.metacfg; with lib.metacfg;

View file

@ -101,6 +101,7 @@ in
uid = 1000; uid = 1000;
extraGroups = [ "wheel" ] ++ cfg.extraGroups; extraGroups = [ "wheel" ] ++ cfg.extraGroups;
} // cfg.extraOptions; }
// cfg.extraOptions;
}; };
} }

View file

@ -1,3 +1,4 @@
{ channels, ... }: final: prev: { { channels, ... }:
final: prev: {
# inherit (channels.nixpkgs.nixsgx) sgx-psw; # inherit (channels.nixpkgs.nixsgx) sgx-psw;
} }

View file

@ -1,14 +1,15 @@
{ lib {
, stdenv lib,
, python312 stdenv,
, fetchFromGitHub python312,
, fetchurl fetchFromGitHub,
, pkg-config fetchurl,
, gitMinimal pkg-config,
, portaudio gitMinimal,
, playwright-driver portaudio,
, pkgs playwright-driver,
, tree-sitter-grammars pkgs,
tree-sitter-grammars,
}: }:
let let
@ -32,19 +33,25 @@ let
typing-extensions typing-extensions
]; ];
nativeBuildInputs = with pkgs; with pkgs.tree-sitter-grammars; [ nativeBuildInputs =
tree-sitter with pkgs;
tree-sitter-c-sharp with pkgs.tree-sitter-grammars;
tree-sitter-embedded-template [
tree-sitter-yaml tree-sitter
]; tree-sitter-c-sharp
tree-sitter-embedded-template
tree-sitter-yaml
];
propagatedBuildInputs = with python312.pkgs; with pkgs.tree-sitter-grammars; [ propagatedBuildInputs =
tree-sitter with python312.pkgs;
tree-sitter-c-sharp with pkgs.tree-sitter-grammars;
tree-sitter-embedded-template [
tree-sitter-yaml tree-sitter
]; tree-sitter-c-sharp
tree-sitter-embedded-template
tree-sitter-yaml
];
nativeCheckInputs = [ python312.pkgs.pytestCheckHook ]; nativeCheckInputs = [ python312.pkgs.pytestCheckHook ];
# Without cd $out, tests fail to import the compiled cython extensions. # Without cd $out, tests fail to import the compiled cython extensions.
@ -183,29 +190,28 @@ let
"tests/help/test_help.py" "tests/help/test_help.py"
]; ];
disabledTests = disabledTests = [
[ # Tests require network
# Tests require network "test_urls"
"test_urls" "test_get_commit_message_with_custom_prompt"
"test_get_commit_message_with_custom_prompt" # FileNotFoundError
# FileNotFoundError "test_get_commit_message"
"test_get_commit_message" # Expected 'launch_gui' to have been called once
# Expected 'launch_gui' to have been called once "test_browser_flag_imports_streamlit"
"test_browser_flag_imports_streamlit" # AttributeError
# AttributeError "test_simple_send_with_retries"
"test_simple_send_with_retries" # Expected 'check_version' to have been called once
# Expected 'check_version' to have been called once "test_main_exit_calls_version_check"
"test_main_exit_calls_version_check" # AssertionError: assert 2 == 1
# AssertionError: assert 2 == 1 "test_simple_send_non_retryable_error"
"test_simple_send_non_retryable_error" ]
] ++ lib.optionals stdenv.hostPlatform.isDarwin [
++ lib.optionals stdenv.hostPlatform.isDarwin [ # Tests fails on darwin
# Tests fails on darwin "test_dark_mode_sets_code_theme"
"test_dark_mode_sets_code_theme" "test_default_env_file_sets_automatic_variable"
"test_default_env_file_sets_automatic_variable" # FileNotFoundError: [Errno 2] No such file or directory: 'vim'
# FileNotFoundError: [Errno 2] No such file or directory: 'vim' "test_pipe_editor"
"test_pipe_editor" ];
];
makeWrapperArgs = [ makeWrapperArgs = [
"--set AIDER_CHECK_UPDATE false" "--set AIDER_CHECK_UPDATE false"
@ -228,10 +234,11 @@ let
passthru = { passthru = {
withPlaywright = aider-chat.overridePythonAttrs ( withPlaywright = aider-chat.overridePythonAttrs (
{ dependencies {
, makeWrapperArgs dependencies,
, propagatedBuildInputs ? [ ] makeWrapperArgs,
, ... propagatedBuildInputs ? [ ],
...
}: }:
{ {
dependencies = dependencies ++ aider-chat.optional-dependencies.playwright; dependencies = dependencies ++ aider-chat.optional-dependencies.playwright;

View file

@ -1,15 +1,15 @@
{ lib {
, stdenv lib,
, fetchFromGitHub stdenv,
, fetchurl fetchFromGitHub,
, rustPlatform fetchurl,
, dbus rustPlatform,
, xorg dbus,
, pkg-config xorg,
, writableTmpDirAsHomeHook pkg-config,
, nix-update-script writableTmpDirAsHomeHook,
, llvmPackages nix-update-script,
, llvmPackages,
}: }:
let let
version = "1.0.20"; version = "1.0.20";
@ -55,20 +55,19 @@ rustPlatform.buildRustPackage rec {
__darwinAllowLocalNetworking = true; __darwinAllowLocalNetworking = true;
checkFlags = checkFlags = [
[ # need dbus-daemon
# need dbus-daemon "--skip=config::base::tests::test_multiple_secrets"
"--skip=config::base::tests::test_multiple_secrets" "--skip=config::base::tests::test_secret_management"
"--skip=config::base::tests::test_secret_management" "--skip=logging::tests::test_log_file_name::with_session_name_and_error_capture"
"--skip=logging::tests::test_log_file_name::with_session_name_and_error_capture" # Observer should be Some with both init project keys set
# Observer should be Some with both init project keys set "--skip=tracing::langfuse_layer::tests::test_create_langfuse_observer"
"--skip=tracing::langfuse_layer::tests::test_create_langfuse_observer" "--skip=providers::gcpauth::tests::test_token_refresh_race_condition"
"--skip=providers::gcpauth::tests::test_token_refresh_race_condition" # Lazy instance has previously been poisoned
# Lazy instance has previously been poisoned "--skip=jetbrains::tests::test_capabilities"
"--skip=jetbrains::tests::test_capabilities" "--skip=jetbrains::tests::test_router_creation"
"--skip=jetbrains::tests::test_router_creation" "--skip=developer::tests::test_text_editor_write_and_view_file"
"--skip=developer::tests::test_text_editor_write_and_view_file" ];
];
passthru.updateScript = nix-update-script { }; passthru.updateScript = nix-update-script { };

View file

@ -1,7 +1,7 @@
{ lib {
, vscode-utils lib,
, vscode-extensions-update-script vscode-utils,
, vscode-extensions-update-script,
}: }:
vscode-utils.buildVscodeMarketplaceExtension { vscode-utils.buildVscodeMarketplaceExtension {

View file

@ -13,7 +13,9 @@ let
inherit (lib) mapAttrsToList concatStringsSep; inherit (lib) mapAttrsToList concatStringsSep;
inherit (lib.metacfg) override-meta; inherit (lib.metacfg) override-meta;
formatted-hosts = mapAttrsToList (name: host: "${name},${host.pkgs.stdenv.hostPlatform.system}") hosts; formatted-hosts = mapAttrsToList (
name: host: "${name},${host.pkgs.stdenv.hostPlatform.system}"
) hosts;
hosts-csv = writeText "hosts.csv" '' hosts-csv = writeText "hosts.csv" ''
Name,System Name,System

View file

@ -23,7 +23,10 @@ rustPlatform.buildRustPackage rec {
doCheck = false; doCheck = false;
nativeBuildInputs = [ pkg-config ]; nativeBuildInputs = [ pkg-config ];
buildInputs = [ openssl sqlite ]; buildInputs = [
openssl
sqlite
];
meta = with lib; { meta = with lib; {
description = "Lightweight autonomous AI assistant infrastructure built in Rust"; description = "Lightweight autonomous AI assistant infrastructure built in Rust";

View file

@ -1,30 +1,44 @@
# Do not modify this file! It was generated by nixos-generate-config # Do not modify this file! It was generated by nixos-generate-config
# and may be overwritten by future invocations. Please make changes # and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead. # to /etc/nixos/configuration.nix instead.
{ config, lib, pkgs, modulesPath, ... }: {
config,
lib,
pkgs,
modulesPath,
...
}:
{ {
imports = [ ]; imports = [ ];
boot.initrd.availableKernelModules = [ "virtio_pci" "xhci_pci" "usbhid" "usb_storage" ]; boot.initrd.availableKernelModules = [
"virtio_pci"
"xhci_pci"
"usbhid"
"usb_storage"
];
boot.initrd.kernelModules = [ ]; boot.initrd.kernelModules = [ ];
boot.kernelModules = [ ]; boot.kernelModules = [ ];
boot.extraModulePackages = [ ]; boot.extraModulePackages = [ ];
fileSystems."/" = fileSystems."/" = {
{ device = "/dev/disk/by-uuid/069a5103-100e-4ff0-9f25-58df709cfd4e"; device = "/dev/disk/by-uuid/069a5103-100e-4ff0-9f25-58df709cfd4e";
fsType = "ext4"; fsType = "ext4";
}; };
fileSystems."/boot" = fileSystems."/boot" = {
{ device = "/dev/disk/by-uuid/8C85-EB75"; device = "/dev/disk/by-uuid/8C85-EB75";
fsType = "vfat"; fsType = "vfat";
options = [ "fmask=0077" "dmask=0077" ]; options = [
}; "fmask=0077"
"dmask=0077"
swapDevices =
[ { device = "/dev/disk/by-uuid/08bbaeb8-8610-4592-9393-938c45ec4d19"; }
]; ];
};
swapDevices = [
{ device = "/dev/disk/by-uuid/08bbaeb8-8610-4592-9393-938c45ec4d19"; }
];
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking # Enables DHCP on each ethernet and wireless interface. In case of scripted networking
# (the default) this is the recommended approach. When using systemd-networkd it's # (the default) this is the recommended approach. When using systemd-networkd it's

View file

@ -1,30 +1,44 @@
# Do not modify this file! It was generated by nixos-generate-config # Do not modify this file! It was generated by nixos-generate-config
# and may be overwritten by future invocations. Please make changes # and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead. # to /etc/nixos/configuration.nix instead.
{ config, lib, pkgs, modulesPath, ... }: {
config,
lib,
pkgs,
modulesPath,
...
}:
{ {
imports = [ ]; imports = [ ];
boot.initrd.availableKernelModules = [ "virtio_pci" "xhci_pci" "usbhid" "usb_storage" ]; boot.initrd.availableKernelModules = [
"virtio_pci"
"xhci_pci"
"usbhid"
"usb_storage"
];
boot.initrd.kernelModules = [ ]; boot.initrd.kernelModules = [ ];
boot.kernelModules = [ ]; boot.kernelModules = [ ];
boot.extraModulePackages = [ ]; boot.extraModulePackages = [ ];
fileSystems."/" = fileSystems."/" = {
{ device = "/dev/disk/by-uuid/3d6d27a9-1840-45cb-8f6f-88958f8e2f4d"; device = "/dev/disk/by-uuid/3d6d27a9-1840-45cb-8f6f-88958f8e2f4d";
fsType = "ext4"; fsType = "ext4";
}; };
fileSystems."/boot" = fileSystems."/boot" = {
{ device = "/dev/disk/by-uuid/0313-3BFB"; device = "/dev/disk/by-uuid/0313-3BFB";
fsType = "vfat"; fsType = "vfat";
options = [ "fmask=0077" "dmask=0077" ]; options = [
}; "fmask=0077"
"dmask=0077"
swapDevices =
[ { device = "/dev/disk/by-uuid/4bfc9e4e-0878-4fd3-878f-950fb4ba6ae8"; }
]; ];
};
swapDevices = [
{ device = "/dev/disk/by-uuid/4bfc9e4e-0878-4fd3-878f-950fb4ba6ae8"; }
];
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking # Enables DHCP on each ethernet and wireless interface. In case of scripted networking
# (the default) this is the recommended approach. When using systemd-networkd it's # (the default) this is the recommended approach. When using systemd-networkd it's

View file

@ -4,12 +4,12 @@
whisper-cpp = { whisper-cpp = {
package = pkgs.wyoming-whisper-rs; package = pkgs.wyoming-whisper-rs;
servers.main = { servers.main = {
enable = true; enable = true;
model = "/var/lib/wyoming/whisper-cpp/ggml-large-v3.bin"; model = "/var/lib/wyoming/whisper-cpp/ggml-large-v3.bin";
uri = "tcp://0.0.0.0:10300"; uri = "tcp://0.0.0.0:10300";
language = "de"; language = "de";
}; };
}; };
piper.servers."main" = { piper.servers."main" = {
enable = true; enable = true;

View file

@ -10,58 +10,58 @@
metacfg.services.acmeBase.credentialsFile = config.sops.secrets.internetbs.path; metacfg.services.acmeBase.credentialsFile = config.sops.secrets.internetbs.path;
security.acme.certs = { security.acme.certs = {
"surfsite.org" = { "surfsite.org" = {
extraDomainNames = [ "*.surfsite.org" ]; extraDomainNames = [ "*.surfsite.org" ];
};
"hartwin-hoyer.de" = {
extraDomainNames = [ "*.hartwin-hoyer.de" ];
};
"herward-hoyer.de" = {
extraDomainNames = [ "*.herward-hoyer.de" ];
};
"varlink.org" = {
extraDomainNames = [ "*.varlink.org" ];
};
"meike-hoyer.de" = { };
"hoyer.xyz" = {
extraDomainNames = [
"*.hoyer.xyz"
"*.harald.hoyer.xyz"
"*.hartwin.hoyer.xyz"
];
};
"hoyer.world" = {
extraDomainNames = [
"*.hoyer.world"
"*.harald.hoyer.world"
"*.hartwin.hoyer.world"
];
};
"hoyer.social" = {
extraDomainNames = [
"*.hoyer.social"
"*.harald.hoyer.social"
"*.hartwin.hoyer.social"
];
};
"hoyer.photos" = {
extraDomainNames = [
"*.hoyer.photos"
"*.harald.hoyer.photos"
"*.hartwin.hoyer.photos"
];
};
"harald-hoyer.de" = {
extraDomainNames = [ "*.harald-hoyer.de" ];
};
}; };
"hartwin-hoyer.de" = {
extraDomainNames = [ "*.hartwin-hoyer.de" ];
};
"herward-hoyer.de" = {
extraDomainNames = [ "*.herward-hoyer.de" ];
};
"varlink.org" = {
extraDomainNames = [ "*.varlink.org" ];
};
"meike-hoyer.de" = { };
"hoyer.xyz" = {
extraDomainNames = [
"*.hoyer.xyz"
"*.harald.hoyer.xyz"
"*.hartwin.hoyer.xyz"
];
};
"hoyer.world" = {
extraDomainNames = [
"*.hoyer.world"
"*.harald.hoyer.world"
"*.hartwin.hoyer.world"
];
};
"hoyer.social" = {
extraDomainNames = [
"*.hoyer.social"
"*.harald.hoyer.social"
"*.hartwin.hoyer.social"
];
};
"hoyer.photos" = {
extraDomainNames = [
"*.hoyer.photos"
"*.harald.hoyer.photos"
"*.hartwin.hoyer.photos"
];
};
"harald-hoyer.de" = {
extraDomainNames = [ "*.harald-hoyer.de" ];
};
};
} }

View file

@ -1,7 +1,8 @@
{ pkgs {
, lib pkgs,
, config lib,
, ... config,
...
}: }:
{ {
sops.secrets."coturn/static-auth-secret" = { sops.secrets."coturn/static-auth-secret" = {

View file

@ -6,7 +6,7 @@
enable = true; enable = true;
nextcloudUrl = "https://nc.hoyer.xyz"; nextcloudUrl = "https://nc.hoyer.xyz";
botSecretFile = config.sops.secrets."nextcloud-claude-bot/secret".path; botSecretFile = config.sops.secrets."nextcloud-claude-bot/secret".path;
allowedUsers = []; # Allow all registered users allowedUsers = [ ]; # Allow all registered users
# Optional extra instructions (base prompt is hardcoded in bot.py) # Optional extra instructions (base prompt is hardcoded in bot.py)
# systemPrompt = "Additional custom instructions here"; # systemPrompt = "Additional custom instructions here";
}; };

View file

@ -13,10 +13,10 @@
# 1. Use the official package if available in nixpkgs # 1. Use the official package if available in nixpkgs
# 2. Package it yourself # 2. Package it yourself
# 3. Use a binary wrapper # 3. Use a binary wrapper
# Option 1: If claude-code is in nixpkgs (check latest state) # Option 1: If claude-code is in nixpkgs (check latest state)
# environment.systemPackages = [ pkgs.claude-code ]; # environment.systemPackages = [ pkgs.claude-code ];
# Option 2: Manual binary installation wrapper # Option 2: Manual binary installation wrapper
nixpkgs.overlays = [ nixpkgs.overlays = [
(final: prev: { (final: prev: {
@ -30,22 +30,22 @@
# Create bot secret # Create bot secret
# Generate with: openssl rand -hex 32 # Generate with: openssl rand -hex 32
# Store in a file, e.g., /var/secrets/nextcloud-claude-bot # Store in a file, e.g., /var/secrets/nextcloud-claude-bot
services.nextcloud-claude-bot = { services.nextcloud-claude-bot = {
enable = true; enable = true;
port = 8085; port = 8085;
host = "127.0.0.1"; host = "127.0.0.1";
nextcloudUrl = "https://cloud.example.com"; nextcloudUrl = "https://cloud.example.com";
botSecretFile = "/var/secrets/nextcloud-claude-bot"; botSecretFile = "/var/secrets/nextcloud-claude-bot";
# Only allow specific users # Only allow specific users
allowedUsers = [ "harald" ]; allowedUsers = [ "harald" ];
# Claude settings # Claude settings
maxTokens = 4096; maxTokens = 4096;
timeout = 120; timeout = 120;
# Optional system prompt # Optional system prompt
systemPrompt = '' systemPrompt = ''
Du bist ein hilfreicher Assistent. Antworte auf Deutsch, Du bist ein hilfreicher Assistent. Antworte auf Deutsch,
@ -62,7 +62,7 @@
# If you need external access (e.g., Nextcloud on different server): # If you need external access (e.g., Nextcloud on different server):
services.nginx.virtualHosts."cloud.example.com" = { services.nginx.virtualHosts."cloud.example.com" = {
# ... your existing Nextcloud config ... # ... your existing Nextcloud config ...
locations."/_claude-bot/" = { locations."/_claude-bot/" = {
proxyPass = "http://127.0.0.1:8085/"; proxyPass = "http://127.0.0.1:8085/";
extraConfig = '' extraConfig = ''
@ -70,7 +70,7 @@
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
# Only allow from Nextcloud itself # Only allow from Nextcloud itself
allow 127.0.0.1; allow 127.0.0.1;
deny all; deny all;

View file

@ -1,61 +1,72 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
with lib; with lib;
let let
cfg = config.services.nextcloud-claude-bot; cfg = config.services.nextcloud-claude-bot;
pythonEnv = pkgs.python3.withPackages (ps: with ps; [ pythonEnv = pkgs.python3.withPackages (
fastapi ps: with ps; [
uvicorn fastapi
httpx uvicorn
]); httpx
]
);
botModule = pkgs.runCommand "nextcloud-claude-bot-module" {} '' botModule = pkgs.runCommand "nextcloud-claude-bot-module" { } ''
mkdir -p $out mkdir -p $out
cp ${./bot.py} $out/nextcloud_claude_bot.py cp ${./bot.py} $out/nextcloud_claude_bot.py
''; '';
in { in
{
options.services.nextcloud-claude-bot = { options.services.nextcloud-claude-bot = {
enable = mkEnableOption "Nextcloud Talk Claude Bot"; enable = mkEnableOption "Nextcloud Talk Claude Bot";
port = mkOption { port = mkOption {
type = types.port; type = types.port;
default = 8085; default = 8085;
description = "Port for the webhook listener"; description = "Port for the webhook listener";
}; };
host = mkOption { host = mkOption {
type = types.str; type = types.str;
default = "127.0.0.1"; default = "127.0.0.1";
description = "Host to bind to"; description = "Host to bind to";
}; };
nextcloudUrl = mkOption { nextcloudUrl = mkOption {
type = types.str; type = types.str;
example = "https://cloud.example.com"; example = "https://cloud.example.com";
description = "Base URL of your Nextcloud instance"; description = "Base URL of your Nextcloud instance";
}; };
botSecretFile = mkOption { botSecretFile = mkOption {
type = types.path; type = types.path;
description = "Path to file containing the bot secret (shared with Nextcloud)"; description = "Path to file containing the bot secret (shared with Nextcloud)";
}; };
claudePath = mkOption { claudePath = mkOption {
type = types.path; type = types.path;
default = "${pkgs.claude-code}/bin/claude"; default = "${pkgs.claude-code}/bin/claude";
description = "Path to claude CLI binary"; description = "Path to claude CLI binary";
}; };
allowedUsers = mkOption { allowedUsers = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
default = []; default = [ ];
example = [ "harald" "admin" ]; example = [
"harald"
"admin"
];
description = "Nextcloud usernames allowed to talk to the bot (empty = all)"; description = "Nextcloud usernames allowed to talk to the bot (empty = all)";
}; };
contextMessages = mkOption { contextMessages = mkOption {
type = types.int; type = types.int;
default = 6; default = 6;
@ -67,7 +78,7 @@ in {
default = 120; default = 120;
description = "Timeout in seconds for Claude CLI"; description = "Timeout in seconds for Claude CLI";
}; };
systemPrompt = mkOption { systemPrompt = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = null; default = null;
@ -75,13 +86,13 @@ in {
description = "Optional system prompt for Claude"; description = "Optional system prompt for Claude";
}; };
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable {
systemd.services.nextcloud-claude-bot = { systemd.services.nextcloud-claude-bot = {
description = "Nextcloud Talk Claude Bot"; description = "Nextcloud Talk Claude Bot";
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
after = [ "network.target" ]; after = [ "network.target" ];
environment = { environment = {
HOME = "/var/lib/nextcloud-claude-bot"; HOME = "/var/lib/nextcloud-claude-bot";
BOT_HOST = cfg.host; BOT_HOST = cfg.host;
@ -133,8 +144,8 @@ in {
home = "/var/lib/nextcloud-claude-bot"; home = "/var/lib/nextcloud-claude-bot";
}; };
users.groups.claude-bot = {}; users.groups.claude-bot = { };
# Nginx reverse proxy config (optional, if you want external access) # Nginx reverse proxy config (optional, if you want external access)
# services.nginx.virtualHosts."cloud.example.com".locations."/claude-bot/" = { # services.nginx.virtualHosts."cloud.example.com".locations."/claude-bot/" = {
# proxyPass = "http://${cfg.host}:${toString cfg.port}/"; # proxyPass = "http://${cfg.host}:${toString cfg.port}/";

View file

@ -1,145 +1,145 @@
{ ... }: { ... }:
{ {
services.nginx.virtualHosts = { services.nginx.virtualHosts = {
"00000" = { "00000" = {
useACMEHost = "hoyer.xyz"; useACMEHost = "hoyer.xyz";
serverName = "_"; serverName = "_";
globalRedirect = "hoyer.xyz"; globalRedirect = "hoyer.xyz";
addSSL = true; addSSL = true;
}; };
"hoyer.photos" = { "hoyer.photos" = {
enableACME = false; enableACME = false;
useACMEHost = "hoyer.photos"; useACMEHost = "hoyer.photos";
forceSSL = true; forceSSL = true;
root = "/var/www/hoyer.xyz/html"; root = "/var/www/hoyer.xyz/html";
}; };
"hoyer.world" = { "hoyer.world" = {
enableACME = false; enableACME = false;
useACMEHost = "hoyer.world"; useACMEHost = "hoyer.world";
forceSSL = true; forceSSL = true;
root = "/var/www/hoyer.xyz/html"; root = "/var/www/hoyer.xyz/html";
}; };
"hoyer.social" = { "hoyer.social" = {
enableACME = false; enableACME = false;
useACMEHost = "hoyer.social"; useACMEHost = "hoyer.social";
forceSSL = true; forceSSL = true;
root = "/var/www/hoyer.xyz/html"; root = "/var/www/hoyer.xyz/html";
}; };
"hoyer.xyz" = { "hoyer.xyz" = {
# serverName = "hoyer.xyz"; # serverName = "hoyer.xyz";
serverAliases = [ "www.hoyer.xyz" ]; serverAliases = [ "www.hoyer.xyz" ];
useACMEHost = "hoyer.xyz"; useACMEHost = "hoyer.xyz";
enableACME = false; enableACME = false;
forceSSL = true; forceSSL = true;
root = "/var/www/hoyer.xyz/html"; root = "/var/www/hoyer.xyz/html";
locations."/stats" = { locations."/stats" = {
basicAuthFile = "/var/www/hoyer.xyz/stats.htaccess"; basicAuthFile = "/var/www/hoyer.xyz/stats.htaccess";
};
locations."/.well-known/webfinger" = {
return = "307 https://nc.hoyer.xyz/.well-known/webfinger";
};
}; };
locations."/.well-known/webfinger" = {
"surfsite.org" = { return = "307 https://nc.hoyer.xyz/.well-known/webfinger";
useACMEHost = "surfsite.org";
enableACME = false;
forceSSL = true;
root = "/var/www/surfsite.org";
};
"kicker.surfsite.org" = {
useACMEHost = "surfsite.org";
enableACME = false;
forceSSL = true;
locations."/" = {
proxyPass = "http://localhost:8087/";
};
};
"git.hoyer.xyz" = {
useACMEHost = "hoyer.xyz";
enableACME = false;
forceSSL = true;
locations."/" = {
proxyPass = "http://localhost:3001/";
};
};
"webmail.hoyer.xyz" = {
useACMEHost = "hoyer.xyz";
enableACME = false;
forceSSL = true;
};
"harald.hoyer.photos" = {
enableACME = true;
forceSSL = true;
root = "/var/www/photo.harald-hoyer.de/html";
};
"photo.harald-hoyer.de" = {
useACMEHost = "harald-hoyer.de";
forceSSL = true;
root = "/var/www/photo.harald-hoyer.de/html";
};
"photo-harald.hoyer.xyz" = {
serverAliases = [ "photo.harald.hoyer.xyz" ];
useACMEHost = "hoyer.xyz";
forceSSL = true;
root = "/var/www/photo.harald-hoyer.de/html";
};
"harald-hoyer.de" = {
serverAliases = [ "www.harald-hoyer.de" ];
useACMEHost = "harald-hoyer.de";
globalRedirect = "harald.hoyer.xyz";
forceSSL = true;
};
"harald.hoyer.xyz" = {
serverAliases = [ "www.harald.hoyer.xyz" ];
useACMEHost = "hoyer.xyz";
root = "/var/www/harald.hoyer.xyz/html/";
extraConfig = ''
rewrite ^/feed/rss$ /rss.xml permanent;
rewrite ^/feed/rss/$ /rss.xml permanent;
rewrite ^/feed/$ /rss.xml permanent;
rewrite ^/feed/rss/index.html$ /rss.xml permanent;
rewrite ^/fedora/fedora/RSS2$ /tags/fedora/rss.xml permanent;
rewrite ^/linux/linux/RSS2$ /tags/linux/rss.xml permanent;
rewrite ^/linux/feed$ /tags/linux/rss.xml permanent;
rewrite ^/wp-rss2.php$ /rss.xml permanent;
rewrite ^/aggregator/rss.xml$ /rss.xml permanent;
rewrite ^/personal/blog/aggregator/RSS$ /rss.xml permanent;
rewrite ^/personal/blog/aggregator/RSS2$ /rss.xml permanent;
rewrite ^/aggregator/RSS$ /rss.xml permanent;
rewrite ^/aggregator/RSS2$ /rss.xml permanent;
rewrite ^/wp-commentsrss2.php$ /rss.xml permanent;
'';
forceSSL = true;
};
"hartwin-hoyer.de" = {
serverAliases = [ "www.hartwin-hoyer.de" ];
useACMEHost = "hartwin-hoyer.de";
globalRedirect = "hartwin.hoyer.xyz";
forceSSL = true;
};
"hartwin.hoyer.xyz" = {
serverAliases = [
"testhartwin.hoyer.xyz"
"www.hartwin.hoyer.xyz"
];
useACMEHost = "hoyer.xyz";
root = "/var/www/hartwin.hoyer.xyz/html/";
forceSSL = true;
};
"nc.hoyer.xyz" = {
useACMEHost = "hoyer.xyz";
forceSSL = true;
}; };
}; };
"surfsite.org" = {
useACMEHost = "surfsite.org";
enableACME = false;
forceSSL = true;
root = "/var/www/surfsite.org";
};
"kicker.surfsite.org" = {
useACMEHost = "surfsite.org";
enableACME = false;
forceSSL = true;
locations."/" = {
proxyPass = "http://localhost:8087/";
};
};
"git.hoyer.xyz" = {
useACMEHost = "hoyer.xyz";
enableACME = false;
forceSSL = true;
locations."/" = {
proxyPass = "http://localhost:3001/";
};
};
"webmail.hoyer.xyz" = {
useACMEHost = "hoyer.xyz";
enableACME = false;
forceSSL = true;
};
"harald.hoyer.photos" = {
enableACME = true;
forceSSL = true;
root = "/var/www/photo.harald-hoyer.de/html";
};
"photo.harald-hoyer.de" = {
useACMEHost = "harald-hoyer.de";
forceSSL = true;
root = "/var/www/photo.harald-hoyer.de/html";
};
"photo-harald.hoyer.xyz" = {
serverAliases = [ "photo.harald.hoyer.xyz" ];
useACMEHost = "hoyer.xyz";
forceSSL = true;
root = "/var/www/photo.harald-hoyer.de/html";
};
"harald-hoyer.de" = {
serverAliases = [ "www.harald-hoyer.de" ];
useACMEHost = "harald-hoyer.de";
globalRedirect = "harald.hoyer.xyz";
forceSSL = true;
};
"harald.hoyer.xyz" = {
serverAliases = [ "www.harald.hoyer.xyz" ];
useACMEHost = "hoyer.xyz";
root = "/var/www/harald.hoyer.xyz/html/";
extraConfig = ''
rewrite ^/feed/rss$ /rss.xml permanent;
rewrite ^/feed/rss/$ /rss.xml permanent;
rewrite ^/feed/$ /rss.xml permanent;
rewrite ^/feed/rss/index.html$ /rss.xml permanent;
rewrite ^/fedora/fedora/RSS2$ /tags/fedora/rss.xml permanent;
rewrite ^/linux/linux/RSS2$ /tags/linux/rss.xml permanent;
rewrite ^/linux/feed$ /tags/linux/rss.xml permanent;
rewrite ^/wp-rss2.php$ /rss.xml permanent;
rewrite ^/aggregator/rss.xml$ /rss.xml permanent;
rewrite ^/personal/blog/aggregator/RSS$ /rss.xml permanent;
rewrite ^/personal/blog/aggregator/RSS2$ /rss.xml permanent;
rewrite ^/aggregator/RSS$ /rss.xml permanent;
rewrite ^/aggregator/RSS2$ /rss.xml permanent;
rewrite ^/wp-commentsrss2.php$ /rss.xml permanent;
'';
forceSSL = true;
};
"hartwin-hoyer.de" = {
serverAliases = [ "www.hartwin-hoyer.de" ];
useACMEHost = "hartwin-hoyer.de";
globalRedirect = "hartwin.hoyer.xyz";
forceSSL = true;
};
"hartwin.hoyer.xyz" = {
serverAliases = [
"testhartwin.hoyer.xyz"
"www.hartwin.hoyer.xyz"
];
useACMEHost = "hoyer.xyz";
root = "/var/www/hartwin.hoyer.xyz/html/";
forceSSL = true;
};
"nc.hoyer.xyz" = {
useACMEHost = "hoyer.xyz";
forceSSL = true;
};
};
} }

View file

@ -1,7 +1,8 @@
{ pkgs {
, lib pkgs,
, config lib,
, ... config,
...
}: }:
let let
backup_new_path = "/mnt/raid/backup/hoyer/new/"; backup_new_path = "/mnt/raid/backup/hoyer/new/";