feat: add aichat kimi and spotify mcp services
Add Home Manager module for `aichat` CLI, integrating Moonshot/Kimi API. Uses sops-nix for API key management. Add `spotify-mcp-server` as a system service, integrated with Hermes. Manages Spotify credentials via sops-nix. Enable `nextcloud` OIDC app. Configure `nextcloud-mcp` to restart on sops secret changes. Enable `zitadel` service.
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
# kimi-aichat.nix
|
||||
# Module Home Manager — importez-le dans votre home.nix
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
cfg = config.programs.aichat-moonshot;
|
||||
in {
|
||||
options.programs.aichat-moonshot = {
|
||||
enable = mkEnableOption "aichat CLI configuré pour Moonshot/Kimi via sops-nix";
|
||||
|
||||
sopsSecretKey = mkOption {
|
||||
type = types.str;
|
||||
default = "kimi/api_key";
|
||||
description = ''
|
||||
Nom de la clé dans votre fichier de secrets sops.
|
||||
Exemple: <literal>kimi/api_key</literal> ou <literal>aichat/moonshot_api_key</literal>.
|
||||
La valeur déchiffrée doit être la clé brute (ex: <literal>sk-xxx</literal>).
|
||||
'';
|
||||
};
|
||||
|
||||
model = mkOption {
|
||||
type = types.str;
|
||||
default = "moonshot:kimi-latest";
|
||||
example = "moonshot:kimi-k1";
|
||||
description = "Modèle par défaut utilisé par aichat.";
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.attrs;
|
||||
default = {};
|
||||
example = literalExpression ''
|
||||
{
|
||||
save = true;
|
||||
keybindings = "vi";
|
||||
highlight = true;
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Attributs Nix supplémentaires mergés dans <filename>~/.config/aichat/config.yaml</filename>.
|
||||
Ne mettez JAMAIS de secrets ici.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
# 1. Déclare le secret pour sops-nix
|
||||
# (suppose que vous avez déjà importé le module sops-nix dans votre config HM)
|
||||
sops.secrets.${cfg.sopsSecretKey} = {};
|
||||
|
||||
# 2. Fichier de config publique (sans clé API)
|
||||
home.file.".config/aichat/config.yaml".text = lib.generators.toYAML {} ({
|
||||
model = cfg.model;
|
||||
}
|
||||
// cfg.extraConfig);
|
||||
|
||||
# 3. Wrapper "kimi" qui lit le secret à l'exécution
|
||||
home.packages = [
|
||||
(pkgs.writeShellApplication {
|
||||
name = "kimi";
|
||||
runtimeInputs = [pkgs.aichat];
|
||||
text = ''
|
||||
export AICHAT_MOONSHOT_API_KEY=$(cat "${config.sops.secrets.${cfg.sopsSecretKey}.path}")
|
||||
exec aichat "$@"
|
||||
'';
|
||||
})
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -26,8 +26,9 @@
|
||||
./hermes
|
||||
./discord-bot
|
||||
./eternal-terminal
|
||||
# ./nextcloud-mcp
|
||||
# ./gotify
|
||||
# ./zitadel
|
||||
./zitadel
|
||||
# ./syncserver
|
||||
# ./newt # attendre maj flakes
|
||||
];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{...}: {
|
||||
{config, ...}: {
|
||||
virtualisation.oci-containers.containers.nextcloud-mcp-claude = {
|
||||
image = "ghcr.io/cbcoutinho/nextcloud-mcp-server:latest";
|
||||
cmd = ["--transport" "streamable-http" "--oauth" "--port" "8004" "--enable-app" "calendar"];
|
||||
@@ -13,5 +13,7 @@
|
||||
environmentFiles = [config.sops.secrets."nextcloud_mcp_oauth_env".path];
|
||||
volumes = ["nextcloud-mcp-claude:/app/data"];
|
||||
};
|
||||
sops.secrets."nextcloud_mcp_oauth_env" = {};
|
||||
sops.secrets."nextcloud_mcp_oauth_env".restartUnits = [
|
||||
"${config.virtualisation.oci-containers.backend}-nextcloud-mcp-claude.service"
|
||||
];
|
||||
}
|
||||
|
||||
@@ -41,6 +41,9 @@
|
||||
{
|
||||
addr = "0.0.0.0";
|
||||
port = 8083;
|
||||
# locations."= /.well-known/openid-configuration".extraConfig = ''
|
||||
# rewrite ^ /index.php$request_uri last;
|
||||
# '';
|
||||
}
|
||||
];
|
||||
};
|
||||
@@ -99,6 +102,11 @@
|
||||
memories
|
||||
notes
|
||||
;
|
||||
oidc = pkgs.fetchNextcloudApp {
|
||||
url = "https://github.com/H2CK/oidc/releases/download/2.3.1/oidc-2.3.1.tar.gz";
|
||||
hash = "sha256-zfdZSUwV8VZ5qq7rwN/G4cSfTbuutaK+yJ6g8556yOQ=";
|
||||
license = "agpl3Plus";
|
||||
};
|
||||
};
|
||||
extraAppsEnable = true;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
# services/spotify-mcp/default.nix
|
||||
# Serveur MCP Spotify (marcelmarais/spotify-mcp-server) lancé en stdio par Hermes sur tower.
|
||||
#
|
||||
# flake.nix, dans inputs (remplace l'ancien input varunneal) :
|
||||
# spotify-mcp = {
|
||||
# url = "github:marcelmarais/spotify-mcp-server/108fd6b26db9bd9627cd4e4a128841a0f8933756";
|
||||
# flake = false;
|
||||
# };
|
||||
#
|
||||
# secrets sops (fichier par défaut) :
|
||||
# spotify_mcp_env: |
|
||||
# SPOTIFY_CLIENT_ID=...
|
||||
# SPOTIFY_CLIENT_SECRET=...
|
||||
#
|
||||
# App Spotify Developer : redirect URI = http://127.0.0.1:8888/callback
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
inputs,
|
||||
...
|
||||
}: let
|
||||
stateDir = "/var/lib/hermes/spotify-mcp";
|
||||
configFile = "${stateDir}/spotify-config.json";
|
||||
redirectUri = "http://127.0.0.1:8888/callback";
|
||||
|
||||
# Le projet exige node >= 26.8.1 (package.json, engines).
|
||||
nodejs = pkgs.nodejs_26;
|
||||
|
||||
spotify-mcp = pkgs.buildNpmPackage {
|
||||
pname = "spotify-mcp-server";
|
||||
version = "1.0.0-unstable-2026-09-21";
|
||||
src = inputs.spotify-mcp;
|
||||
inherit nodejs;
|
||||
|
||||
# 1er build : laisser fakeHash, copier le hash donné par l'erreur, rebuild.
|
||||
npmDepsHash = lib.fakeHash;
|
||||
|
||||
# Le chemin de config est codé en dur à côté du build, donc dans le store
|
||||
# en lecture seule. On le rend pilotable par variable d'environnement,
|
||||
# sinon le refresh du token échoue au bout d'une heure.
|
||||
postPatch = ''
|
||||
substituteInPlace src/utils.ts \
|
||||
--replace-fail \
|
||||
"path.join(__dirname, '../spotify-config.json')" \
|
||||
"(process.env.SPOTIFY_MCP_CONFIG ?? path.join(__dirname, '../spotify-config.json'))"
|
||||
'';
|
||||
|
||||
meta.mainProgram = "spotify-mcp";
|
||||
};
|
||||
|
||||
authScript = "${spotify-mcp}/lib/node_modules/spotify-mcp-server/build/auth.js";
|
||||
|
||||
# Le fichier de config contient aussi les tokens, réécrits par le serveur.
|
||||
# À chaque lancement on y réinjecte client id / secret depuis sops,
|
||||
# en conservant accessToken, refreshToken et expiresAt.
|
||||
prepareConfig = ''
|
||||
set -euo pipefail
|
||||
umask 077
|
||||
set -a
|
||||
. ${config.sops.secrets.spotify_mcp_env.path}
|
||||
set +a
|
||||
[ -f ${configFile} ] || echo '{}' > ${configFile}
|
||||
${lib.getExe pkgs.jq} \
|
||||
--arg id "$SPOTIFY_CLIENT_ID" \
|
||||
--arg secret "$SPOTIFY_CLIENT_SECRET" \
|
||||
--arg uri ${redirectUri} \
|
||||
'. + {clientId: $id, clientSecret: $secret, redirectUri: $uri}' \
|
||||
${configFile} > ${configFile}.tmp
|
||||
mv ${configFile}.tmp ${configFile}
|
||||
export SPOTIFY_MCP_CONFIG=${configFile}
|
||||
'';
|
||||
|
||||
wrapper = pkgs.writeShellScript "spotify-mcp-hermes" ''
|
||||
${prepareConfig}
|
||||
exec ${lib.getExe spotify-mcp}
|
||||
'';
|
||||
|
||||
# Login OAuth unique, à lancer à la main en tant que hermes.
|
||||
login = pkgs.writeShellScriptBin "spotify-mcp-login" ''
|
||||
${prepareConfig}
|
||||
exec ${lib.getExe nodejs} ${authScript}
|
||||
'';
|
||||
in {
|
||||
sops.secrets.spotify_mcp_env = {
|
||||
owner = "hermes";
|
||||
mode = "0400";
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${stateDir} 0700 hermes hermes -"
|
||||
];
|
||||
|
||||
environment.systemPackages = [login];
|
||||
|
||||
services.hermes-agent.mcpServers.spotify = {
|
||||
command = "${wrapper}";
|
||||
# Optionnel, pour démarrer sans les actions destructives :
|
||||
# tools.exclude = [ "removeUsersSavedTracks" "unfollowPlaylist" "removeTracksFromPlaylist" ];
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user