Compare commits
No commits in common. "d0e2933c9deca543d9e45c24f056f4e07dd73b5b" and "12cc2321f987b721a7af8c4b7ee3eba1ae11f5da" have entirely different histories.
d0e2933c9d
...
12cc2321f9
8 changed files with 2 additions and 131 deletions
|
|
@ -10,7 +10,6 @@
|
||||||
inputs.clients.nixosModules.syncthing
|
inputs.clients.nixosModules.syncthing
|
||||||
|
|
||||||
outputs.nixosModules.tailscale
|
outputs.nixosModules.tailscale
|
||||||
outputs.nixosModules.journald-upload
|
|
||||||
|
|
||||||
./forgejo.nix
|
./forgejo.nix
|
||||||
./jirafeau.nix
|
./jirafeau.nix
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@
|
||||||
inputs.synix.nixosModules.openssh
|
inputs.synix.nixosModules.openssh
|
||||||
|
|
||||||
outputs.nixosModules.tailscale
|
outputs.nixosModules.tailscale
|
||||||
outputs.nixosModules.journald-remote
|
|
||||||
|
|
||||||
./headscale.nix
|
./headscale.nix
|
||||||
./mailserver.nix
|
./mailserver.nix
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
|
# ./journald.nix # FIXME: start systemd-journal-upload.service after tailscaled
|
||||||
./nix.nix
|
./nix.nix
|
||||||
./overlays.nix
|
./overlays.nix
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
services.journald.upload = {
|
services.journald.upload = {
|
||||||
enable = true;
|
enable = true;
|
||||||
settings.Upload.URL = "http://100.64.0.6:19532";
|
settings.Upload.URL = "http://100.64.0.5:19532";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -5,8 +5,6 @@
|
||||||
forgejo = import ./forgejo;
|
forgejo = import ./forgejo;
|
||||||
forgejo-runner = import ./forgejo-runner;
|
forgejo-runner = import ./forgejo-runner;
|
||||||
gnome = import ./gnome;
|
gnome = import ./gnome;
|
||||||
journald-remote = import ./journald-remote;
|
|
||||||
journald-upload = import ./journald-upload;
|
|
||||||
monero = import ./monero;
|
monero = import ./monero;
|
||||||
rsshub-oci = import ./rsshub-oci;
|
rsshub-oci = import ./rsshub-oci;
|
||||||
tailscale = import ./tailscale;
|
tailscale = import ./tailscale;
|
||||||
|
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
{
|
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
python = pkgs.python3Packages;
|
|
||||||
|
|
||||||
mcp-log-server = python.buildPythonApplication {
|
|
||||||
pname = "mcp-log-server";
|
|
||||||
version = "1.0.0";
|
|
||||||
|
|
||||||
src = ./.;
|
|
||||||
|
|
||||||
pyproject = true;
|
|
||||||
build-system = [ python.setuptools ];
|
|
||||||
|
|
||||||
propagatedBuildInputs = with python; [
|
|
||||||
fastmcp
|
|
||||||
];
|
|
||||||
|
|
||||||
meta.mainProgram = "mcp-log-server";
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
services.journald.remote = {
|
|
||||||
enable = true;
|
|
||||||
listen = "http";
|
|
||||||
port = 19532;
|
|
||||||
settings.Remote.SplitMode = "host";
|
|
||||||
};
|
|
||||||
|
|
||||||
users.users.sid.extraGroups = [
|
|
||||||
"systemd-journal"
|
|
||||||
"systemd-journal-remote"
|
|
||||||
];
|
|
||||||
|
|
||||||
systemd.services.mcp-log-server = {
|
|
||||||
description = "AI Log Access MCP Server";
|
|
||||||
after = [
|
|
||||||
"network.target"
|
|
||||||
"multi-user.target"
|
|
||||||
"systemd-journald.service"
|
|
||||||
];
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
|
|
||||||
script = lib.getExe mcp-log-server;
|
|
||||||
|
|
||||||
serviceConfig = {
|
|
||||||
User = "root";
|
|
||||||
Group = "root";
|
|
||||||
Environment = "PYTHONUNBUFFERED=1";
|
|
||||||
Restart = "on-failure";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
[build-system]
|
|
||||||
requires = ["setuptools"]
|
|
||||||
|
|
||||||
[project]
|
|
||||||
name = "mcp-log-server"
|
|
||||||
version = "1.0.0"
|
|
||||||
|
|
||||||
[project.scripts]
|
|
||||||
mcp-log-server = "server:main"
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
import json
|
|
||||||
import subprocess
|
|
||||||
from fastmcp import FastMCP
|
|
||||||
|
|
||||||
mcp = FastMCP('ClusterLogQuery')
|
|
||||||
|
|
||||||
@mcp.tool()
|
|
||||||
async def search_cluster_logs(keyword: str, unit: str = '*', limit: int = 500):
|
|
||||||
"""
|
|
||||||
Search centralized systemd logs collected on this server.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
keyword: The string to search for (e.g. 'oom-kill', 'failed').
|
|
||||||
unit: The systemd unit to filter (e.g. 'sshd'). If '*', matches all.
|
|
||||||
limit: The number of log lines to return (Max 1000).
|
|
||||||
"""
|
|
||||||
|
|
||||||
limit = min(max(limit, 10), 1000)
|
|
||||||
|
|
||||||
cmd = [
|
|
||||||
'journalctl',
|
|
||||||
'-n', str(limit),
|
|
||||||
'-o', 'json',
|
|
||||||
'--no-pager',
|
|
||||||
'--directory', '/var/log/journal/remote'
|
|
||||||
]
|
|
||||||
|
|
||||||
if unit != '*':
|
|
||||||
cmd.append(f'UNIT={unit}')
|
|
||||||
if keyword:
|
|
||||||
cmd.append(keyword)
|
|
||||||
|
|
||||||
try:
|
|
||||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
||||||
stdout, _ = proc.communicate()
|
|
||||||
|
|
||||||
logs = []
|
|
||||||
for line in stdout.decode('utf-8').strip().split('\n'):
|
|
||||||
if not line: continue
|
|
||||||
try:
|
|
||||||
log = json.loads(line)
|
|
||||||
logs.append({
|
|
||||||
'timestamp': log.get('TIME_ISO8601'),
|
|
||||||
'hostname': log.get('_HOSTNAME'),
|
|
||||||
'unit': log.get('_COMM'),
|
|
||||||
'message': log.get('MESSAGE')
|
|
||||||
})
|
|
||||||
except: pass
|
|
||||||
|
|
||||||
return json.dumps(logs)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return f'Error querying logs: {str(e)}'
|
|
||||||
|
|
||||||
def main():
|
|
||||||
mcp.run(transport='sse', host='0.0.0.0', port=9500)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue