fix(nextcloud): retirer password.txt (mdp en clair, repo public) + rotation forcée
services/nextcloud/password.txt était committé en clair ("nextcloudpassword")
depuis le 25/11/2025, jamais référencé dans la config, exposé publiquement
sur github/codeberg depuis. Supprimé.
adminpassFile ne fixe le mot de passe QUE lors de l'install initiale (occ
maintenance:install) : sur une instance déjà installée il est ignoré, donc
juste changer le secret sops n'aurait pas suffi. Ajout d'un ExecStartPost
sur nextcloud-setup qui force le mot de passe du compte root à la valeur
sops à chaque activation (self-healing).
Vérifié sur tower : ancien mot de passe "nextcloudpassword" -> 401 après
le switch, hash oc_users.password changé.
This commit is contained in:
@@ -6,7 +6,7 @@ Marc Partensky's NixOS + Home Manager + nix-darwin configuration, one flake for
|
||||
|
||||
| Flake output | Type | Description |
|
||||
|---|---|---|
|
||||
| `nixosConfigurations.tower` | NixOS | Main machine (AMD, ZFS). `profiles/common` + `profiles/tower` + `services/` (all self-hosted services) + `protonmail-mcp`. |
|
||||
| `nixosConfigurations.tower` | NixOS | Main machine (AMD, ZFS). `profiles/common` + `profiles/tower` + `services/` (all self-hosted services). |
|
||||
| `nixosConfigurations.laptop` | NixOS | Laptop. `profiles/common` + `profiles/laptop` + `services/`. |
|
||||
| `nixosConfigurations.deck` | NixOS | Steam Deck (via Jovian-NixOS). `profiles/deck` only, no `services/`. |
|
||||
| `nixosConfigurations.anywhere` | NixOS | VPS (RackNerd): Pangolin, Traefik/Caddy, Stalwart mail, headscale. `profiles/anywhere`. |
|
||||
@@ -81,7 +81,7 @@ Registry in `services/default.nix`. Active on tower:
|
||||
- **Media**: `jellyfin`, `navidrome`, `audiobookshelf`, `radarr`/`sonarr`/`readarr`/`prowlarr`/`flaresolverr`, `qbittorrent`/`rqbit`, `media` (shared `/srv/media` dataset).
|
||||
- **Matrix bridges**: `matrix` (Synapse), `matrix-whatsapp`, `matrix-signal`, `matrix-discord`, `matrix-meta`, `matrix-linkedin`.
|
||||
- **Hermes agent**: `hermes` (gateway + plugins), `hermes-webui`, `hermes-dashboard`, `hermes-pocket` (mobile app backend), `discord-bot`.
|
||||
- **MCP servers** (tools for Hermes): `nextcloud-mcp`, `amazon-mcp`, `firefox-mcp`, `zitadel-mcp`, `roku`, `codeberg-mcp`, `ibkr-mcp`, `meetup-mcp`, `gitea-mcp`, `arr-mcp`, `mcp-nixos`. `protonmail-mcp` is tower-only (imported directly from `flake.nix`, not in the registry).
|
||||
- **MCP servers** (tools for Hermes): `nextcloud-mcp`, `amazon-mcp`, `firefox-mcp`, `zitadel-mcp`, `roku`, `codeberg-mcp`, `ibkr-mcp`, `meetup-mcp`, `gitea-mcp`, `arr-mcp`, `mcp-nixos`.
|
||||
- **Other apps**: `jupyterhub`.
|
||||
- **Disabled in the registry** (commented out, pending a fix or waiting on something): `dnscrypt`, `minio`, `stalwart-mcp` (cargoHash mismatch), `gotify`, `kanidm`, `syncserver`, `matrix-telegram` (waiting on api_id/api_hash), `vaultwarden-mcp` (missing input).
|
||||
|
||||
|
||||
Generated
-17
@@ -1167,22 +1167,6 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"proton-mail-bridge-client": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1790365286,
|
||||
"narHash": "sha256-IXTyW7tYbBoDMDidCJoCZwalENCXNHGMnHhsGJ/ETNI=",
|
||||
"owner": "googlarz",
|
||||
"repo": "proton-mail-bridge-client",
|
||||
"rev": "e5ac1999e89573a1b61e1dc59a7d440c1714177f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "googlarz",
|
||||
"repo": "proton-mail-bridge-client",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"pyproject-build-systems": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
@@ -1533,7 +1517,6 @@
|
||||
"nur": "nur",
|
||||
"option-pricer": "option-pricer",
|
||||
"portfolio-optimizer": "portfolio-optimizer",
|
||||
"proton-mail-bridge-client": "proton-mail-bridge-client",
|
||||
"pyproject-nix": "pyproject-nix_8",
|
||||
"sops": "sops",
|
||||
"sopswarden": "sopswarden",
|
||||
|
||||
@@ -138,17 +138,6 @@
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
# Le sous-flake ./protonmail-mcp n'est PAS branché en input "path:" : un input
|
||||
# relatif produit un noeud non verrouillé dans flake.lock, que les nix trop
|
||||
# anciens (<= 2.20) refusent ("lock file contains mutable lock"), ce qui casse
|
||||
# le switch sur le Mac. On appelle directement son outputs plus bas (cf.
|
||||
# `protonmailMcp`), et on ne prend ici que sa dépendance externe, qui elle se
|
||||
# verrouille normalement.
|
||||
proton-mail-bridge-client = {
|
||||
url = "github:googlarz/proton-mail-bridge-client";
|
||||
flake = false;
|
||||
};
|
||||
|
||||
firefox-devtools-mcp = {
|
||||
# Officiel Mozilla, MCP browser automation via WebDriver BiDi. Pin sur le
|
||||
# commit de la release v0.10.4 (2026-09-22) ; flake=false, source brute.
|
||||
@@ -176,15 +165,7 @@
|
||||
self,
|
||||
nixpkgs,
|
||||
...
|
||||
} @ inputs: let
|
||||
# Sous-flake local ./protonmail-mcp appelé directement (pas d'input path:),
|
||||
# avec les inputs passés explicitement depuis le flake racine.
|
||||
protonmailMcp = (import ./protonmail-mcp/flake.nix).outputs {
|
||||
self = { };
|
||||
nixpkgs = inputs.nixpkgs;
|
||||
proton-mail-bridge-client = inputs.proton-mail-bridge-client;
|
||||
};
|
||||
in {
|
||||
} @ inputs: {
|
||||
nixosConfigurations = {
|
||||
laptop = nixpkgs.lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
@@ -203,11 +184,6 @@
|
||||
./profiles/common
|
||||
./profiles/tower/configuration.nix
|
||||
./services
|
||||
# protonmail-mcp = tower uniquement (le module du input déclare
|
||||
# l'option ; les autres hôtes ne l'importent donc pas). Cf.
|
||||
# services/default.nix où l'import est commenté.
|
||||
./services/protonmail-mcp
|
||||
protonmailMcp.nixosModules.default
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
@@ -7,11 +7,6 @@
|
||||
imports = [
|
||||
../../hosts/tower/disko.nix
|
||||
../../hosts/laptop/hardware-configuration.nix
|
||||
# config herdr de marc (~/.config/herdr/config.toml) : affiche le modele
|
||||
# hermes de chaque pane dans la sidebar. Voir modules/nixos/herdr/.
|
||||
# Posee cote systeme (symlink vers le store) : le build systeme (root) ne
|
||||
# construit PAS la conf home-manager de marc, qui reste standalone
|
||||
# (`HM=marc just home` / homeConfigurations.marc).
|
||||
../../modules/nixos/herdr
|
||||
];
|
||||
|
||||
@@ -27,35 +22,26 @@
|
||||
"a /var/lib/hermes/.hermes/plugins - - - - u:marc:rwx,d:u:marc:rwx,m::rwx,d:m::rwx"
|
||||
];
|
||||
|
||||
boot.kernelParams = ["panic=10"]; # reboot 10 s après un kernel panic, le dump reste dans pstore
|
||||
systemd.settings.Manager.RuntimeWatchdogSec = "30s"; # reboot si la machine gèle (si `sudo wdctl` trouve un watchdog)
|
||||
# vkms = écran virtuel. Sans écran branché (DP-1/DP-2/HDMI-A-1 en disconnected sur
|
||||
# le Renoir), niri n'a AUCUNE sortie vidéo : wayvnc sort en "No output found" et
|
||||
# il n'y a rien à afficher en VNC. vkms crée une sortie virtuelle que niri adopte.
|
||||
boot.kernelParams = ["panic=10"];
|
||||
systemd.settings.Manager.RuntimeWatchdogSec = "30s";
|
||||
boot.kernelModules = ["vkms"];
|
||||
# boot.initrd.clevis.enable = true;
|
||||
# boot.initrd.clevis.devices."zroot/root".secretFile = ./zfs.jwe; # ton encryptionroot
|
||||
# boot.initrd.availableKernelModules = ["tpm_crb"]; # driver du fTPM AMD
|
||||
boot.initrd.secrets."/etc/secrets/zfs-root.key" = "/etc/secrets/zfs-root.key"; # entre guillemets, sinon la clé finit dans le /nix/store lisible par tous
|
||||
boot.initrd.secrets."/etc/secrets/zfs-root.key" = "/etc/secrets/zfs-root.key";
|
||||
|
||||
networking.firewall = {
|
||||
enable = true;
|
||||
|
||||
allowedTCPPorts = [
|
||||
2022
|
||||
8083 # Pangolin / Apps
|
||||
8050
|
||||
5432 # PostgreSQL
|
||||
6080 # noVNC (client VNC web de la session niri) ; wayvnc reste sur loopback
|
||||
6080 # noVNC (client VNC web de la session niri)
|
||||
8089 # rustguac
|
||||
8443 # KasmVNC
|
||||
8200 # Vault
|
||||
21115 21116 21118 # RustDesk
|
||||
];
|
||||
};
|
||||
|
||||
# Clé ed25519 du Mac de marc : connexion Hermes Desktop de type SSH
|
||||
# (user@host = hermes@tower, auth par clé, pas de mot de passe).
|
||||
# Déclaratif exprès : /var/lib/hermes est en 2770 (écriture groupe, requis par
|
||||
# le partage hermes/marc) et sshd (StrictModes, cf services/hermes) ignore
|
||||
# alors %h/.ssh/authorized_keys. Cette option écrit /etc/ssh/authorized_keys.d/
|
||||
# hermes en root 444, qui passe le contrôle, et survit aux rebuilds.
|
||||
users.users.hermes.openssh.authorizedKeys.keys = lib.mkAfter [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMdX5m7b8xWL/9ZUeFRxahB4YY0v2rAV5CCFv8xTOlUh marc@Air-de-Marc"
|
||||
];
|
||||
@@ -63,12 +49,126 @@
|
||||
networking.hostName = "tower";
|
||||
sops.defaultSopsFile = lib.mkForce ../../secrets/tower.yml;
|
||||
|
||||
# Token Matrix du bot @hermes : seul tower est destinataire du fichier sops
|
||||
# (secrets/hermes-matrix.yml). Les autres hôtes qui importent services/hermes
|
||||
# (laptop, anywhere) ne doivent PAS le déclarer. Voir services/hermes.
|
||||
services.hermes.enableMatrixToken = true;
|
||||
|
||||
# Token du bot Discord dédié (voix + texte natifs, profil `discord-bridge`) :
|
||||
# même règle, secrets/hermes-discord.yml n'a que tower comme destinataire.
|
||||
services.hermes.enableDiscordToken = true;
|
||||
|
||||
# ========================================================================
|
||||
# RUSTGUAC - Gateway Rust (remplace Guacamole Java)
|
||||
# ========================================================================
|
||||
services.rustguac = {
|
||||
enable = true;
|
||||
host = "127.0.0.1";
|
||||
port = 8089;
|
||||
guacd = { host = "127.0.0.1"; port = 4822; tls = true; };
|
||||
vault = {
|
||||
address = "http://127.0.0.1:8200";
|
||||
token = "/run/secrets/vault-rustguac-root-token";
|
||||
mountPath = "secret";
|
||||
connectionsPath = "rustguac/connections";
|
||||
};
|
||||
oidc = {
|
||||
issuerUrl = "https://auth.example.com"; # TODO: remplacer par ton OIDC
|
||||
clientId = "rustguac";
|
||||
clientSecret = "/run/secrets/rustguac-oidc-client-secret";
|
||||
redirectUrl = "https://guac.example.com/api/oidc/callback";
|
||||
scopes = [ "openid" "profile" "email" "groups" ];
|
||||
usernameClaim = "email";
|
||||
groupsClaim = "groups";
|
||||
};
|
||||
recording = {
|
||||
enable = true;
|
||||
storagePath = "/var/lib/rustguac/recordings";
|
||||
maxSize = "10G";
|
||||
maxAge = "30d";
|
||||
};
|
||||
rateLimit = { enabled = true; requestsPerMinute = 120; };
|
||||
};
|
||||
|
||||
# ========================================================================
|
||||
# KASMVNC - Bureau personnel Wayland haute perf (LXQt + kwin_wayland)
|
||||
# ========================================================================
|
||||
services.kasmvnc = {
|
||||
enable = true;
|
||||
user = "marc";
|
||||
display = ":1";
|
||||
geometry = "1920x1080";
|
||||
port = 8443;
|
||||
tls = {
|
||||
certFile = "/run/secrets/kasmvnc-cert-pem";
|
||||
keyFile = "/run/secrets/kasmvnc-key-pem";
|
||||
};
|
||||
passwordFile = "/home/marc/.kasmpasswd";
|
||||
desktop = "lxqt-wayland";
|
||||
};
|
||||
|
||||
# ========================================================================
|
||||
# VAULT - Dev mode pour rustguac (stockage connexions)
|
||||
# ========================================================================
|
||||
services.vault-rustguac = {
|
||||
enable = true;
|
||||
mode = "dev";
|
||||
address = "127.0.0.1";
|
||||
port = 8200;
|
||||
devRootToken = "/run/secrets/vault-rustguac-root-token";
|
||||
ui = true;
|
||||
};
|
||||
|
||||
# ========================================================================
|
||||
# RUSTDESK - Serveur P2P (hbbs + hbbr)
|
||||
# ========================================================================
|
||||
services.rustdesk = {
|
||||
enable = true;
|
||||
hbbs = { enable = true; port = 21115; };
|
||||
hbbr = { enable = true; port = 21116; apiPort = 21118; };
|
||||
openFirewall = true;
|
||||
};
|
||||
|
||||
# ========================================================================
|
||||
# NGINX - Reverse proxy avec ACME pour les deux services
|
||||
# ========================================================================
|
||||
services.nginx.rustguac = {
|
||||
enable = true;
|
||||
domain = "guac.example.com"; # TODO: ton domaine
|
||||
upstream = "http://127.0.0.1:8089";
|
||||
acme = true;
|
||||
};
|
||||
|
||||
services.nginx.kasmvnc = {
|
||||
enable = true;
|
||||
domain = "kasm.example.com"; # TODO: ton domaine
|
||||
upstream = "https://127.0.0.1:8443";
|
||||
acme = true;
|
||||
};
|
||||
|
||||
# ========================================================================
|
||||
# SECRETS (sops-nix)
|
||||
# ========================================================================
|
||||
sops.secrets = {
|
||||
vault-rustguac-root-token = {
|
||||
sopsFile = ../../secrets/tower.yml;
|
||||
owner = "root";
|
||||
mode = "0400";
|
||||
};
|
||||
rustguac-oidc-client-secret = {
|
||||
sopsFile = ../../secrets/tower.yml;
|
||||
owner = "root";
|
||||
mode = "0400";
|
||||
};
|
||||
kasmvnc-cert-pem = {
|
||||
sopsFile = ../../secrets/tower.yml;
|
||||
owner = "root";
|
||||
mode = "0400";
|
||||
};
|
||||
kasmvnc-key-pem = {
|
||||
sopsFile = ../../secrets/tower.yml;
|
||||
owner = "root";
|
||||
mode = "0400";
|
||||
};
|
||||
"pangolin/api_key" = {
|
||||
sopsFile = ../../secrets/common.yml;
|
||||
};
|
||||
};
|
||||
|
||||
# User lingering pour KasmVNC systemd user service
|
||||
users.users.marc.linger = true;
|
||||
}
|
||||
Generated
-44
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1790218706,
|
||||
"narHash": "sha256-6e4Na3z008XpdVyOXgfasXIn1aN+z8AXFG+jXdQSyuI=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c508844df6c28fa6dabc1b6af70f3ccbd65c5201",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-26.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"proton-mail-bridge-client": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1790247463,
|
||||
"narHash": "sha256-AoIuoIb8us2DfyjgfoU0/AouILBnSIVNB1GGtw+AYH0=",
|
||||
"owner": "googlarz",
|
||||
"repo": "proton-mail-bridge-client",
|
||||
"rev": "811be5de9dda5debf4d41ceae22b69c9fed8a42f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "googlarz",
|
||||
"repo": "proton-mail-bridge-client",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs",
|
||||
"proton-mail-bridge-client": "proton-mail-bridge-client"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -1,275 +0,0 @@
|
||||
# Proton Mail MCP pour NixOS
|
||||
#
|
||||
# - packages.<system>.default : proton-mail-bridge-client (serveur MCP + CLI)
|
||||
# https://github.com/googlarz/proton-mail-bridge-client
|
||||
# - nixosModules.default : Proton Mail Bridge headless (service système)
|
||||
# + entrée MCP prête à brancher dans Hermes
|
||||
#
|
||||
# Flake local (sous-dossier de ce repo), branché en path: depuis le flake.nix
|
||||
# racine (pas de repo GitHub séparé pour l'instant) :
|
||||
# inputs.protonmail-mcp.url = "path:./protonmail-mcp";
|
||||
# inputs.protonmail-mcp.inputs.nixpkgs.follows = "nixpkgs";
|
||||
# modules = [ inputs.protonmail-mcp.nixosModules.default ];
|
||||
# (déplaçable vers "github:MarcPartensky/<repo>" si ça sort un jour dans son
|
||||
# propre repo : même wiring, juste changer l'url ci-dessus.)
|
||||
#
|
||||
# Sur l'hôte Hermes (Bridge et le MCP doivent tourner sur la même machine) :
|
||||
# sops.secrets."protonmail/bridge-password".owner = "hermes";
|
||||
# services.protonmail-mcp = {
|
||||
# enable = true;
|
||||
# username = "marc.partensky@proton.me";
|
||||
# passwordFile = config.sops.secrets."protonmail/bridge-password".path;
|
||||
# };
|
||||
# services.hermes-agent.mcpServers.protonmail = config.services.protonmail-mcp.mcpServer;
|
||||
#
|
||||
# Premier login Bridge (une seule fois) : sudo protonmail-bridge-login
|
||||
# puis dans le CLI : login, info (copier le mot de passe Bridge dans sops), exit
|
||||
{
|
||||
description = "Proton Mail MCP (proton-mail-bridge-client) + Proton Mail Bridge headless";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
|
||||
proton-mail-bridge-client = {
|
||||
url = "github:googlarz/proton-mail-bridge-client";
|
||||
flake = false;
|
||||
};
|
||||
};
|
||||
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
proton-mail-bridge-client,
|
||||
}:
|
||||
let
|
||||
systems = [
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
"aarch64-darwin"
|
||||
];
|
||||
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system});
|
||||
|
||||
mkPackage =
|
||||
pkgs:
|
||||
pkgs.callPackage (
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
buildNpmPackage,
|
||||
importNpmLock,
|
||||
}:
|
||||
let
|
||||
src = proton-mail-bridge-client;
|
||||
package = lib.importJSON "${src}/package.json";
|
||||
lock = lib.importJSON "${src}/package-lock.json";
|
||||
# TypeScript 7 = un binaire natif par plateforme (~10 Mo chacun, 20 plateformes) :
|
||||
# on ne garde que celui de la machine qui build
|
||||
tsPrefix = "node_modules/@typescript/typescript-";
|
||||
tsPlatform =
|
||||
{
|
||||
x86_64-linux = "linux-x64";
|
||||
aarch64-linux = "linux-arm64";
|
||||
aarch64-darwin = "darwin-arm64";
|
||||
}
|
||||
.${stdenv.hostPlatform.system};
|
||||
in
|
||||
buildNpmPackage {
|
||||
pname = package.name;
|
||||
inherit (package) version;
|
||||
inherit src;
|
||||
|
||||
# Deps lues depuis package-lock.json : aucun hash à maintenir,
|
||||
# `nix flake update` suffit pour monter de version
|
||||
npmDeps = importNpmLock {
|
||||
inherit package;
|
||||
packageLock = lock // {
|
||||
packages = lib.filterAttrs (
|
||||
name: _: !(lib.hasPrefix tsPrefix name) || name == tsPrefix + tsPlatform
|
||||
) lock.packages;
|
||||
};
|
||||
};
|
||||
npmConfigHook = importNpmLock.npmConfigHook;
|
||||
|
||||
# better-sqlite3 compilé depuis les sources (pas de réseau dans le sandbox)
|
||||
env.npm_config_build_from_source = "true";
|
||||
# le script "prepare" relancerait tsc pendant `npm pack`
|
||||
npmPackFlags = [ "--ignore-scripts" ];
|
||||
|
||||
meta = {
|
||||
description = "MCP server and CLI for Proton Mail through Proton Mail Bridge";
|
||||
homepage = "https://github.com/googlarz/proton-mail-bridge-client";
|
||||
license = lib.licenses.mit;
|
||||
mainProgram = "proton-mail-bridge-mcp";
|
||||
platforms = systems;
|
||||
};
|
||||
}
|
||||
) { };
|
||||
in
|
||||
{
|
||||
packages = forAllSystems (pkgs: rec {
|
||||
proton-mail-bridge-client = mkPackage pkgs;
|
||||
default = proton-mail-bridge-client;
|
||||
});
|
||||
|
||||
overlays.default = final: _prev: {
|
||||
proton-mail-bridge-client = mkPackage final;
|
||||
};
|
||||
|
||||
nixosModules.default =
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.services.protonmail-mcp;
|
||||
user = "protonmail-bridge";
|
||||
home = "/var/lib/protonmail-bridge";
|
||||
|
||||
# Premier login : coupe le service et ouvre le CLI Bridge sous l'utilisateur dédié
|
||||
loginHelper = pkgs.writeShellApplication {
|
||||
name = "protonmail-bridge-login";
|
||||
runtimeInputs = [
|
||||
pkgs.coreutils
|
||||
config.systemd.package
|
||||
];
|
||||
text = ''
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "À lancer avec sudo" >&2
|
||||
exit 1
|
||||
fi
|
||||
systemctl stop protonmail-bridge.service
|
||||
trap 'systemctl start protonmail-bridge.service' EXIT
|
||||
echo "CLI Bridge : login, puis info (mot de passe à mettre dans sops), puis exit"
|
||||
systemd-run --pty --wait --collect --quiet \
|
||||
--uid=${user} --gid=${user} \
|
||||
--setenv=HOME=${home} \
|
||||
--setenv=PATH=${
|
||||
lib.makeBinPath [
|
||||
pkgs.pass
|
||||
pkgs.gnupg
|
||||
]
|
||||
} \
|
||||
${lib.getExe cfg.bridgePackage} --cli
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
options.services.protonmail-mcp = {
|
||||
enable = lib.mkEnableOption "Proton Mail Bridge headless et le serveur MCP proton-mail-bridge-client";
|
||||
|
||||
package = lib.mkOption {
|
||||
type = lib.types.package;
|
||||
default = mkPackage pkgs;
|
||||
defaultText = lib.literalMD "`proton-mail-bridge-client` construit par ce flake";
|
||||
description = "Paquet du serveur MCP.";
|
||||
};
|
||||
|
||||
bridgePackage = lib.mkPackageOption pkgs "protonmail-bridge" { };
|
||||
|
||||
username = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
example = "you@proton.me";
|
||||
description = "Adresse Proton (identifiant IMAP/SMTP de Bridge).";
|
||||
};
|
||||
|
||||
passwordFile = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
example = "/run/secrets/protonmail/bridge-password";
|
||||
description = "Fichier contenant le mot de passe généré par Bridge (commande info du CLI), pas celui du compte Proton. Doit être lisible par le client MCP.";
|
||||
};
|
||||
|
||||
environment = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.str;
|
||||
default = { };
|
||||
example = {
|
||||
PROTONMAIL_ALLOW_SEND = "true";
|
||||
PROTONMAIL_RESTRICT_OUTBOUND_TO_SELF = "true";
|
||||
};
|
||||
description = "Variables PROTONMAIL_* supplémentaires, prioritaires sur les garde-fous par défaut.";
|
||||
};
|
||||
|
||||
mcpServer = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.anything;
|
||||
readOnly = true;
|
||||
defaultText = lib.literalMD "`command` + `env` dérivés des options ci-dessus";
|
||||
description = "Entrée MCP stdio prête à l'emploi, pour services.hermes-agent.mcpServers.";
|
||||
default = {
|
||||
command = lib.getExe cfg.package;
|
||||
env = {
|
||||
PROTONMAIL_USERNAME = cfg.username;
|
||||
PROTONMAIL_PASSWORD_FILE = cfg.passwordFile;
|
||||
# Garde-fous (l'agent lit des mails écrits par n'importe qui) :
|
||||
# lecture + brouillons, ni envoi ni suppression, 25 outils au lieu de 96
|
||||
PROTONMAIL_TOOL_TIER = "core";
|
||||
PROTONMAIL_ALLOW_SEND = "false";
|
||||
PROTONMAIL_ALLOWED_ACTIONS = "mark_read,mark_unread,star,unstar,archive";
|
||||
}
|
||||
// cfg.environment;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = !config.services.protonmail-bridge.enable;
|
||||
message = "services.protonmail-mcp lance déjà Proton Bridge : désactive services.protonmail-bridge.";
|
||||
}
|
||||
];
|
||||
|
||||
users.users.${user} = {
|
||||
isSystemUser = true;
|
||||
group = user;
|
||||
inherit home;
|
||||
};
|
||||
users.groups.${user} = { };
|
||||
|
||||
systemd.services.protonmail-bridge = {
|
||||
description = "Proton Mail Bridge (headless)";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
path = [
|
||||
pkgs.pass
|
||||
pkgs.gnupg
|
||||
];
|
||||
# Trousseau "pass" exigé par Bridge : clé GPG sans phrase de passe,
|
||||
# protégée par les droits 0700 du dossier d'état
|
||||
preStart = ''
|
||||
if ! gpg --list-secret-keys ${user} >/dev/null 2>&1; then
|
||||
gpg --batch --pinentry-mode loopback --passphrase "" \
|
||||
--quick-gen-key ${user} default default never
|
||||
fi
|
||||
[ -f "$HOME/.password-store/.gpg-id" ] || pass init ${user}
|
||||
'';
|
||||
serviceConfig = {
|
||||
User = user;
|
||||
Group = user;
|
||||
StateDirectory = "protonmail-bridge";
|
||||
StateDirectoryMode = "0700";
|
||||
ExecStart = "${lib.getExe cfg.bridgePackage} --noninteractive";
|
||||
Restart = "always";
|
||||
RestartSec = 10;
|
||||
NoNewPrivileges = true;
|
||||
PrivateTmp = true;
|
||||
PrivateDevices = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
RestrictSUIDSGID = true;
|
||||
LockPersonality = true;
|
||||
};
|
||||
};
|
||||
|
||||
environment.systemPackages = [
|
||||
cfg.package
|
||||
loginHelper
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
+11
-11
@@ -10,7 +10,7 @@ wifi_ap25g: ENC[AES256_GCM,data:IaMchO3+mJSsB5o=,iv:ToxPMHuj5nVewMJxNnopCmQkuizd
|
||||
wifi_hkt: ENC[AES256_GCM,data:yh1n7dHSUof/6zM=,iv:57B721emA50vRPXhjDMNFsm/nZBHpFdsBAlatQdCpUQ=,tag:T8hnp/65Nf45+zcpxBfzMQ==,type:str]
|
||||
wifi_wifi_de_marc: ENC[AES256_GCM,data:8iQ4dOFboI/8pUkVRhgC7/jDXCo=,iv:SAlVjCtoljMb7tODHPcfrTgqkZiPjrcRLnMN38HVudo=,tag:FQa3fqPm5E++azNrdmmFJg==,type:str]
|
||||
wifi_wifi_de_marc_2_4g: ENC[AES256_GCM,data:ER7IAhvODiuVjao8c5or2+hMc/o=,iv:J9JOqQ0ifqGztblmdRR9GrtJz24Pv3e0+Y6QkH1vSHo=,tag:JfzAC09aQi7fgzKKoMSFyA==,type:str]
|
||||
pangolin_env: ENC[AES256_GCM,data:im5qDycZbiPiVDpBsyCkzVwNo5z+EfWdpg2wooKEN37sy+2ZjKy1ZIWGu79dEKE95sric/Hmr9XQfL4vecZm9IJGh5jT3fZ+PggdzthlPOr3vIUjsuXIfEJPretOvrNHRx6DVH7/MbZWt2pciku/Oa8=,iv:/VuxUTRNcjowerLMTDrUVFDNSZBe/ZPsxE6IHJwsNJ8=,tag:1tjyu9R1/HUsMb68j5R+aw==,type:str]
|
||||
pangolin_env: ENC[AES256_GCM,data:HM/s+i55RgHLSL4KNwsyEUEMTy3zSrSrwlmWt7/pb3z11htSHZDjNIjPJHPdfq7gvXSYZGGValgglrCxuwD4Bhs3k97e0H4f28WSu4ZQH5ikvRZKeySdpKGK7M5OAVYwo8C7rNeLDbIz5sNghaH8xc3yUNzsn9y4J29ufUiZ1wwzxITsCL0yOabN3GJc1s0lpLlh5M140kAjBueT9BgBj9+uLwUo2yRD8V/0m5Qk0bnSOGP86eSopu8gDQ==,iv:YMVlcailneap9tYuGx9KFBo6cANaYR15091uWysXOIY=,tag:6/ralsNNHf0ZOqu+SOu0pw==,type:str]
|
||||
newt_env: ENC[AES256_GCM,data:ZvbK5GLIdZW4Ry9VO8ssqYEKFThOqe4x391sK/SD6M5XPCct7L49apyNu7PygtnNwJbAg+qW4TD6SlWRYMYBGrvD6GEN5CBA2W5WkxVb1X/LEVM+q/DIAQGnJh1ZZ9Tle1ZJLScWDG0w9JhTCZ6z+rYRjmxuwu4cAd2nmBeJqLK1PgYWoX4/0rO9ns5wog==,iv:ptI1gXb8Q1T2UIKFW3DGmbg6sVlxwnsoQ4GvRCnSTrQ=,tag:OLLs2htywljkus4u7EYhAg==,type:str]
|
||||
zitadel_db_password: ENC[AES256_GCM,data:SoWp+EaEr8Vvz+PQ1CgreLIMXV3bzUir9HmF6g==,iv:QoWO+Iqio5JZpVm6NfI6tMp4C+1ekj04/4HJbjSXfLA=,tag:+d5cWEmJpk8PyyZ5F//XuQ==,type:str]
|
||||
zitadel_admin_password: ENC[AES256_GCM,data:WzJ9gNMC9Co7qj9sdeu+ftr5AqjWjwUaYBWNJVqDEg==,iv:LLNkLn7o7DMOKI2ATePGUAlti3gpQ9hOoVlVVpHF6Jc=,tag:PkbXTbCNRzpptgnPtyVbFw==,type:str]
|
||||
@@ -28,8 +28,7 @@ postgres_crt: ENC[AES256_GCM,data:dTWwOxo2KtFhQkNZlBwn60hspmksNohV3kwMwBD6zzluzQ
|
||||
postgres_key: ENC[AES256_GCM,data:qvahOvxQLzWe5IpG4+qvJQE8MgfM2vx9MKfW1AuXm1euXS5dPvb4q1bLGsOJx27Z1vFAgmu/W1mgCLUx83awHEoYZwKIYabUHy8jcZdg9yZQQNkUrWi+CiVZzEw944ZyVgkC/AACi8Hu3kLWxAj4qcVWlkquOACUaEdIrFbp10AxFSU85YW/tJT7yIxrrdFJu5GGSCJvbR3HKT9vFaCxbzFK8ZavOfGLfoJ3WjAtI80z2Y2cw+hEsDYz5rcoC2fjPbSMD7Gwnnx+bBJ1M7ZqQaP5JctyPPbbsLxRqP32FveDKrJRYT1FmYX6Vz9KAu3FiphFKN1V+IBW9CMyBWWr2sYcpRE74WbOCrJxc9LCo37Py91LjGlpjHfu+ngHj+hsfm60KQRmhIPW1pfw86Q572C8OuhoZPcpHGzdB5NDErgchyhQJVbG4ufU3+1KuM2ioTzUv1CqfJHfEcpngR7ziXO0Xu5UyAJwMiFtSScZm+/KaO6noA90nr/AZBQEjOksj2XLPny1yF2iliyI2vmiADFbjG3Ffb0PZZBQtE/2EiB5Ejbphay1KfKY8nF9ScI0g0VlB/RASWyQOoSOjVeKbyPHpLPefOUd3iqPUmnUYZUA33v5iPhiB3dGydE/ETIsZpXGNYlIiy61bXbp4IMvxa7Mif5e++5xcOQADEVZSLWxMy8Y0E164Z+xVKfTa1OzCdsXkrJYqEt012Y8FL10SiSCd20R3vW3uAs7zVtZyzFm2IQmEJHwbm/R9Cnh6lksq9lSks9m33qHLjF0/HhTjpwbGRTUA51TA+fi6frtVVLWe/JMaflql2p9gdO5HMvfIZCFtcXpQprdtECF7tkiLh9wNcg3k7XqWVBGReLDfoiNiYDbBJjUJ1Gr/FTnObvzodqSwXqWeBZO9qE2WL5OKmU+G7uDmkLxEkgxiwZ3Ez0SKu5DifX53wUaNpXtyJOmyKVRsYllSCPiFu1wkYwX/+W29RkMGPJCFnJjMnRFKZHERS7Lo0QUeWoMB+dPDixpU061Xcx2K73AfprvtcXLwwnL5O8KihTmzLQxz0iHd3q+ir3bzMeOsX5jmopwrlym69XaV0x+fnr+vZ93Fu6VNHxdJU53gekgFdkZHBeyideERqRqEGioO4VS2Vmx4jPpDOIV+Oa9P3NPxQVNgWqdPdcUMaJQ1x5sO7xguKdZLww4s0Y4BGhd0aCs8RtKocJjIjoUxpeKMR2CS0XnzftSa49pGDtywUND9oklX2eX1X8njexH5cXKaAV0qVNvskegNNu3eT6xGdloBKei/JgwfzCh/rO3VNEEhFGEadh/s3vM2yj3+xR7MNiZW5jox14XYJYzO2AcIbYmRhxAyASisuKzNxjLqh0Jl9w02dJcDNlQEa2pKL2wifvfvWQlgHuUmJkfsURgGjnD/kI+GRvgQ7MRNbes2LeLM2k6avnFRJIYoqXCQ0DdG/yd7JvmsS/gfQadP90+2w==,iv:GutSu4hsM/5vpj8kBIr3etyJydKblaopparN4/6Tf94=,tag:Yt1k6rKyeSP4hZKuZH5qyw==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- recipient: age189v3q8quejkaz4r8u0g2aechsfk4sk3465khx35erd320rea5scq98v7er
|
||||
enc: |
|
||||
- enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2dFgyd1VSWUxLZTZNbkZa
|
||||
c0x5Uk9EOGtBbTJXQzVwQWpGMFAyRkRmQWlrClVBSzFxR3VtVEdFdVpOZ21xcXl4
|
||||
@@ -37,8 +36,8 @@ sops:
|
||||
R1FONm52SFYvZ2hnUm9MOWhBbGU4cFUKAzvxN3xv9DeRmlWxVKjLQjvifRLkuzOL
|
||||
kg1mjiNqbLDTge6wFyjmpOpOpuqDzlv7lLJH0Af3yLt1ghPHeiehcg==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1usm3v0ugducrzcwud60dda7xfnj49fdhecj7kcsfm7dzwzrzeyqq2jc2hm
|
||||
enc: |
|
||||
recipient: age189v3q8quejkaz4r8u0g2aechsfk4sk3465khx35erd320rea5scq98v7er
|
||||
- enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXWmlpZFBqM1lteWhacHcx
|
||||
ZDdZZUpQZEdCaVI5b3kyZkt5VEMveVNYbzFJCnEvYVlFRExzR0M5WjRZUjNvbGcw
|
||||
@@ -46,8 +45,8 @@ sops:
|
||||
VWFSakJvZTJFOXBkaEVOaDYrSlo2aFEKyN2A4vPJ8szeIhwVC/VExXj7kDggJE02
|
||||
FtzFIiwHVLzWkIlUc6SFFVX43Q1Z3aYmQtnDuXDBZVOSQfMtg96eew==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1fwgg9h68zz8s6xa8qt4yn7gqcqp59s0mma8xdfq9hunksqv8fq4qd9yfaz
|
||||
enc: |
|
||||
recipient: age1usm3v0ugducrzcwud60dda7xfnj49fdhecj7kcsfm7dzwzrzeyqq2jc2hm
|
||||
- enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0N1FWaVV4MGNnWmExaW8r
|
||||
UjlnMlZEQnlrSDA0RERONXBYdTM3bDFLZ0VNCmEvVFhXT2lhMHV0QlRWbXNFYWRE
|
||||
@@ -55,8 +54,8 @@ sops:
|
||||
U0NCT0ptR3dlNjdRVlMwZTR4UDRGWEkKiLgiuZWVKYGEtoh4o0iEwdzzngUAkFn1
|
||||
YRpNjwSqiCIsoerTyN7oghbRnNKY8kSl4TyNGiaxwUprLdzEqgwLbw==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1rhnaejc5nwcqcuq5qt45wzr2fncurlwqw0adhnavfraqsdzffatqh5yhl4
|
||||
enc: |
|
||||
recipient: age1fwgg9h68zz8s6xa8qt4yn7gqcqp59s0mma8xdfq9hunksqv8fq4qd9yfaz
|
||||
- enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXcXFPV0g4c2ZSOWdhZGhl
|
||||
SUh1K2NMSWEyTWU3SzJIUGd1L3g0MEdlVWs4CmhXdTkxT3lQYWdDa2ljYkg5cXUz
|
||||
@@ -64,7 +63,8 @@ sops:
|
||||
anFCMmpFNzBob21LMUprdzkwV1IxUHMKQ+BcKYxGY3Lph/byMfUjf3pDgue2Q7up
|
||||
PbCNf5QMRd82DCYnCIMk5WD/A5kQArnd+jDjaHuaQp14SRdZ4ph+8Q==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2026-07-28T18:10:33Z"
|
||||
mac: ENC[AES256_GCM,data:mb/27a5tLqslt3EGsNa6XzmszVWCJmo48y5jLeZsiumVXA3iN/h85aMUHE4g8cPddaG23HWkmroEfFlOgEGr90rfTJL2tw0ASUU4x4GCdHqg9m3mjsnHQU0m8O+YtZLBqwOKTXfznxSYPY/WibFLpyg4frDHlNkkodNoJGloUQs=,iv:euuX0ALHrfBkZmpjspHuyzAbwmtB8RRtqHo2BX6TA9E=,tag:ExRBXAHTHftiwstEuCnKHQ==,type:str]
|
||||
recipient: age1rhnaejc5nwcqcuq5qt45wzr2fncurlwqw0adhnavfraqsdzffatqh5yhl4
|
||||
lastmodified: "2026-09-28T04:48:43Z"
|
||||
mac: ENC[AES256_GCM,data:SlyWkzQqqgsDT1uLC5LwWhPhGyexjK1+7Qq02OECNXmr9dhoGzNf76+G1nCoLP5czd4MpaCnk7//aN6Qs6M9DhBV8GG4wDN9pFGWq7JgE7GvPTzJ3J3Ady+yaat3k1HdAE775lIHERa79lxMurzJn66ctgSmsYuIf52qf1WI3GU=,iv:kbawvEBfwyJHYJ0+Q1e0fGTmudPj+mwptt/NxH1u87o=,tag:UYBB28ndbNFSdV0Pf0ncAw==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.13.3
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
hermes_api_server_env: ENC[AES256_GCM,data:HXrlAYPQ1BGc5G2JZrJ59cpnKHcDDi/lFCANSOxWOl1acXFfjemUNwHu5rf0iacb4KNbvhTPGd7U0tQ7tHdO/Sl1HZIacerovZsNA8yplQ==,iv:g5Uj1YLVwOJDYCkZ9EZi2EvMONq+MEY3ZKIoTCh1rWE=,tag:uH/S8NMAx2POMfbMkOWHRQ==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6cmc4MzlkRUMzdTE1QktD
|
||||
aU5jQnQ2UlhrMXlrSmFMQUU5Z1duWDRTdlJJCmVZZVZlWElhOCtPbUJwR3BrbTdm
|
||||
YzNJZFZlbGJKV3RsWFJNTVRJNkRNajQKLS0tIHNXREhldlg4WStGSFBwMGd2YUZu
|
||||
N1AxQkdUWS9sS1U2UUdPSWs2dUVqZGMK/Y4SNZp0OSFexe/O0rrVT6VOKRicMK2L
|
||||
1arpCWGU9tSk2DH9DlJLmvjmx//buwzHqb+cxn2sxPBpbhdFJGndJg==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age1usm3v0ugducrzcwud60dda7xfnj49fdhecj7kcsfm7dzwzrzeyqq2jc2hm
|
||||
lastmodified: "2026-09-27T21:17:19Z"
|
||||
mac: ENC[AES256_GCM,data:/tW5B1QqJQFkj1radKTyNMcnVs/W4F+kfZAnUKJol11xQVNZnohLFYfpNzjmi2HhARiNkvjdiJtb+Nh/XTuiGUW3XVE2pcgseFxIgbpdnxpoMKbfPPyZ8A0DpSAZ4v6XRrWzqCM7D/JO9FC3CAuVgfhIwLJlfLb82MJRAlI67sg=,iv:M7n6yY80oc3jWbEM3L9TUr7wL0sF4fjCsVIepJslgTI=,tag:75mt57Qz5IFk781F3mGwIw==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.13.3
|
||||
@@ -0,0 +1,16 @@
|
||||
openrouter_api_key: ENC[AES256_GCM,data:+vLLYfzwtIboTanbbDAXYoVRkutdrogZmSHozEoptw7+QKMw7j9llIqWwrc=,iv:+sjK5IJCpTizzzHpGk9F3SExC9adA3d/D8ZLZMb2MVU=,tag:hlX6IZ1DpvuqHhngaZ156g==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZTG0vanhiOW5XSk1neTNQ
|
||||
VWpTZXN4UWVjcDU3bDB4M3dtNzVjOXlqRVZFCkNiQWRCWFdxQy9EaXV3QjZ3QzZ5
|
||||
WHoreTNlQnVRbElOSDhrRC9QSDZWdWMKLS0tIHB4bllPNnBMVmxHM2dWS0o1QVI1
|
||||
V0s5Y29CUzNkcGQzWHk5MXE4aDFPVVUKqPGqKAX1J6J5hgZaVqj6gtMi0J6wImg2
|
||||
qvPwkHzyEeZrOLwghp9P+nuaphE9kcvNHZxUImeLCRW8+CvIDbT6mA==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age1usm3v0ugducrzcwud60dda7xfnj49fdhecj7kcsfm7dzwzrzeyqq2jc2hm
|
||||
lastmodified: "2026-09-29T01:43:17Z"
|
||||
mac: ENC[AES256_GCM,data:AlUGyhoIgbzAHlKIJ91W8KMjegDX53KfrRdfCME3taUjBAcFi7rBCJijbYPtJ7X1zhzj4PnlwB2/jVSTMz6nMSAvJPZvKrFvB8tlaId74+rD3cClCJO5W2rpKBmZkvrF+XD0Vt/UJFT11DQDu81Vdsw7RN6bexf+YA9WjlZg15w=,iv:BECdLTVKzLYjWtIdtmRwj67qDoUwOUwn3meZgUAJiDE=,tag:AAKqS2kteHFzc2r0EAtJ2A==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.13.3
|
||||
@@ -1,16 +0,0 @@
|
||||
bridge_password: ENC[AES256_GCM,data:wQcsTjgeco5adaFu4onNcWkXIEO71MtSRRZsY1sOv4c0hWL31KHLT+hXqNMl,iv:0I4DJ8Ce8NVxBqyOUQuWXoXtNTAvuRTWWXcmyBEH59I=,tag:fyH/4siYdzG/mZcVluUZXw==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOeWxBWTlMS2Ywb1pNT0Zu
|
||||
Zy8vdW5TdUQzUWV3MUJTSW5tTERVLzJlV1VnClUvYTVkRXNSMXZaeTZ4amYvdFRh
|
||||
RVg5UE1RTDFlaTJCYWNkNTlFOG1DNjQKLS0tIGFid1Y3QWU5T1BDQzFyUWMraUpD
|
||||
R1dzTUxjQnRUbzVuZnJ6K01sMVRNdmcKGcjvpi7LYGmIhP1OEjrMqI5ho09Wgtrt
|
||||
z38WGqa5DA3oWTqFURXnSpX4241u3dcFtZSwQ1+HGUm9mUx6ZP5ZSQ==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age1usm3v0ugducrzcwud60dda7xfnj49fdhecj7kcsfm7dzwzrzeyqq2jc2hm
|
||||
lastmodified: "2026-09-24T23:58:23Z"
|
||||
mac: ENC[AES256_GCM,data:22GAQLwjmwEDlydzdSnJl6BcCjereUyKixbZ9mNTtAEj6TuhUE3xtPmx3MFykEEbPo2Im3JrKzoOiFq724rkdrJ7KxowPgQ1oG+aGwDbonyALnzKDQIj8yGPgUhdBpUVA211DXKL6xoA9/xr6PY08PqKq0iuAUqigpF32kZLEAU=,iv:AMz/92xIQuNgKsp0AKp766JRiWRns5wO+QYoXOnZcfs=,tag:3S1ZPtJDXeG1vBrbolkCsA==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.13.3
|
||||
@@ -0,0 +1,17 @@
|
||||
reactive_resume_db_password: ENC[AES256_GCM,data:M4zhT3WA+gq3MVS4423genlHk91kEQ582nYMrUyVuPz29U6VK3V0JkfcH6rzYnN6,iv:0ddtIl7k5yMv9O8N8HoxxVeF0iWJ/hyMsVjGXbAPiCs=,tag:Dgr7FJXMFlRQxU5B1E6apw==,type:str]
|
||||
reactive_resume_auth_secret: ENC[AES256_GCM,data:TYEtZu5L5iO17OEelYepiI47WOMSSU+6qFrBbZ+oQM1Uxf/dP68xFma4yWHKrS7jz2nmvDBhWZWUAiw0V+8fTA==,iv:uLkcABpCHwZ+pYX1fjtxCXwZ6WZzad1EXHz2RlFsrLQ=,tag:gDJszmiFWMjZGIVUFBt4vg==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0QmhFZFZkWkFTSllMOFli
|
||||
L0hHeXNEOGc0ajFWOXRmdVJheHRxcmErR0JVCmwrVDZWQ3pQZkZtN1BKOXNoMnRS
|
||||
bm5OcjlRQ1FTbmV5R3diNW5LeEVJSVEKLS0tIDV6V3o5UWhCNXczZWxESkpOWTFF
|
||||
Rzl5Yk10UmhXR1dBWlRUUnAxaUYvZXMK+ikexhXADk9X9MLm2ZDxsMwSZmvgQ/zG
|
||||
z986Sl4bu3rFIXuUqeueOS0GDznjALIvi2e/OV4CDffKUQEr7DxeFw==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age1usm3v0ugducrzcwud60dda7xfnj49fdhecj7kcsfm7dzwzrzeyqq2jc2hm
|
||||
lastmodified: "2026-09-28T05:26:05Z"
|
||||
mac: ENC[AES256_GCM,data:TdwDkVJ/8CbK2RJ28EBTYB9TwY9pmcCMi74zuQcPjehwbrrTkh9ClSXRopnRSAJjU3Nv/GAyYAeKF5HtxxVFbo0OwmajQQDmAkVOlDKg+LqkEUbBQPwAQ8a3SUFNj6CXf20qBX83aOI9SCiUEWKHvtQ6N+esUQfmRWb7IzDM7S8=,iv:A87PvE9LTuYxE0YbOZbigwdGVxU4iPDrUkx51w9VonQ=,tag:K6D2sKigNbAIUFFFjXo5nw==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.13.3
|
||||
@@ -44,10 +44,6 @@
|
||||
./eternal-terminal
|
||||
./nextcloud-mcp
|
||||
./amazon-mcp
|
||||
# ./protonmail-mcp : tower uniquement (ses options viennent du input
|
||||
# protonmail-mcp, importé par flake.nix pour tower seul). L'import ici
|
||||
# cassait l'éval de laptop/laptop-iso (option `services.protonmail-mcp`
|
||||
# inexistante chez eux). Le fichier reste importé pour tower via flake.nix.
|
||||
./firefox-mcp
|
||||
# ./stalwart-mcp # FIXME: cargoHash mismatch, needs fix
|
||||
# ./gotify
|
||||
|
||||
@@ -132,7 +132,21 @@
|
||||
Environment = ["NC_SETUP_FORCE_RUN=20260924"];
|
||||
# app:enable ne lance PAS les migrations : sans upgrade ensuite, les
|
||||
# tables des apps (oc_calendars...) ne sont jamais créées.
|
||||
ExecStartPost = "${config.services.nextcloud.occ}/bin/nextcloud-occ upgrade";
|
||||
ExecStartPost = [
|
||||
"${config.services.nextcloud.occ}/bin/nextcloud-occ upgrade"
|
||||
# adminpassFile ne fixe le mot de passe QUE lors de l'install initiale
|
||||
# (occ maintenance:install) : sur une instance déjà installée il est
|
||||
# ignoré silencieusement. Ce ExecStartPost force le mot de passe du
|
||||
# compte "root" à la valeur sops à CHAQUE activation (self-healing,
|
||||
# tourne à chaque switch). Ajouté le 28/09/2026 suite à un fichier
|
||||
# services/nextcloud/password.txt committé en clair (repo public) :
|
||||
# rotation forcée pour invalider toute ancienne valeur exposée.
|
||||
"${pkgs.writeShellScript "nextcloud-rotate-admin-password" ''
|
||||
set -euo pipefail
|
||||
export OC_PASS="$(cat ${config.sops.secrets."nextcloud/admin_password".path})"
|
||||
${config.services.nextcloud.occ}/bin/nextcloud-occ user:resetpassword --password-from-env root
|
||||
''}"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
nextcloudpassword
|
||||
@@ -0,0 +1,80 @@
|
||||
# services/openrouter-mcp/default.nix
|
||||
# Serveur MCP OpenRouter (codeChap/mcp-server-open-router) pour hermes.
|
||||
#
|
||||
# 5 outils : chat, chat_with_vision, chat_with_search (recherche web via le
|
||||
# plugin OpenRouter), list_models, credits. Binaire Rust unique, stdio.
|
||||
# Choisi plutôt que le serveur officiel distant (mcp.openrouter.ai, OAuth) :
|
||||
# ce dernier délivre une clé à durée de vie 7 jours (spend cap $10), donc
|
||||
# inutilisable pour une intégration nix déclarative "pose et oublie".
|
||||
#
|
||||
# Le binaire lit sa config depuis $XDG_CONFIG_HOME/mcp-server-open-router/config.toml
|
||||
# (crate `dirs`, pas de variable d'env ni de flag pour le chemin). Le wrapper
|
||||
# régénère ce fichier à chaque lancement depuis le secret sops : la clé n'est
|
||||
# JAMAIS en clair dans le nix store ni dans mcpServers.env.
|
||||
#
|
||||
# Secret : secrets/openrouter-mcp.yml (sops, clé openrouter_api_key).
|
||||
# PLACEHOLDER à remplacer par marc :
|
||||
# nix run nixpkgs#sops -- secrets/openrouter-mcp.yml
|
||||
# (clé OpenRouter : https://openrouter.ai/settings/keys)
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
version = "unstable-2026-09-28";
|
||||
src = pkgs.fetchFromGitHub {
|
||||
owner = "codeChap";
|
||||
repo = "mcp-server-open-router";
|
||||
rev = "f6952c1053121b3a02c87c5f5c7c21f7517445d0";
|
||||
hash = "sha256-rlck7hpyRxwAB+/kR+2eeuOCbmceTMzTvuCqS0kTEV0=";
|
||||
};
|
||||
|
||||
openrouter-mcp = pkgs.rustPlatform.buildRustPackage {
|
||||
pname = "mcp-server-open-router";
|
||||
inherit version src;
|
||||
cargoLock.lockFile = "${src}/Cargo.lock";
|
||||
# reqwest -> openssl-sys a besoin de pkg-config + libs de dev OpenSSL.
|
||||
nativeBuildInputs = [pkgs.pkg-config];
|
||||
buildInputs = [pkgs.openssl];
|
||||
meta = {
|
||||
description = "MCP server for OpenRouter (chat, vision, web search, model list, credits)";
|
||||
homepage = "https://github.com/codeChap/mcp-server-open-router";
|
||||
license = lib.licenses.mit;
|
||||
mainProgram = "open-router";
|
||||
};
|
||||
};
|
||||
|
||||
stateDir = "/var/lib/hermes/openrouter-mcp";
|
||||
configDir = "${stateDir}/config";
|
||||
apiKeyFile = config.sops.secrets."openrouter-mcp/api_key".path;
|
||||
|
||||
# Régénère config.toml à chaque lancement depuis le secret sops (jq -Rs
|
||||
# produit une chaîne TOML/JSON correctement échappée, jamais d'interpolation
|
||||
# brute d'une valeur pouvant contenir des guillemets).
|
||||
wrapper = pkgs.writeShellScript "openrouter-mcp-hermes" ''
|
||||
set -euo pipefail
|
||||
umask 077
|
||||
mkdir -p ${configDir}/mcp-server-open-router
|
||||
key_json=$(printf '%s' "$(cat ${apiKeyFile})" | ${lib.getExe pkgs.jq} -Rs .)
|
||||
printf 'api_key = %s\n' "$key_json" > ${configDir}/mcp-server-open-router/config.toml
|
||||
export XDG_CONFIG_HOME=${configDir}
|
||||
exec ${lib.getExe openrouter-mcp}
|
||||
'';
|
||||
in {
|
||||
sops.secrets."openrouter-mcp/api_key" = {
|
||||
key = "openrouter_api_key";
|
||||
sopsFile = ../../secrets/openrouter-mcp.yml;
|
||||
owner = "hermes";
|
||||
mode = "0400";
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${stateDir} 0700 hermes hermes -"
|
||||
];
|
||||
|
||||
services.hermes-agent.mcpServers.openrouter = {
|
||||
command = "${wrapper}";
|
||||
timeout = 60;
|
||||
};
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
# services/protonmail-mcp/default.nix
|
||||
# Proton Mail Bridge headless (service système) + serveur MCP
|
||||
# proton-mail-bridge-client, câblés via le flake local ../../protonmail-mcp
|
||||
# (nixosModules.default = inputs.protonmail-mcp.nixosModules.default, importé
|
||||
# directement dans flake.nix pour tower).
|
||||
#
|
||||
# Secret sops : secrets/protonmail.yml, fichier SÉPARÉ de tower.yml — hermes
|
||||
# n'a ni le binaire sops ni la clé age privée de tower (lecture seule pour
|
||||
# root), donc pas moyen de rééditer tower.yml en place. Un fichier neuf ne
|
||||
# demande que les clés PUBLIQUES déjà présentes dans .sops.yaml -> chiffrable
|
||||
# par hermes via `nix run nixpkgs#sops -- --encrypt --in-place`.
|
||||
# Contient pour l'instant un mot de passe PLACEHOLDER (Bridge non logué) :
|
||||
# le service démarre mais le MCP n'authentifie pas encore.
|
||||
#
|
||||
# PREMIER LOGIN (une fois, humain — nécessite le vrai mot de passe Proton de
|
||||
# marc.partensky@proton.me, potentiellement 2FA) :
|
||||
# sudo protonmail-bridge-login
|
||||
# Dans le CLI Bridge : login, puis info (copier le mot de passe Bridge généré),
|
||||
# puis exit.
|
||||
# Ensuite : remplacer le placeholder dans secrets/protonmail.yml par ce mot de
|
||||
# passe (re-chiffrement complet du fichier, toujours sans clé privée), puis
|
||||
# décommenter la ligne services.hermes-agent.mcpServers.protonmail ci-dessous
|
||||
# et relancer `just nixos`.
|
||||
{config, ...}: {
|
||||
sops.secrets."protonmail/bridge-password" = {
|
||||
sopsFile = ../../secrets/protonmail.yml;
|
||||
key = "bridge_password";
|
||||
owner = "hermes";
|
||||
};
|
||||
|
||||
services.protonmail-mcp = {
|
||||
# DÉSACTIVÉ : Proton Mail Bridge exige un plan payant. Le login Bridge a été
|
||||
# tenté le 24/09/2026 et refusé par l'API Proton :
|
||||
# 422 POST https://mail-api.proton.me/auth/v4: Please upgrade to a paid plan
|
||||
# to use this client (Code=10004)
|
||||
# L'auto-forwarding est payant lui aussi (même via filtre custom), donc aucune
|
||||
# voie gratuite : la lecture des mails passe par le webmail piloté au
|
||||
# navigateur (services/firefox-mcp + firefox-mcp-login).
|
||||
# Tout est conservé (flake ../../protonmail-mcp, secret, user système) : il
|
||||
# suffit de repasser enable = true si marc prend Mail Plus un jour.
|
||||
enable = false;
|
||||
username = "marc.partensky@proton.me";
|
||||
passwordFile = config.sops.secrets."protonmail/bridge-password".path;
|
||||
};
|
||||
|
||||
# À décommenter seulement après le premier login Bridge (mot de passe réel
|
||||
# dans secrets/protonmail.yml, pas le placeholder) :
|
||||
# services.hermes-agent.mcpServers.protonmail = config.services.protonmail-mcp.mcpServer;
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
# services/reactive-resume/default.nix
|
||||
# Reactive Resume (github.com/AmruthPillai/Reactive-Resume) sur cv.marcpartensky.com.
|
||||
#
|
||||
# Inspiré du docker-compose perso de marc (github.com/marcpartensky/docker,
|
||||
# services/resume/compose.yml), mais simplifié pour la version actuelle de
|
||||
# l'image (>= v5.1) : depuis v5.1 le PDF est généré côté navigateur
|
||||
# (@react-pdf/renderer), donc plus de conteneur "printer" (browserless/chrome)
|
||||
# ni de PRINTER_*/BROWSERLESS_*. Le stockage S3 (Minio dans l'ancien setup) est
|
||||
# optionnel depuis la même version : si les S3_* sont vides l'app stocke les
|
||||
# uploads en local (/app/data) — on garde ça pour éviter de déployer Minio.
|
||||
#
|
||||
# Pas de paquet nixpkgs pour cette app (Node/NestJS) : conteneur OCI via podman
|
||||
# (virtualisation.oci-containers, cf. services/chhoto pour le même pattern).
|
||||
# --network=host (au lieu d'un port mappé) car l'app doit parler à Postgres en
|
||||
# 127.0.0.1:5432 comme un service natif — même convention loopback que le
|
||||
# reste de tower, le firewall reste fermé sur ce port (assertion en bas).
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}: {
|
||||
sops.secrets."reactive-resume/db_password" = {
|
||||
key = "reactive_resume_db_password";
|
||||
sopsFile = ../../secrets/reactive-resume.yml;
|
||||
# lu par reactive-resume-db-password.service (User=postgres) ; l'app elle
|
||||
# même lit le mot de passe via le template sops ci-dessous, pas ce fichier.
|
||||
owner = "postgres";
|
||||
};
|
||||
|
||||
sops.secrets."reactive-resume/auth_secret" = {
|
||||
key = "reactive_resume_auth_secret";
|
||||
sopsFile = ../../secrets/reactive-resume.yml;
|
||||
};
|
||||
|
||||
sops.templates."reactive-resume.env" = {
|
||||
content = ''
|
||||
TZ=America/New_York
|
||||
APP_URL=https://cv.marcpartensky.com
|
||||
PORT=8098
|
||||
DATABASE_URL=postgresql://reactive-resume:${config.sops.placeholder."reactive-resume/db_password"}@127.0.0.1:5432/reactive-resume
|
||||
AUTH_SECRET=${config.sops.placeholder."reactive-resume/auth_secret"}
|
||||
# Inscription ouverte pour l'instant : le point d'entrée est de toute
|
||||
# façon derrière le SSO Pangolin (auth.sso-enabled ci-dessous). Repasser
|
||||
# à true une fois le compte de marc créé si on veut fermer les inscriptions.
|
||||
FLAG_DISABLE_SIGNUPS=false
|
||||
'';
|
||||
};
|
||||
|
||||
services.postgresql = {
|
||||
ensureDatabases = ["reactive-resume"];
|
||||
ensureUsers = [
|
||||
{
|
||||
name = "reactive-resume";
|
||||
ensureDBOwnership = true;
|
||||
}
|
||||
];
|
||||
# types.lines : se concatène avec les règles des autres modules (postgres,
|
||||
# vaultwarden, gitea...) au lieu de les remplacer. Même mkOverride 10 que
|
||||
# vaultwarden, pattern vérifié fonctionnel (cf skill marc-nixos-config).
|
||||
authentication = lib.mkOverride 10 ''
|
||||
host reactive-resume reactive-resume 127.0.0.1/32 md5
|
||||
'';
|
||||
};
|
||||
|
||||
# Aligne le mot de passe du rôle postgres sur le secret sops utilisé par
|
||||
# DATABASE_URL (ensureUsers ne pose pas de mot de passe). Même pattern que
|
||||
# vaultwarden-db-password.
|
||||
systemd.services.reactive-resume-db-password = {
|
||||
description = "Aligne le mot de passe du rôle postgres reactive-resume sur le secret sops";
|
||||
wantedBy = ["multi-user.target"];
|
||||
before = ["podman-reactive-resume.service"];
|
||||
requiredBy = ["podman-reactive-resume.service"];
|
||||
after = ["postgresql.service"];
|
||||
requires = ["postgresql.service"];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "postgres";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
script = ''
|
||||
${config.services.postgresql.package}/bin/psql \
|
||||
-v pw="$(cat ${config.sops.secrets."reactive-resume/db_password".path})" <<'SQL'
|
||||
ALTER ROLE "reactive-resume" WITH PASSWORD :'pw';
|
||||
SQL
|
||||
'';
|
||||
};
|
||||
|
||||
# Uploads locaux (pas de S3/Minio). uid/gid 1000 = user "node" de l'image
|
||||
# officielle amruthpillai/reactive-resume.
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /var/lib/reactive-resume 0750 1000 1000 -"
|
||||
"d /var/lib/reactive-resume/data 0750 1000 1000 -"
|
||||
];
|
||||
|
||||
virtualisation.podman = {
|
||||
enable = true;
|
||||
dockerCompat = true;
|
||||
defaultNetwork.settings.dns_enabled = true;
|
||||
};
|
||||
|
||||
virtualisation.oci-containers.backend = "podman";
|
||||
|
||||
virtualisation.oci-containers.containers.reactive-resume = {
|
||||
image = "docker.io/amruthpillai/reactive-resume:v5.2";
|
||||
autoStart = true;
|
||||
volumes = ["/var/lib/reactive-resume/data:/app/data"];
|
||||
environmentFiles = [config.sops.templates."reactive-resume.env".path];
|
||||
extraOptions = [
|
||||
"--network=host"
|
||||
"--pull=newer"
|
||||
"--health-cmd=node -e \"fetch('http://127.0.0.1:8098/api/health').then((r) => { if (!r.ok) process.exit(1); }).catch(() => process.exit(1));\""
|
||||
"--health-interval=30s"
|
||||
"--health-timeout=10s"
|
||||
"--health-retries=3"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services."podman-reactive-resume" = {
|
||||
after = ["network-online.target" "postgresql.service" "reactive-resume-db-password.service"];
|
||||
wants = ["network-online.target"];
|
||||
requires = ["reactive-resume-db-password.service"];
|
||||
};
|
||||
|
||||
# Ressource publique Pangolin : schéma `proxy-resources` + `protocol`
|
||||
# (celui qui marche réellement sur ce contrôleur, cf gitea/jellyfin).
|
||||
# SSO activé : ferme l'accès (y compris inscription) derrière Zitadel.
|
||||
services.newt.blueprint.proxy-resources.reactive-resume = {
|
||||
name = "reactive-resume";
|
||||
protocol = "http";
|
||||
full-domain = "cv.marcpartensky.com";
|
||||
auth.sso-enabled = true;
|
||||
targets = [
|
||||
{
|
||||
hostname = "127.0.0.1";
|
||||
port = 8098;
|
||||
method = "http";
|
||||
healthcheck = {
|
||||
enabled = true;
|
||||
hostname = "127.0.0.1";
|
||||
port = 8098;
|
||||
path = "/api/health";
|
||||
scheme = "http";
|
||||
mode = "http";
|
||||
method = "GET";
|
||||
interval = 30;
|
||||
unhealthy-interval = 30;
|
||||
timeout = 5;
|
||||
healthy-threshold = 1;
|
||||
unhealthy-threshold = 3;
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
# Garde-fou : jamais de port ouvert au LAN (--network=host expose 8098 sur
|
||||
# toutes les interfaces du host, seul le firewall protège du LAN direct).
|
||||
assertions = [
|
||||
{
|
||||
assertion = !(lib.elem 8098 config.networking.firewall.allowedTCPPorts);
|
||||
message = "reactive-resume : le port 8098 ne doit pas être ouvert au firewall (exposition uniquement via Pangolin/newt).";
|
||||
}
|
||||
];
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"git+https://github.com/haydonryan/vaultwarden-cli.git?rev=96cdd0dfc04d04223ec88f8ab0dd217dc0e9beb8#vaultwarden-cli@0.5.4": "0pnyw91jxlwjsx3111bhpmjf7njxzvvkxvlzf77v3kll63pcqkr0"
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
# services/vaultwarden-mcp/default.nix
|
||||
# Serveur MCP Vaultwarden (jr2804/vaultwarden-mcp) : binaire Rust, stdio.
|
||||
# Binaire téléchargé depuis GitHub releases (plus simple que crate2nix).
|
||||
#
|
||||
# Authentification : VAULTWARDEN_SERVER + VAULTWARDEN_PASSWORD (master password).
|
||||
# Les identifiants sont gérés via sops.
|
||||
#
|
||||
# Fichier créé à la demande de Marc (27/09/2026).
|
||||
# IMPORTANT : il faut ajouter `vaultwarden_mcp_password` dans secrets/tower.yml
|
||||
# avant activation :
|
||||
# sops secrets/tower.yml # ajouter la clé vaultwarden_mcp_password
|
||||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
inputs,
|
||||
...
|
||||
}: let
|
||||
# Binaire téléchargé depuis GitHub releases
|
||||
vaultwarden-mcp-bin = pkgs.stdenv.mkDerivation {
|
||||
pname = "vaultwarden-mcp";
|
||||
version = "0.1.0";
|
||||
src = pkgs.fetchurl {
|
||||
url = "https://github.com/jr2804/vaultwarden-mcp/releases/latest/download/vaultwarden-mcp-linux-x86_64";
|
||||
sha256 = "sha256-y47/eiZ43QO4Zh6secpPemd+hr4X/xYyO8UzGLhJf8Q=";
|
||||
};
|
||||
dontUnpack = true;
|
||||
installPhase = ''
|
||||
install -D $src $out/bin/vaultwarden-mcp
|
||||
chmod +x $out/bin/vaultwarden-mcp
|
||||
'';
|
||||
meta = {
|
||||
description = "MCP server for Vaultwarden vault access";
|
||||
homepage = "https://github.com/jr2804/vaultwarden-mcp";
|
||||
license = lib.licenses.mit;
|
||||
};
|
||||
};
|
||||
in {
|
||||
# Le binaire est disponible sur le PATH du système.
|
||||
environment.systemPackages = [vaultwarden-mcp-bin];
|
||||
|
||||
# Secret sops pour le mot de passe master Vaultwarden (défini dans secrets/tower.yml).
|
||||
sops.secrets."vaultwarden/mcp_password" = {
|
||||
key = "vaultwarden_mcp_password";
|
||||
sopsFile = ../../secrets/tower.yml;
|
||||
owner = "hermes";
|
||||
group = "hermes";
|
||||
mode = "0400";
|
||||
};
|
||||
|
||||
# Intégration dans le service hermes-agent : serveur MCP stdio.
|
||||
services.hermes-agent.mcpServers.vaultwarden = {
|
||||
command = "${vaultwarden-mcp-bin}/bin/vaultwarden-mcp";
|
||||
args = ["serve" "--transport" "stdio"];
|
||||
env = {
|
||||
VAULTWARDEN_SERVER = "https://vault.marcpartensky.com";
|
||||
VAULTWARDEN_PASSWORD = config.sops.placeholder."vaultwarden/mcp_password";
|
||||
RUST_LOG = "info";
|
||||
};
|
||||
timeout = 180;
|
||||
};
|
||||
|
||||
# Note : le service Vaultwarden existant (services/vaultwarden/default.nix)
|
||||
# tourne déjà sur tower avec le domaine https://vault.marcpartensky.com,
|
||||
# le port 8222, et l'admin token dans secrets/common.yml.
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"git+https://github.com/haydonryan/vaultwarden-cli.git?rev=96cdd0dfc04d04223ec88f8ab0dd217dc0e9beb8#vaultwarden-cli@0.5.4": "0pnyw91jxlwjsx3111bhpmjf7njxzvvkxvlzf77v3kll63pcqkr0"
|
||||
}
|
||||
Reference in New Issue
Block a user