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:
Hermes Agent
2026-09-29 19:49:09 -04:00
parent 605c355d3a
commit 9180562410
22 changed files with 39555 additions and 474 deletions
+2 -2
View File
@@ -6,7 +6,7 @@ Marc Partensky's NixOS + Home Manager + nix-darwin configuration, one flake for
| Flake output | Type | Description | | 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.laptop` | NixOS | Laptop. `profiles/common` + `profiles/laptop` + `services/`. |
| `nixosConfigurations.deck` | NixOS | Steam Deck (via Jovian-NixOS). `profiles/deck` only, no `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`. | | `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). - **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`. - **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`. - **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`. - **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). - **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
View File
@@ -1167,22 +1167,6 @@
"type": "github" "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": { "pyproject-build-systems": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
@@ -1533,7 +1517,6 @@
"nur": "nur", "nur": "nur",
"option-pricer": "option-pricer", "option-pricer": "option-pricer",
"portfolio-optimizer": "portfolio-optimizer", "portfolio-optimizer": "portfolio-optimizer",
"proton-mail-bridge-client": "proton-mail-bridge-client",
"pyproject-nix": "pyproject-nix_8", "pyproject-nix": "pyproject-nix_8",
"sops": "sops", "sops": "sops",
"sopswarden": "sopswarden", "sopswarden": "sopswarden",
+1 -25
View File
@@ -138,17 +138,6 @@
inputs.nixpkgs.follows = "nixpkgs"; 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 = { firefox-devtools-mcp = {
# Officiel Mozilla, MCP browser automation via WebDriver BiDi. Pin sur le # 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. # commit de la release v0.10.4 (2026-09-22) ; flake=false, source brute.
@@ -176,15 +165,7 @@
self, self,
nixpkgs, nixpkgs,
... ...
} @ inputs: let } @ inputs: {
# 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 {
nixosConfigurations = { nixosConfigurations = {
laptop = nixpkgs.lib.nixosSystem { laptop = nixpkgs.lib.nixosSystem {
system = "x86_64-linux"; system = "x86_64-linux";
@@ -203,11 +184,6 @@
./profiles/common ./profiles/common
./profiles/tower/configuration.nix ./profiles/tower/configuration.nix
./services ./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
]; ];
}; };
+129 -29
View File
@@ -7,11 +7,6 @@
imports = [ imports = [
../../hosts/tower/disko.nix ../../hosts/tower/disko.nix
../../hosts/laptop/hardware-configuration.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 ../../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" "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 boot.kernelParams = ["panic=10"];
systemd.settings.Manager.RuntimeWatchdogSec = "30s"; # reboot si la machine gèle (si `sudo wdctl` trouve un watchdog) systemd.settings.Manager.RuntimeWatchdogSec = "30s";
# 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.kernelModules = ["vkms"]; boot.kernelModules = ["vkms"];
# boot.initrd.clevis.enable = true; boot.initrd.secrets."/etc/secrets/zfs-root.key" = "/etc/secrets/zfs-root.key";
# 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
networking.firewall = { networking.firewall = {
enable = true; enable = true;
allowedTCPPorts = [ allowedTCPPorts = [
2022 2022
8083 # Pangolin / Apps 8083 # Pangolin / Apps
8050 8050
5432 # PostgreSQL 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 [ users.users.hermes.openssh.authorizedKeys.keys = lib.mkAfter [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMdX5m7b8xWL/9ZUeFRxahB4YY0v2rAV5CCFv8xTOlUh marc@Air-de-Marc" "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMdX5m7b8xWL/9ZUeFRxahB4YY0v2rAV5CCFv8xTOlUh marc@Air-de-Marc"
]; ];
@@ -63,12 +49,126 @@
networking.hostName = "tower"; networking.hostName = "tower";
sops.defaultSopsFile = lib.mkForce ../../secrets/tower.yml; 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; 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; 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;
}
-44
View File
@@ -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
}
-275
View File
@@ -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
View File
@@ -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_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: 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] 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] 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_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] 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] 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: sops:
age: age:
- recipient: age189v3q8quejkaz4r8u0g2aechsfk4sk3465khx35erd320rea5scq98v7er - enc: |
enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2dFgyd1VSWUxLZTZNbkZa YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2dFgyd1VSWUxLZTZNbkZa
c0x5Uk9EOGtBbTJXQzVwQWpGMFAyRkRmQWlrClVBSzFxR3VtVEdFdVpOZ21xcXl4 c0x5Uk9EOGtBbTJXQzVwQWpGMFAyRkRmQWlrClVBSzFxR3VtVEdFdVpOZ21xcXl4
@@ -37,8 +36,8 @@ sops:
R1FONm52SFYvZ2hnUm9MOWhBbGU4cFUKAzvxN3xv9DeRmlWxVKjLQjvifRLkuzOL R1FONm52SFYvZ2hnUm9MOWhBbGU4cFUKAzvxN3xv9DeRmlWxVKjLQjvifRLkuzOL
kg1mjiNqbLDTge6wFyjmpOpOpuqDzlv7lLJH0Af3yLt1ghPHeiehcg== kg1mjiNqbLDTge6wFyjmpOpOpuqDzlv7lLJH0Af3yLt1ghPHeiehcg==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1usm3v0ugducrzcwud60dda7xfnj49fdhecj7kcsfm7dzwzrzeyqq2jc2hm recipient: age189v3q8quejkaz4r8u0g2aechsfk4sk3465khx35erd320rea5scq98v7er
enc: | - enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXWmlpZFBqM1lteWhacHcx YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXWmlpZFBqM1lteWhacHcx
ZDdZZUpQZEdCaVI5b3kyZkt5VEMveVNYbzFJCnEvYVlFRExzR0M5WjRZUjNvbGcw ZDdZZUpQZEdCaVI5b3kyZkt5VEMveVNYbzFJCnEvYVlFRExzR0M5WjRZUjNvbGcw
@@ -46,8 +45,8 @@ sops:
VWFSakJvZTJFOXBkaEVOaDYrSlo2aFEKyN2A4vPJ8szeIhwVC/VExXj7kDggJE02 VWFSakJvZTJFOXBkaEVOaDYrSlo2aFEKyN2A4vPJ8szeIhwVC/VExXj7kDggJE02
FtzFIiwHVLzWkIlUc6SFFVX43Q1Z3aYmQtnDuXDBZVOSQfMtg96eew== FtzFIiwHVLzWkIlUc6SFFVX43Q1Z3aYmQtnDuXDBZVOSQfMtg96eew==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1fwgg9h68zz8s6xa8qt4yn7gqcqp59s0mma8xdfq9hunksqv8fq4qd9yfaz recipient: age1usm3v0ugducrzcwud60dda7xfnj49fdhecj7kcsfm7dzwzrzeyqq2jc2hm
enc: | - enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0N1FWaVV4MGNnWmExaW8r YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0N1FWaVV4MGNnWmExaW8r
UjlnMlZEQnlrSDA0RERONXBYdTM3bDFLZ0VNCmEvVFhXT2lhMHV0QlRWbXNFYWRE UjlnMlZEQnlrSDA0RERONXBYdTM3bDFLZ0VNCmEvVFhXT2lhMHV0QlRWbXNFYWRE
@@ -55,8 +54,8 @@ sops:
U0NCT0ptR3dlNjdRVlMwZTR4UDRGWEkKiLgiuZWVKYGEtoh4o0iEwdzzngUAkFn1 U0NCT0ptR3dlNjdRVlMwZTR4UDRGWEkKiLgiuZWVKYGEtoh4o0iEwdzzngUAkFn1
YRpNjwSqiCIsoerTyN7oghbRnNKY8kSl4TyNGiaxwUprLdzEqgwLbw== YRpNjwSqiCIsoerTyN7oghbRnNKY8kSl4TyNGiaxwUprLdzEqgwLbw==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1rhnaejc5nwcqcuq5qt45wzr2fncurlwqw0adhnavfraqsdzffatqh5yhl4 recipient: age1fwgg9h68zz8s6xa8qt4yn7gqcqp59s0mma8xdfq9hunksqv8fq4qd9yfaz
enc: | - enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXcXFPV0g4c2ZSOWdhZGhl YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXcXFPV0g4c2ZSOWdhZGhl
SUh1K2NMSWEyTWU3SzJIUGd1L3g0MEdlVWs4CmhXdTkxT3lQYWdDa2ljYkg5cXUz SUh1K2NMSWEyTWU3SzJIUGd1L3g0MEdlVWs4CmhXdTkxT3lQYWdDa2ljYkg5cXUz
@@ -64,7 +63,8 @@ sops:
anFCMmpFNzBob21LMUprdzkwV1IxUHMKQ+BcKYxGY3Lph/byMfUjf3pDgue2Q7up anFCMmpFNzBob21LMUprdzkwV1IxUHMKQ+BcKYxGY3Lph/byMfUjf3pDgue2Q7up
PbCNf5QMRd82DCYnCIMk5WD/A5kQArnd+jDjaHuaQp14SRdZ4ph+8Q== PbCNf5QMRd82DCYnCIMk5WD/A5kQArnd+jDjaHuaQp14SRdZ4ph+8Q==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2026-07-28T18:10:33Z" recipient: age1rhnaejc5nwcqcuq5qt45wzr2fncurlwqw0adhnavfraqsdzffatqh5yhl4
mac: ENC[AES256_GCM,data:mb/27a5tLqslt3EGsNa6XzmszVWCJmo48y5jLeZsiumVXA3iN/h85aMUHE4g8cPddaG23HWkmroEfFlOgEGr90rfTJL2tw0ASUU4x4GCdHqg9m3mjsnHQU0m8O+YtZLBqwOKTXfznxSYPY/WibFLpyg4frDHlNkkodNoJGloUQs=,iv:euuX0ALHrfBkZmpjspHuyzAbwmtB8RRtqHo2BX6TA9E=,tag:ExRBXAHTHftiwstEuCnKHQ==,type:str] 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 unencrypted_suffix: _unencrypted
version: 3.13.3 version: 3.13.3
+16
View File
@@ -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
+16
View File
@@ -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
-16
View File
@@ -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
+17
View File
@@ -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
-4
View File
@@ -44,10 +44,6 @@
./eternal-terminal ./eternal-terminal
./nextcloud-mcp ./nextcloud-mcp
./amazon-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 ./firefox-mcp
# ./stalwart-mcp # FIXME: cargoHash mismatch, needs fix # ./stalwart-mcp # FIXME: cargoHash mismatch, needs fix
# ./gotify # ./gotify
+15 -1
View File
@@ -132,7 +132,21 @@
Environment = ["NC_SETUP_FORCE_RUN=20260924"]; Environment = ["NC_SETUP_FORCE_RUN=20260924"];
# app:enable ne lance PAS les migrations : sans upgrade ensuite, les # app:enable ne lance PAS les migrations : sans upgrade ensuite, les
# tables des apps (oc_calendars...) ne sont jamais créées. # 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
View File
@@ -1 +0,0 @@
nextcloudpassword
+80
View File
@@ -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;
};
}
-49
View File
@@ -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;
}
+164
View File
@@ -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"
}