Skip to main content
Glama

IT-MCP

by acampkin95
ubuntuAdmin.ts29.8 kB
import { CommandRunner, type CommandResult } from "../utils/commandRunner.js"; export interface ServiceActionOptions { readonly service: string; readonly action: "start" | "stop" | "restart" | "status" | "enable" | "disable"; readonly requiresSudo?: boolean; } export interface PackageUpdateOptions { readonly upgrade?: boolean; readonly autoRemove?: boolean; readonly requiresSudo?: boolean; } export interface DockerActionOptions { readonly command: | "status" | "ps" | "images" | "prune" | "compose-up" | "compose-down" | "logs" | "stats" | "inspect" | "diff" | "export" | "commit" | "logs-label"; readonly composeFile?: string; readonly services?: string[]; readonly tail?: number; readonly build?: boolean; readonly pull?: boolean; readonly envFile?: string; readonly container?: string; readonly format?: string; readonly exportPath?: string; readonly imageName?: string; readonly labelFilter?: string; readonly since?: string; readonly requiresSudo?: boolean; } export interface PostgreActionOptions { readonly command: | "status" | "connections" | "vacuum" | "custom" | "repack" | "backup" | "restore" | "wal" | "isready"; readonly database?: string; readonly customSql?: string; readonly backupPath?: string; readonly restorePath?: string; readonly requiresSudo?: boolean; } export interface NetworkDiagnosticsOptions { readonly interfaceName?: string; readonly analyzeRoutes?: boolean; readonly capture?: boolean; readonly destination?: string; } export interface VirtualminOptions { readonly command: | "list-domains" | "list-users" | "restart-service" | "create-domain" | "backup-domain" | "check-config" | "custom"; readonly domain?: string; readonly user?: string; readonly service?: string; readonly destination?: string; readonly options?: string; readonly customArgs?: string; } export interface FilesystemAction { readonly command: | "list-shares" | "test-smb" | "reload-samba" | "check-samba-config" | "show-nfs-shares" | "file-permissions" | "set-permissions" | "windows-acl-note" | "repair-postgres" | "add-share" | "remove-share" | "add-samba-user" | "list-samba-users" | "update-nfs" | "permissions-snapshot" | "permissions-restore" | "set-acl" | "ownership"; readonly path?: string; readonly user?: string; readonly mode?: string; readonly recursive?: boolean; readonly sharesFile?: string; readonly service?: "smb" | "winbind" | "nmbd" | "nfs"; readonly postgresDb?: string; readonly postgresFix?: "reindex" | "vacuum" | "fsck"; readonly shareName?: string; readonly sharePath?: string; readonly shareComment?: string; readonly nfsEntry?: string; readonly dryRun?: boolean; readonly snapshotFile?: string; readonly restoreFile?: string; readonly aclSpec?: string; readonly owner?: string; readonly group?: string; } export interface SecurityAction { readonly command: | "generate-ssh-key" | "list-authorized-keys" | "harden-ssh" | "ufw" | "iptables" | "tcp-health" | "ipv6-health" | "docker-troubleshoot" | "storage-info" | "storage-sync" | "rotate-ssh-key" | "clean-known-hosts" | "fail2ban" | "auditd" | "suricata" | "cis-audit" | "apparmor-status" | "selinux-status" | "lynis" | "ssh-trust-report"; readonly keyType?: string; readonly keyComment?: string; readonly keyPath?: string; readonly user?: string; readonly ufwAction?: "enable" | "disable" | "status" | "allow" | "deny"; readonly ufwRule?: string; readonly iptablesRule?: string; readonly target?: string; readonly interfaceName?: string; readonly dockerLog?: string; readonly bucketProvider?: "aws" | "gcs" | "azure" | "minio"; readonly bucketName?: string; readonly bucketPath?: string; readonly syncDestination?: string; readonly knownHost?: string; } export interface KubernetesActionOptions { readonly command: "context" | "get-pods" | "describe-node" | "logs" | "restart-deployment" | "get-nodes"; readonly namespace?: string; readonly resource?: string; readonly container?: string; readonly since?: string; readonly deployment?: string; } export class UbuntuAdminService { public constructor(private readonly runner: CommandRunner) {} public updatePackages(options: PackageUpdateOptions = {}): Promise<CommandResult[]> { const commands: string[] = ["sudo apt-get update"]; if (options.upgrade !== false) { commands.push("sudo apt-get upgrade -y"); } if (options.autoRemove) { commands.push("sudo apt-get autoremove -y"); } return this.executeSeries(commands, { requiresSudo: options.requiresSudo ?? true }); } public manageService(options: ServiceActionOptions): Promise<CommandResult> { const command = `systemctl ${options.action} ${options.service}`; return this.runner.run(command, { requiresSudo: options.requiresSudo ?? true }); } public nginxTestConfiguration(): Promise<CommandResult> { return this.runner.run("nginx -t", { requiresSudo: true }); } public nginxReload(): Promise<CommandResult> { return this.runner.run("systemctl reload nginx", { requiresSudo: true }); } public pm2Status(): Promise<CommandResult> { return this.runner.run("pm2 status"); } public pm2Logs(app?: string): Promise<CommandResult> { return this.runner.run(app ? `pm2 logs ${app}` : "pm2 logs"); } public dockerAction(options: DockerActionOptions): Promise<CommandResult> { switch (options.command) { case "status": return this.runner.run("systemctl status docker", { requiresSudo: options.requiresSudo ?? true }); case "ps": return this.runner.run("docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'"); case "images": return this.runner.run("docker images"); case "prune": return this.runner.run("docker system prune -f", { requiresSudo: options.requiresSudo ?? true }); case "stats": return this.runner.run("docker stats --no-stream"); case "inspect": if (!options.container) { throw new Error("Docker inspect requires 'container'."); } return this.runner.run( `docker inspect${options.format ? ` --format '${options.format}'` : ""} ${options.container}`, ); case "diff": if (!options.container) { throw new Error("Docker diff requires 'container'."); } return this.runner.run(`docker diff ${options.container}`); case "export": if (!options.container || !options.exportPath) { throw new Error("Docker export requires 'container' and 'exportPath'."); } return this.runner.run(`docker export ${options.container} -o ${options.exportPath}`); case "commit": if (!options.container || !options.imageName) { throw new Error("Docker commit requires 'container' and 'imageName'."); } return this.runner.run(`docker commit ${options.container} ${options.imageName}`); case "logs-label": if (!options.labelFilter) { throw new Error("Docker logs-label requires 'labelFilter'."); } return this.runner.run( `for cid in $(docker ps --filter "label=${options.labelFilter}" -q); do echo "Logs for $cid"; docker logs${options.since ? ` --since ${options.since}` : ""} $cid; done`, ); case "compose-up": return this.runner.run( this.dockerComposeCommand("up -d", options.composeFile, options.services, { build: options.build, pull: options.pull, envFile: options.envFile, }), { requiresSudo: options.requiresSudo ?? true }, ); case "compose-down": return this.runner.run( this.dockerComposeCommand("down", options.composeFile, options.services), { requiresSudo: options.requiresSudo ?? true }, ); case "logs": return this.runner.run( this.dockerComposeCommand( `logs${options.tail ? ` --tail=${options.tail}` : ""}`, options.composeFile, options.services, ), { requiresSudo: options.requiresSudo ?? true }, ); default: throw new Error(`Unsupported Docker command: ${options.command}`); } } public postgresAction(options: PostgreActionOptions): Promise<CommandResult> { switch (options.command) { case "status": return this.runner.run("systemctl status postgresql", { requiresSudo: options.requiresSudo ?? true }); case "connections": return this.runner.run( `sudo -u postgres psql${options.database ? ` ${options.database}` : ""} -c "SELECT state, count(*) FROM pg_stat_activity GROUP BY state;"`, { requiresSudo: true }, ); case "vacuum": return this.runner.run( `sudo -u postgres vacuumdb --all --analyze`, { requiresSudo: true }, ); case "custom": if (!options.customSql) { throw new Error("Custom SQL command requires 'customSql' input."); } return this.runner.run( `sudo -u postgres psql${options.database ? ` ${options.database}` : ""} -c "${options.customSql.replace(/"/g, '\\"')}"`, { requiresSudo: true }, ); case "repack": return this.runner.run(`sudo -u postgres pg_repack --all`, { requiresSudo: true }); case "backup": if (!options.backupPath) { throw new Error("PostgreSQL backup requires 'backupPath'."); } return this.runner.run(`sudo -u postgres pg_dumpall > ${options.backupPath}`, { requiresSudo: true }); case "restore": if (!options.restorePath) { throw new Error("PostgreSQL restore requires 'restorePath'."); } return this.runner.run(`sudo -u postgres psql -f ${options.restorePath}`, { requiresSudo: true }); case "wal": return this.runner.run( `sudo -u postgres psql -c "SELECT pg_current_wal_lsn(), pg_walfile_name(pg_current_wal_lsn());"`, { requiresSudo: true }, ); case "isready": return this.runner.run(`pg_isready${options.database ? ` -d ${options.database}` : ""}`, { requiresSudo: options.requiresSudo ?? false, }); default: throw new Error(`Unsupported PostgreSQL command: ${options.command}`); } } public async networkDiagnostics(options: NetworkDiagnosticsOptions): Promise<CommandResult[]> { const commands: string[] = []; const iface = options.interfaceName ?? "eth0"; commands.push(`ip addr show ${iface}`); commands.push("ip route show"); if (options.analyzeRoutes) { commands.push("netstat -rn"); commands.push("ss -tulpn"); } if (options.destination) { commands.push(`mtr -rwc 5 ${options.destination}`); } if (options.capture) { commands.push(`sudo tcpdump -i ${iface} -c 100`); } return this.executeSeries(commands, { requiresSudo: false }); } public virtualminCommand(options: VirtualminOptions): Promise<CommandResult> { const base = "sudo virtualmin"; switch (options.command) { case "list-domains": return this.runner.run(`${base} list-domains --name-only`); case "list-users": return this.runner.run(`${base} list-users --multiline`); case "restart-service": if (!options.service) { throw new Error("Virtualmin service restart requires 'service'."); } return this.runner.run(`${base} restart-service --service ${options.service}`); case "create-domain": if (!options.domain) { throw new Error("Virtualmin create-domain requires 'domain'."); } return this.runner.run( `${base} create-domain --domain ${options.domain}${ options.user ? ` --unix-user ${options.user}` : "" }${options.options ? ` ${options.options}` : ""}`, ); case "backup-domain": if (!options.domain || !options.destination) { throw new Error("Virtualmin backup-domain requires 'domain' and 'destination'."); } return this.runner.run(`${base} backup-domain --domain ${options.domain} --dest ${options.destination}`); case "check-config": return this.runner.run(`${base} check-config`); case "custom": if (!options.customArgs) { throw new Error("Virtualmin custom command requires 'customArgs'."); } return this.runner.run(`${base} ${options.customArgs}`); default: throw new Error(`Unsupported Virtualmin command: ${options.command}`); } } public async filesystemAction(options: FilesystemAction): Promise<CommandResult[]> { switch (options.command) { case "list-shares": { const cmd = options.sharesFile ? `testparm -s ${options.sharesFile}` : "testparm -s"; return this.executeSeries([cmd], { requiresSudo: true }); } case "test-smb": { if (!options.user || !options.path) { throw new Error("SMB testing requires 'user' and 'path'."); } return this.executeSeries( [ `smbclient -L localhost -U ${options.user}`, `smbclient ${options.path} -U ${options.user} -c 'dir'`, ], { requiresSudo: false }, ); } case "reload-samba": { const cmd = options.service ? `systemctl reload ${options.service}` : "systemctl reload smbd"; return this.executeSeries([cmd], { requiresSudo: true }); } case "check-samba-config": return this.executeSeries(["testparm -s"], { requiresSudo: true }); case "show-nfs-shares": return this.executeSeries(["exportfs -v"], { requiresSudo: true }); case "file-permissions": if (!options.path) { throw new Error("File permissions check requires 'path'."); } return this.executeSeries( [ `ls -alh ${options.path}`, `stat ${options.path}`, ], { requiresSudo: false }, ); case "set-permissions": if (!options.path || !options.mode) { throw new Error("Setting permissions requires 'path' and 'mode'."); } return this.executeSeries( [ `${options.recursive ? "chmod -R" : "chmod"} ${options.mode} ${options.path}`, `ls -alh ${options.path}`, ], { requiresSudo: true }, ); case "windows-acl-note": return this.executeSeries( [ `echo "Windows ACL management requires PowerShell or Samba vfs_acl_xattr configuration."`, `echo "Refer to: https://wiki.samba.org/index.php/Setting_up_a_Share_Using_Windows_ACLs"`, ], { requiresSudo: false }, ); case "repair-postgres": return this.handlePostgresRepair(options); case "add-share": { if (!options.shareName || !options.sharePath) { throw new Error("Adding a share requires 'shareName' and 'sharePath'."); } const comment = options.shareComment ?? "Managed by ubuntu-admin"; return this.executeSeries( [ `sudo net usershare add ${options.shareName} ${options.sharePath} "${comment}" everyone:F`, "sudo net usershare info", ], { requiresSudo: true }, ); } case "remove-share": { if (!options.shareName) { throw new Error("Removing a share requires 'shareName'."); } return this.executeSeries( [ `sudo net usershare delete ${options.shareName}`, "sudo net usershare info", ], { requiresSudo: true }, ); } case "add-samba-user": { if (!options.user) { throw new Error("Adding Samba user requires 'user'."); } return this.executeSeries([ `sudo smbpasswd -a ${options.user}`, "sudo pdbedit -L", ], { requiresSudo: true }); } case "list-samba-users": return this.executeSeries(["sudo pdbedit -L"], { requiresSudo: true }); case "update-nfs": { if (!options.nfsEntry) { throw new Error("Updating NFS exports requires 'nfsEntry'."); } if (options.dryRun) { return this.executeSeries([ `echo "Would append to /etc/exports: ${options.nfsEntry}"`, "exportfs -s", ], { requiresSudo: false }); } return this.executeSeries([ `sudo sh -c "echo '${options.nfsEntry}' >> /etc/exports"`, "sudo exportfs -arv", ], { requiresSudo: true }); } case "permissions-snapshot": { if (!options.path || !options.snapshotFile) { throw new Error("Permissions snapshot requires 'path' and 'snapshotFile'."); } return this.executeSeries([ `getfacl -R ${options.path} > ${options.snapshotFile}`, ], { requiresSudo: true }); } case "permissions-restore": { if (!options.restoreFile) { throw new Error("Permissions restore requires 'restoreFile'."); } return this.executeSeries([ `setfacl --restore=${options.restoreFile}`, ], { requiresSudo: true }); } case "set-acl": { if (!options.path || !options.aclSpec) { throw new Error("Setting ACL requires 'path' and 'aclSpec'."); } return this.executeSeries([ `setfacl ${options.recursive ? "-R" : ""} -m ${options.aclSpec} ${options.path}`, `getfacl ${options.path}`, ], { requiresSudo: true }); } case "ownership": { if (!options.path || (!options.owner && !options.group)) { throw new Error("Ownership change requires 'path' and at least 'owner' or 'group'."); } const ownerSpec = `${options.owner ?? ""}${options.group ? `:${options.group}` : ""}`; return this.executeSeries([ `${options.recursive ? "chown -R" : "chown"} ${ownerSpec} ${options.path}`, `ls -alh ${options.path}`, ], { requiresSudo: true }); } default: throw new Error(`Unsupported filesystem command: ${options.command}`); } } public async securityAction(options: SecurityAction): Promise<CommandResult[]> { switch (options.command) { case "generate-ssh-key": { const keyType = options.keyType ?? "ed25519"; const comment = options.keyComment ?? "mcp-key"; const keyPath = options.keyPath ?? `~/.ssh/${comment}`; return this.executeSeries( [ `ssh-keygen -t ${keyType} -C "${comment}" -f ${keyPath} -N ""`, `ls -alh ${keyPath}*`, ], { requiresSudo: false }, ); } case "list-authorized-keys": { const user = options.user ?? "$USER"; return this.executeSeries([`sudo -u ${user} cat ~/.ssh/authorized_keys`], { requiresSudo: true }); } case "harden-ssh": return this.executeSeries( [ "sudo sed -i.bak -e 's/^#*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config", "sudo sed -i -e 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config", "sudo systemctl restart sshd", ], { requiresSudo: true }, ); case "ufw": { const action = options.ufwAction ?? "status"; const rule = options.ufwRule ?? ""; const command = `sudo ufw ${action}${rule ? ` ${rule}` : ""}`; return this.executeSeries([command, "sudo ufw status verbose"], { requiresSudo: true }); } case "iptables": { if (!options.iptablesRule) { throw new Error("iptables action requires 'iptablesRule'."); } return this.executeSeries( [ `sudo iptables ${options.iptablesRule}`, "sudo iptables -L -n -v", ], { requiresSudo: true }, ); } case "tcp-health": { const iface = options.interfaceName ?? "eth0"; const target = options.target ?? "8.8.8.8"; return this.executeSeries( [ `ethtool ${iface}`, `ss -s`, `sudo tcpdump -i ${iface} -c 50`, `mtr -rwc 5 ${target}`, ], { requiresSudo: false }, ); } case "ipv6-health": { const target = options.target ?? "2001:4860:4860::8888"; return this.executeSeries( [ "sysctl net.ipv6.conf.all.disable_ipv6", "ip -6 addr", `ping6 -c 3 ${target}`, `traceroute6 ${target}`, ], { requiresSudo: false }, ); } case "docker-troubleshoot": return this.executeSeries( [ "docker ps -a", "docker system df", "docker events --since 1h --until 0s", options.dockerLog ? `docker logs ${options.dockerLog}` : "echo 'Set dockerLog to fetch container logs'", ], { requiresSudo: false }, ); case "rotate-ssh-key": { const keyType = options.keyType ?? "ed25519"; const comment = options.keyComment ?? `rotated-${Date.now()}`; const keyPath = options.keyPath ?? `~/.ssh/${comment}`; return this.executeSeries( [ `ssh-keygen -t ${keyType} -C "${comment}" -f ${keyPath} -N ""`, options.target ? `ssh-keygen -R ${options.target}` : "echo 'Provide securityTarget to clean known host entry'", `ls -alh ${keyPath}*`, ], { requiresSudo: false }, ); } case "clean-known-hosts": if (!options.knownHost) { throw new Error("Cleaning known hosts requires 'knownHost'."); } return this.executeSeries([ `ssh-keygen -R ${options.knownHost}`, `grep ${options.knownHost} ~/.ssh/known_hosts || echo 'Entry removed.'`, ], { requiresSudo: false }); case "fail2ban": return this.executeSeries([ "sudo fail2ban-client status", "sudo fail2ban-client status sshd", ], { requiresSudo: true }); case "auditd": return this.executeSeries([ "sudo service auditd status", "sudo aureport --summary", ], { requiresSudo: true }); case "suricata": return this.executeSeries([ "sudo systemctl status suricata", "sudo tail -n 100 /var/log/suricata/suricata.log", ], { requiresSudo: true }); case "cis-audit": return this.executeSeries([ "echo 'Running CIS audit via lynis --tests-from-group cis'", "sudo lynis audit system --tests-from-group cis --quick", ], { requiresSudo: true }); case "apparmor-status": return this.executeSeries(["sudo aa-status"], { requiresSudo: true }); case "selinux-status": return this.executeSeries(["sestatus"], { requiresSudo: false }); case "lynis": return this.executeSeries([ "sudo lynis audit system --cronjob", ], { requiresSudo: true }); case "ssh-trust-report": return this.executeSeries([ "sudo find /home -name authorized_keys -print -exec cat {} \;", ], { requiresSudo: true }); case "storage-info": { const provider = options.bucketProvider ?? "aws"; switch (provider) { case "aws": return this.executeSeries( options.bucketName ? [`aws s3 ls s3://${options.bucketName}`] : ["aws s3 ls"], { requiresSudo: false }, ); case "gcs": return this.executeSeries( options.bucketName ? [`gsutil ls gs://${options.bucketName}`] : ["gsutil ls"], { requiresSudo: false }, ); case "azure": return this.executeSeries( options.bucketName ? [`az storage blob list --container-name ${options.bucketName} --output table`] : ["az storage container list --output table"], { requiresSudo: false }, ); case "minio": return this.executeSeries( options.bucketName ? [`mc ls myminio/${options.bucketName}`] : ["mc ls myminio"], { requiresSudo: false }, ); default: throw new Error(`Unsupported bucket provider: ${provider}`); } } case "storage-sync": { if (!options.bucketProvider || !options.bucketName || !options.syncDestination) { throw new Error("Storage sync requires provider, bucketName, and syncDestination."); } const destination = options.syncDestination; switch (options.bucketProvider) { case "aws": return this.executeSeries( [`aws s3 sync s3://${options.bucketName}${options.bucketPath ?? ""} ${destination}`], { requiresSudo: false }, ); case "gcs": return this.executeSeries( [`gsutil -m rsync -r gs://${options.bucketName}${options.bucketPath ?? ""} ${destination}`], { requiresSudo: false }, ); case "azure": return this.executeSeries( [ `az storage blob sync --container ${options.bucketName} --source ${destination} --destination ${options.bucketPath ?? ""}`, ], { requiresSudo: false }, ); case "minio": return this.executeSeries( [`mc mirror myminio/${options.bucketName}${options.bucketPath ?? ""} ${destination}`], { requiresSudo: false }, ); default: throw new Error(`Unsupported bucket provider: ${options.bucketProvider}`); } } default: throw new Error(`Unsupported security command: ${options.command}`); } } public kubernetesAction(options: KubernetesActionOptions): Promise<CommandResult> { switch (options.command) { case "context": return this.runner.run("kubectl config get-contexts"); case "get-pods": return this.runner.run( `kubectl get pods${options.namespace ? ` -n ${options.namespace}` : ""} -o wide`, ); case "get-nodes": return this.runner.run("kubectl get nodes -o wide"); case "describe-node": if (!options.resource) { throw new Error("Describe node requires 'resource'."); } return this.runner.run(`kubectl describe node ${options.resource}`); case "logs": if (!options.resource) { throw new Error("Fetching logs requires 'resource' (pod name)."); } return this.runner.run( `kubectl logs ${options.resource}${options.container ? ` -c ${options.container}` : ""}${options.namespace ? ` -n ${options.namespace}` : ""}${options.since ? ` --since=${options.since}` : ""}`, ); case "restart-deployment": if (!options.deployment) { throw new Error("Restart deployment requires 'deployment'."); } return this.runner.run( `kubectl rollout restart deployment/${options.deployment}${options.namespace ? ` -n ${options.namespace}` : ""}`, ); default: throw new Error(`Unsupported Kubernetes command: ${options.command}`); } } private dockerComposeCommand( action: string, composeFile?: string, services?: string[], options?: { build?: boolean; pull?: boolean; envFile?: string }, ): string { const parts = ["docker compose"]; if (composeFile) { parts.push("-f", composeFile); } if (options?.envFile) { parts.push("--env-file", options.envFile); } parts.push(action); if (options?.build) { parts.push("--build"); } if (options?.pull) { parts.push("--pull"); } if (services?.length) { parts.push(...services); } return parts.join(" "); } private async executeSeries(commands: string[], options: { requiresSudo: boolean }): Promise<CommandResult[]> { const results: CommandResult[] = []; for (const command of commands) { results.push( await this.runner.run(command, { requiresSudo: options.requiresSudo }), ); } return results; } private async handlePostgresRepair(options: FilesystemAction): Promise<CommandResult[]> { const db = options.postgresDb ?? "postgres"; switch (options.postgresFix) { case "reindex": return this.executeSeries( [`sudo -u postgres reindexdb --all`], { requiresSudo: true }, ); case "vacuum": return this.executeSeries( [`sudo -u postgres vacuumdb --all --analyze --verbose`], { requiresSudo: true }, ); case "fsck": return this.executeSeries( [ "systemctl stop postgresql", "sudo fsck -y /var/lib/postgresql", "systemctl start postgresql", ], { requiresSudo: true }, ); default: return this.executeSeries( [ `sudo -u postgres psql ${db} -c 'CHECKPOINT;'`, `sudo -u postgres psql ${db} -c 'SELECT datname, last_vacuum, last_autovacuum FROM pg_stat_database;'`, ], { requiresSudo: true }, ); } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/acampkin95/MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server