synology_project_manage
Manage Docker Compose projects on Synology NAS: start (up -d), stop (down), restart, or pull updates by specifying the project name and action.
Instructions
Manage a docker-compose project (up, down, restart)
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| project_name | Yes | Name of the project folder in NAS_DOCKER_DIR | |
| action | Yes | Docker compose action |
Implementation Reference
- src/index.ts:155-164 (registration)Tool registration with inputSchema for 'synology_project_manage' - defines the tool name, description, and expects project_name (string) and action (enum: up -d, down, restart, pull) as required parameters.
name: "synology_project_manage", description: "Manage a docker-compose project (up, down, restart)", inputSchema: { type: "object", properties: { project_name: { type: "string", description: "Name of the project folder in NAS_DOCKER_DIR" }, action: { type: "string", enum: ["up -d", "down", "restart", "pull"], description: "Docker compose action" }, }, required: ["project_name", "action"], }, - src/index.ts:231-245 (handler)Handler function for 'synology_project_manage' - validates the project name, checks the compose action is valid, verifies docker-compose.yml exists in the project directory, then executes docker-compose with the given action on the NAS via SSH.
else if (name === "synology_project_manage") { const { project_name, action } = args as { project_name: string; action: string }; validateProjectName(project_name); if (!VALID_COMPOSE_ACTIONS.has(action)) { throw new Error(`Invalid compose action: ${action}`); } const projectPath = `${NAS_DOCKER_DIR}/${project_name}`; const checkRes = await execSshCommand(`ls ${shQuote(projectPath + "/docker-compose.yml")}`); if (checkRes.code !== 0) { return { content: [{ type: "text", text: `Error: docker-compose.yml not found in ${projectPath}` }] }; } const cmd = `cd ${shQuote(projectPath)} && docker-compose -p ${shQuote(project_name)} ${action}`; const res = await execSshCommand(cmd); return { content: [{ type: "text", text: `Command executed in ${projectPath}.\nExit Code: ${res.code}\nOutput:\n${res.stdout}\n${res.stderr}` }] }; } - src/index.ts:40-44 (helper)Validation helper used by the handler - validateProjectName ensures the project name matches allowed pattern (alphanumeric, underscore, hyphen).
function validateProjectName(name: string): void { if (!/^[a-zA-Z0-9][a-zA-Z0-9_\-]*$/.test(name)) { throw new Error(`Invalid project name: ${name}`); } } - src/index.ts:25-25 (helper)Set of valid docker-compose actions used by the handler.
const VALID_COMPOSE_ACTIONS = new Set(["up -d", "down", "restart", "pull"]); - src/index.ts:72-109 (helper)SSH command executor used by the handler to run commands on the Synology NAS via SSH with sudo.
async function execSshCommand(command: string): Promise<{ stdout: string; stderr: string; code: number }> { return new Promise((resolve, reject) => { const conn = new Client(); conn.on("ready", () => { // Use printf to avoid shell expansion of password contents. const fullCommand = `export PATH=$PATH:/usr/local/bin:/opt/bin:/bin:/usr/bin && printf '%s\\n' ${shQuote(NAS_PASSWORD!)} | sudo -S sh -c ${shQuote(command)}`; conn.exec(fullCommand, (err, stream) => { if (err) { conn.end(); return reject(err); } let stdout = ""; let stderr = ""; stream .on("close", (code: number) => { conn.end(); resolve({ stdout, stderr, code }); }) .on("data", (data: any) => { stdout += data; }) .stderr.on("data", (data: any) => { stderr += data; }); }); }).on("error", (err) => { reject(err); }).connect({ host: NAS_HOST, port: NAS_PORT, username: NAS_USER, password: NAS_PASSWORD, readyTimeout: 30000, }); }); }