The bot no longer shells out to `opencode run`. Instead it POSTs to the OpenAI-compatible /chat/completions endpoint exposed by llama-server on halo.hoyer.tail:8000 directly. This removes the Bun/sqlite cold-start overhead per request, drops the pkgs.opencode runtime dependency, and eliminates the ExecStartPre dance that materialized config.json into the service's $HOME. Conversation history is now stored as a proper OpenAI `messages` list with system/user/assistant roles, instead of the XML blob that was inlined into a single `opencode run` argument. The interactive opencode setup (config/opencode/config.json) is unchanged — only the bot stops depending on it. The module gains a `modelBaseUrl` option; `model` is now the bare model name (`halo-8000`) without the provider/ prefix that the opencode CLI required.
154 lines
3.9 KiB
Nix
154 lines
3.9 KiB
Nix
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
|
|
with lib;
|
|
|
|
let
|
|
cfg = config.services.nextcloud-opencode-bot;
|
|
|
|
pythonEnv = pkgs.python3.withPackages (
|
|
ps: with ps; [
|
|
fastapi
|
|
uvicorn
|
|
httpx
|
|
]
|
|
);
|
|
|
|
botModule = pkgs.runCommand "nextcloud-opencode-bot-module" { } ''
|
|
mkdir -p $out
|
|
cp ${./bot.py} $out/nextcloud_opencode_bot.py
|
|
'';
|
|
|
|
in
|
|
{
|
|
options.services.nextcloud-opencode-bot = {
|
|
enable = mkEnableOption "Nextcloud Talk OpenCode Bot";
|
|
|
|
port = mkOption {
|
|
type = types.port;
|
|
default = 8086;
|
|
description = "Port for the webhook listener";
|
|
};
|
|
|
|
host = mkOption {
|
|
type = types.str;
|
|
default = "127.0.0.1";
|
|
description = "Host to bind to";
|
|
};
|
|
|
|
nextcloudUrl = mkOption {
|
|
type = types.str;
|
|
example = "https://cloud.example.com";
|
|
description = "Base URL of your Nextcloud instance";
|
|
};
|
|
|
|
botSecretFile = mkOption {
|
|
type = types.path;
|
|
description = "Path to file containing the bot secret (shared with Nextcloud)";
|
|
};
|
|
|
|
modelBaseUrl = mkOption {
|
|
type = types.str;
|
|
example = "http://halo.hoyer.tail:8000/v1";
|
|
description = "Base URL of the OpenAI-compatible chat-completions endpoint (without trailing /chat/completions)";
|
|
};
|
|
|
|
model = mkOption {
|
|
type = types.str;
|
|
default = "halo-8000";
|
|
description = "Model name passed in the `model` field of /chat/completions requests";
|
|
};
|
|
|
|
botName = mkOption {
|
|
type = types.str;
|
|
default = "Halo";
|
|
description = "Bot display name (used to detect @mentions in group chats)";
|
|
};
|
|
|
|
allowedUsers = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [ ];
|
|
description = "Nextcloud usernames allowed to talk to the bot (empty = all)";
|
|
};
|
|
|
|
contextMessages = mkOption {
|
|
type = types.int;
|
|
default = 6;
|
|
description = "Number of recent (user+assistant) turns to keep as context";
|
|
};
|
|
|
|
timeout = mkOption {
|
|
type = types.int;
|
|
default = 120;
|
|
description = "Timeout in seconds for the model API call";
|
|
};
|
|
|
|
systemPrompt = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Optional additional system prompt appended to the built-in one";
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
systemd.services.nextcloud-opencode-bot = {
|
|
description = "Nextcloud Talk OpenCode Bot";
|
|
wantedBy = [ "multi-user.target" ];
|
|
after = [ "network.target" ];
|
|
|
|
environment = {
|
|
BOT_HOST = cfg.host;
|
|
BOT_PORT = toString cfg.port;
|
|
NEXTCLOUD_URL = cfg.nextcloudUrl;
|
|
MODEL_BASE_URL = cfg.modelBaseUrl;
|
|
MODEL_NAME = cfg.model;
|
|
BOT_NAME = cfg.botName;
|
|
ALLOWED_USERS = concatStringsSep "," cfg.allowedUsers;
|
|
CONTEXT_MESSAGES = toString cfg.contextMessages;
|
|
TIMEOUT = toString cfg.timeout;
|
|
SYSTEM_PROMPT = cfg.systemPrompt or "";
|
|
PYTHONPATH = botModule;
|
|
};
|
|
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
|
|
ExecStart = "${pythonEnv}/bin/uvicorn nextcloud_opencode_bot:app --host ${cfg.host} --port ${toString cfg.port}";
|
|
Restart = "always";
|
|
RestartSec = 5;
|
|
|
|
User = "opencode-bot";
|
|
Group = "opencode-bot";
|
|
|
|
NoNewPrivileges = true;
|
|
ProtectSystem = "strict";
|
|
ProtectHome = "read-only";
|
|
PrivateTmp = true;
|
|
PrivateDevices = true;
|
|
ProtectKernelTunables = true;
|
|
ProtectKernelModules = true;
|
|
ProtectControlGroups = true;
|
|
RestrictNamespaces = true;
|
|
RestrictRealtime = true;
|
|
RestrictSUIDSGID = true;
|
|
MemoryDenyWriteExecute = false;
|
|
LockPersonality = true;
|
|
|
|
LoadCredential = "bot-secret:${cfg.botSecretFile}";
|
|
|
|
StateDirectory = "nextcloud-opencode-bot";
|
|
};
|
|
};
|
|
|
|
users.users.opencode-bot = {
|
|
isSystemUser = true;
|
|
group = "opencode-bot";
|
|
};
|
|
|
|
users.groups.opencode-bot = { };
|
|
};
|
|
}
|