synapse-mcp 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1820 -147
- package/dist/constants.d.ts +10 -4
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +18 -8
- package/dist/constants.js.map +1 -1
- package/dist/events/emitter.d.ts +63 -0
- package/dist/events/emitter.d.ts.map +1 -0
- package/dist/events/emitter.js +112 -0
- package/dist/events/emitter.js.map +1 -0
- package/dist/events/index.d.ts +3 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/index.js +3 -0
- package/dist/events/index.js.map +1 -0
- package/dist/events/types.d.ts +51 -0
- package/dist/events/types.d.ts.map +1 -0
- package/dist/events/types.js +3 -0
- package/dist/events/types.js.map +1 -0
- package/dist/formatters/compose.d.ts +185 -0
- package/dist/formatters/compose.d.ts.map +1 -0
- package/dist/formatters/compose.js +397 -0
- package/dist/formatters/compose.js.map +1 -0
- package/dist/formatters/container.d.ts +84 -0
- package/dist/formatters/container.d.ts.map +1 -0
- package/dist/formatters/container.js +323 -0
- package/dist/formatters/container.js.map +1 -0
- package/dist/formatters/diagnostics.d.ts +20 -0
- package/dist/formatters/diagnostics.d.ts.map +1 -0
- package/dist/formatters/diagnostics.js +73 -0
- package/dist/formatters/diagnostics.js.map +1 -0
- package/dist/formatters/docker.d.ts +139 -0
- package/dist/formatters/docker.d.ts.map +1 -0
- package/dist/formatters/docker.js +216 -0
- package/dist/formatters/docker.js.map +1 -0
- package/dist/formatters/host.d.ts +137 -0
- package/dist/formatters/host.d.ts.map +1 -0
- package/dist/formatters/host.js +198 -0
- package/dist/formatters/host.js.map +1 -0
- package/dist/formatters/index.d.ts +17 -270
- package/dist/formatters/index.d.ts.map +1 -1
- package/dist/formatters/index.js +21 -456
- package/dist/formatters/index.js.map +1 -1
- package/dist/formatters/scout.d.ts +424 -0
- package/dist/formatters/scout.d.ts.map +1 -0
- package/dist/formatters/scout.js +687 -0
- package/dist/formatters/scout.js.map +1 -0
- package/dist/formatters/strategy.d.ts +105 -0
- package/dist/formatters/strategy.d.ts.map +1 -0
- package/dist/formatters/strategy.js +120 -0
- package/dist/formatters/strategy.js.map +1 -0
- package/dist/formatters/utils.d.ts +84 -0
- package/dist/formatters/utils.d.ts.map +1 -0
- package/dist/formatters/utils.js +129 -0
- package/dist/formatters/utils.js.map +1 -0
- package/dist/health-rate-limiter.d.ts +59 -0
- package/dist/health-rate-limiter.d.ts.map +1 -0
- package/dist/health-rate-limiter.js +159 -0
- package/dist/health-rate-limiter.js.map +1 -0
- package/dist/index.js +61 -100
- package/dist/index.js.map +1 -1
- package/dist/middleware/async-handler.d.ts +62 -0
- package/dist/middleware/async-handler.d.ts.map +1 -0
- package/dist/middleware/async-handler.js +58 -0
- package/dist/middleware/async-handler.js.map +1 -0
- package/dist/middleware/auth.d.ts +32 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +63 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/csrf-protection.d.ts +58 -0
- package/dist/middleware/csrf-protection.d.ts.map +1 -0
- package/dist/middleware/csrf-protection.js +123 -0
- package/dist/middleware/csrf-protection.js.map +1 -0
- package/dist/middleware/error-handler.d.ts +49 -0
- package/dist/middleware/error-handler.d.ts.map +1 -0
- package/dist/middleware/error-handler.js +90 -0
- package/dist/middleware/error-handler.js.map +1 -0
- package/dist/middleware/error-mapper.d.ts +44 -0
- package/dist/middleware/error-mapper.d.ts.map +1 -0
- package/dist/middleware/error-mapper.js +127 -0
- package/dist/middleware/error-mapper.js.map +1 -0
- package/dist/middleware/index.d.ts +13 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +13 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/request-id.d.ts +22 -0
- package/dist/middleware/request-id.d.ts.map +1 -0
- package/dist/middleware/request-id.js +31 -0
- package/dist/middleware/request-id.js.map +1 -0
- package/dist/middleware/types.d.ts +33 -0
- package/dist/middleware/types.d.ts.map +1 -0
- package/dist/middleware/types.js +2 -0
- package/dist/middleware/types.js.map +1 -0
- package/dist/schemas/common.d.ts +205 -8
- package/dist/schemas/common.d.ts.map +1 -1
- package/dist/schemas/common.js +290 -17
- package/dist/schemas/common.js.map +1 -1
- package/dist/schemas/flux/compose.d.ts +307 -44
- package/dist/schemas/flux/compose.d.ts.map +1 -1
- package/dist/schemas/flux/compose.js +74 -48
- package/dist/schemas/flux/compose.js.map +1 -1
- package/dist/schemas/flux/container.d.ts +423 -56
- package/dist/schemas/flux/container.d.ts.map +1 -1
- package/dist/schemas/flux/container.js +83 -61
- package/dist/schemas/flux/container.js.map +1 -1
- package/dist/schemas/flux/docker.d.ts +254 -37
- package/dist/schemas/flux/docker.d.ts.map +1 -1
- package/dist/schemas/flux/docker.js +69 -39
- package/dist/schemas/flux/docker.js.map +1 -1
- package/dist/schemas/flux/host.d.ts +312 -29
- package/dist/schemas/flux/host.d.ts.map +1 -1
- package/dist/schemas/flux/host.js +74 -31
- package/dist/schemas/flux/host.js.map +1 -1
- package/dist/schemas/flux/index.d.ts +503 -11
- package/dist/schemas/flux/index.d.ts.map +1 -1
- package/dist/schemas/flux/index.js +34 -70
- package/dist/schemas/flux/index.js.map +1 -1
- package/dist/schemas/host-config.d.ts +76 -0
- package/dist/schemas/host-config.d.ts.map +1 -0
- package/dist/schemas/host-config.js +105 -0
- package/dist/schemas/host-config.js.map +1 -0
- package/dist/schemas/scout/index.d.ts +80 -23
- package/dist/schemas/scout/index.d.ts.map +1 -1
- package/dist/schemas/scout/index.js +26 -11
- package/dist/schemas/scout/index.js.map +1 -1
- package/dist/schemas/scout/logs.d.ts +17 -5
- package/dist/schemas/scout/logs.d.ts.map +1 -1
- package/dist/schemas/scout/logs.js +41 -31
- package/dist/schemas/scout/logs.js.map +1 -1
- package/dist/schemas/scout/simple.d.ts +126 -11
- package/dist/schemas/scout/simple.d.ts.map +1 -1
- package/dist/schemas/scout/simple.js +112 -57
- package/dist/schemas/scout/simple.js.map +1 -1
- package/dist/schemas/scout/zfs.d.ts +17 -5
- package/dist/schemas/scout/zfs.d.ts.map +1 -1
- package/dist/schemas/scout/zfs.js +34 -25
- package/dist/schemas/scout/zfs.js.map +1 -1
- package/dist/services/cache-layer.d.ts +160 -0
- package/dist/services/cache-layer.d.ts.map +1 -0
- package/dist/services/cache-layer.js +138 -0
- package/dist/services/cache-layer.js.map +1 -0
- package/dist/services/compose-cache.d.ts +75 -0
- package/dist/services/compose-cache.d.ts.map +1 -0
- package/dist/services/compose-cache.js +178 -0
- package/dist/services/compose-cache.js.map +1 -0
- package/dist/services/compose-discovery.d.ts +46 -0
- package/dist/services/compose-discovery.d.ts.map +1 -0
- package/dist/services/compose-discovery.js +219 -0
- package/dist/services/compose-discovery.js.map +1 -0
- package/dist/services/compose-project-lister.d.ts +27 -0
- package/dist/services/compose-project-lister.d.ts.map +1 -0
- package/dist/services/compose-project-lister.js +71 -0
- package/dist/services/compose-project-lister.js.map +1 -0
- package/dist/services/compose-scanner.d.ts +63 -0
- package/dist/services/compose-scanner.d.ts.map +1 -0
- package/dist/services/compose-scanner.js +253 -0
- package/dist/services/compose-scanner.js.map +1 -0
- package/dist/services/compose.d.ts +64 -28
- package/dist/services/compose.d.ts.map +1 -1
- package/dist/services/compose.js +220 -98
- package/dist/services/compose.js.map +1 -1
- package/dist/services/config-loader.d.ts +23 -0
- package/dist/services/config-loader.d.ts.map +1 -0
- package/dist/services/config-loader.js +124 -0
- package/dist/services/config-loader.js.map +1 -0
- package/dist/services/config-service.d.ts +38 -0
- package/dist/services/config-service.d.ts.map +1 -0
- package/dist/services/config-service.js +225 -0
- package/dist/services/config-service.js.map +1 -0
- package/dist/services/container-host-map-cache.d.ts +121 -0
- package/dist/services/container-host-map-cache.d.ts.map +1 -0
- package/dist/services/container-host-map-cache.js +188 -0
- package/dist/services/container-host-map-cache.js.map +1 -0
- package/dist/services/container.d.ts +194 -6
- package/dist/services/container.d.ts.map +1 -1
- package/dist/services/container.js +386 -11
- package/dist/services/container.js.map +1 -1
- package/dist/services/diagnostics.d.ts +57 -0
- package/dist/services/diagnostics.d.ts.map +1 -0
- package/dist/services/diagnostics.js +271 -0
- package/dist/services/diagnostics.js.map +1 -0
- package/dist/services/docker/container-service.d.ts +123 -0
- package/dist/services/docker/container-service.d.ts.map +1 -0
- package/dist/services/docker/container-service.js +347 -0
- package/dist/services/docker/container-service.js.map +1 -0
- package/dist/services/docker/image-service.d.ts +82 -0
- package/dist/services/docker/image-service.d.ts.map +1 -0
- package/dist/services/docker/image-service.js +193 -0
- package/dist/services/docker/image-service.js.map +1 -0
- package/dist/services/docker/index.d.ts +80 -0
- package/dist/services/docker/index.d.ts.map +1 -0
- package/dist/services/docker/index.js +103 -0
- package/dist/services/docker/index.js.map +1 -0
- package/dist/services/docker/network-service.d.ts +22 -0
- package/dist/services/docker/network-service.d.ts.map +1 -0
- package/dist/services/docker/network-service.js +43 -0
- package/dist/services/docker/network-service.js.map +1 -0
- package/dist/services/docker/system-service.d.ts +49 -0
- package/dist/services/docker/system-service.d.ts.map +1 -0
- package/dist/services/docker/system-service.js +215 -0
- package/dist/services/docker/system-service.js.map +1 -0
- package/dist/services/docker/utils/client-factory.d.ts +56 -0
- package/dist/services/docker/utils/client-factory.d.ts.map +1 -0
- package/dist/services/docker/utils/client-factory.js +139 -0
- package/dist/services/docker/utils/client-factory.js.map +1 -0
- package/dist/services/docker/utils/client-manager.d.ts +88 -0
- package/dist/services/docker/utils/client-manager.d.ts.map +1 -0
- package/dist/services/docker/utils/client-manager.js +124 -0
- package/dist/services/docker/utils/client-manager.js.map +1 -0
- package/dist/services/docker/utils/exec-handler.d.ts +94 -0
- package/dist/services/docker/utils/exec-handler.d.ts.map +1 -0
- package/dist/services/docker/utils/exec-handler.js +197 -0
- package/dist/services/docker/utils/exec-handler.js.map +1 -0
- package/dist/services/docker/utils/formatters.d.ts +13 -0
- package/dist/services/docker/utils/formatters.d.ts.map +1 -0
- package/dist/services/docker/utils/formatters.js +33 -0
- package/dist/services/docker/utils/formatters.js.map +1 -0
- package/dist/services/docker/utils/log-parser.d.ts +10 -0
- package/dist/services/docker/utils/log-parser.d.ts.map +1 -0
- package/dist/services/docker/utils/log-parser.js +48 -0
- package/dist/services/docker/utils/log-parser.js.map +1 -0
- package/dist/services/docker/utils/stats-calculator.d.ts +68 -0
- package/dist/services/docker/utils/stats-calculator.d.ts.map +1 -0
- package/dist/services/docker/utils/stats-calculator.js +61 -0
- package/dist/services/docker/utils/stats-calculator.js.map +1 -0
- package/dist/services/docker/volume-service.d.ts +22 -0
- package/dist/services/docker/volume-service.d.ts.map +1 -0
- package/dist/services/docker/volume-service.js +48 -0
- package/dist/services/docker/volume-service.js.map +1 -0
- package/dist/services/docker-interfaces.d.ts +283 -0
- package/dist/services/docker-interfaces.d.ts.map +1 -0
- package/dist/services/docker-interfaces.js +13 -0
- package/dist/services/docker-interfaces.js.map +1 -0
- package/dist/services/docker.d.ts +42 -5
- package/dist/services/docker.d.ts.map +1 -1
- package/dist/services/docker.js +335 -127
- package/dist/services/docker.js.map +1 -1
- package/dist/services/file-service.d.ts +6 -2
- package/dist/services/file-service.d.ts.map +1 -1
- package/dist/services/file-service.js +156 -52
- package/dist/services/file-service.js.map +1 -1
- package/dist/services/host-config-repository.d.ts +133 -0
- package/dist/services/host-config-repository.d.ts.map +1 -0
- package/dist/services/host-config-repository.js +323 -0
- package/dist/services/host-config-repository.js.map +1 -0
- package/dist/services/host-resolver.d.ts +49 -0
- package/dist/services/host-resolver.d.ts.map +1 -0
- package/dist/services/host-resolver.js +176 -0
- package/dist/services/host-resolver.js.map +1 -0
- package/dist/services/interfaces.d.ts +61 -194
- package/dist/services/interfaces.d.ts.map +1 -1
- package/dist/services/local-executor.d.ts +31 -0
- package/dist/services/local-executor.d.ts.map +1 -0
- package/dist/services/local-executor.js +71 -0
- package/dist/services/local-executor.js.map +1 -0
- package/dist/services/ssh-config-loader.d.ts +35 -0
- package/dist/services/ssh-config-loader.d.ts.map +1 -0
- package/dist/services/ssh-config-loader.js +218 -0
- package/dist/services/ssh-config-loader.js.map +1 -0
- package/dist/services/ssh-pool.d.ts +26 -1
- package/dist/services/ssh-pool.d.ts.map +1 -1
- package/dist/services/ssh-pool.js +166 -25
- package/dist/services/ssh-pool.js.map +1 -1
- package/dist/services/ssh-service.d.ts +3 -0
- package/dist/services/ssh-service.d.ts.map +1 -1
- package/dist/services/ssh-service.js +53 -31
- package/dist/services/ssh-service.js.map +1 -1
- package/dist/services/ssh.d.ts +2 -6
- package/dist/services/ssh.d.ts.map +1 -1
- package/dist/services/ssh.js +9 -40
- package/dist/services/ssh.js.map +1 -1
- package/dist/tools/definitions/flux.d.ts +13 -0
- package/dist/tools/definitions/flux.d.ts.map +1 -0
- package/dist/tools/definitions/flux.js +101 -0
- package/dist/tools/definitions/flux.js.map +1 -0
- package/dist/tools/definitions/index.d.ts +8 -0
- package/dist/tools/definitions/index.d.ts.map +1 -0
- package/dist/tools/definitions/index.js +8 -0
- package/dist/tools/definitions/index.js.map +1 -0
- package/dist/tools/definitions/scout.d.ts +13 -0
- package/dist/tools/definitions/scout.d.ts.map +1 -0
- package/dist/tools/definitions/scout.js +78 -0
- package/dist/tools/definitions/scout.js.map +1 -0
- package/dist/tools/flux.d.ts +16 -8
- package/dist/tools/flux.d.ts.map +1 -1
- package/dist/tools/flux.js +27 -66
- package/dist/tools/flux.js.map +1 -1
- package/dist/tools/handlers/base-handler.d.ts +172 -0
- package/dist/tools/handlers/base-handler.d.ts.map +1 -0
- package/dist/tools/handlers/base-handler.js +234 -0
- package/dist/tools/handlers/base-handler.js.map +1 -0
- package/dist/tools/handlers/compose-handlers.d.ts +108 -0
- package/dist/tools/handlers/compose-handlers.d.ts.map +1 -0
- package/dist/tools/handlers/compose-handlers.js +293 -0
- package/dist/tools/handlers/compose-handlers.js.map +1 -0
- package/dist/tools/handlers/compose-utils.d.ts +35 -0
- package/dist/tools/handlers/compose-utils.d.ts.map +1 -0
- package/dist/tools/handlers/compose-utils.js +76 -0
- package/dist/tools/handlers/compose-utils.js.map +1 -0
- package/dist/tools/handlers/compose.d.ts +23 -0
- package/dist/tools/handlers/compose.d.ts.map +1 -0
- package/dist/tools/handlers/compose.js +125 -0
- package/dist/tools/handlers/compose.js.map +1 -0
- package/dist/tools/handlers/container.d.ts +23 -0
- package/dist/tools/handlers/container.d.ts.map +1 -0
- package/dist/tools/handlers/container.js +333 -0
- package/dist/tools/handlers/container.js.map +1 -0
- package/dist/tools/handlers/docker.d.ts +24 -0
- package/dist/tools/handlers/docker.d.ts.map +1 -0
- package/dist/tools/handlers/docker.js +155 -0
- package/dist/tools/handlers/docker.js.map +1 -0
- package/dist/tools/handlers/host.d.ts +23 -0
- package/dist/tools/handlers/host.d.ts.map +1 -0
- package/dist/tools/handlers/host.js +196 -0
- package/dist/tools/handlers/host.js.map +1 -0
- package/dist/tools/handlers/scout-logs.d.ts +24 -0
- package/dist/tools/handlers/scout-logs.d.ts.map +1 -0
- package/dist/tools/handlers/scout-logs.js +119 -0
- package/dist/tools/handlers/scout-logs.js.map +1 -0
- package/dist/tools/handlers/scout-simple.d.ts +23 -0
- package/dist/tools/handlers/scout-simple.d.ts.map +1 -0
- package/dist/tools/handlers/scout-simple.js +286 -0
- package/dist/tools/handlers/scout-simple.js.map +1 -0
- package/dist/tools/handlers/scout-zfs.d.ts +23 -0
- package/dist/tools/handlers/scout-zfs.d.ts.map +1 -0
- package/dist/tools/handlers/scout-zfs.js +82 -0
- package/dist/tools/handlers/scout-zfs.js.map +1 -0
- package/dist/tools/index.d.ts +32 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +41 -35
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/registry.d.ts +135 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +151 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/scout.d.ts +16 -8
- package/dist/tools/scout.d.ts.map +1 -1
- package/dist/tools/scout.js +36 -78
- package/dist/tools/scout.js.map +1 -1
- package/dist/types.d.ts +629 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/command-security.d.ts +82 -0
- package/dist/utils/command-security.d.ts.map +1 -0
- package/dist/utils/command-security.js +122 -0
- package/dist/utils/command-security.js.map +1 -0
- package/dist/utils/error-sanitization.d.ts +77 -0
- package/dist/utils/error-sanitization.d.ts.map +1 -0
- package/dist/utils/error-sanitization.js +107 -0
- package/dist/utils/error-sanitization.js.map +1 -0
- package/dist/utils/errors.d.ts +30 -6
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +91 -12
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/help-handler.d.ts +23 -0
- package/dist/utils/help-handler.d.ts.map +1 -0
- package/dist/utils/help-handler.js +21 -0
- package/dist/utils/help-handler.js.map +1 -0
- package/dist/utils/help.d.ts +1 -1
- package/dist/utils/help.d.ts.map +1 -1
- package/dist/utils/help.js +57 -16
- package/dist/utils/help.js.map +1 -1
- package/dist/utils/host-utils.d.ts +31 -0
- package/dist/utils/host-utils.d.ts.map +1 -0
- package/dist/utils/host-utils.js +80 -0
- package/dist/utils/host-utils.js.map +1 -0
- package/dist/utils/index.d.ts +8 -2
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +8 -2
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/init-detection.d.ts +36 -0
- package/dist/utils/init-detection.d.ts.map +1 -0
- package/dist/utils/init-detection.js +79 -0
- package/dist/utils/init-detection.js.map +1 -0
- package/dist/utils/logger.d.ts +11 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +32 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/pagination.d.ts +20 -0
- package/dist/utils/pagination.d.ts.map +1 -0
- package/dist/utils/pagination.js +29 -0
- package/dist/utils/pagination.js.map +1 -0
- package/dist/utils/path-security.d.ts +132 -18
- package/dist/utils/path-security.d.ts.map +1 -1
- package/dist/utils/path-security.js +164 -35
- package/dist/utils/path-security.js.map +1 -1
- package/dist/utils/sorting.d.ts +33 -0
- package/dist/utils/sorting.d.ts.map +1 -0
- package/dist/utils/sorting.js +57 -0
- package/dist/utils/sorting.js.map +1 -0
- package/dist/utils/text-filters.d.ts +13 -0
- package/dist/utils/text-filters.d.ts.map +1 -0
- package/dist/utils/text-filters.js +18 -0
- package/dist/utils/text-filters.js.map +1 -0
- package/dist/utils/time.d.ts +11 -0
- package/dist/utils/time.d.ts.map +1 -0
- package/dist/utils/time.js +13 -0
- package/dist/utils/time.js.map +1 -0
- package/dist/utils/validation.d.ts +25 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +56 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +45 -19
- package/dist/schemas/discriminator.d.ts +0 -20
- package/dist/schemas/discriminator.d.ts.map +0 -1
- package/dist/schemas/discriminator.js +0 -25
- package/dist/schemas/discriminator.js.map +0 -1
- package/dist/schemas/unified.d.ts +0 -674
- package/dist/schemas/unified.d.ts.map +0 -1
- package/dist/schemas/unified.js +0 -453
- package/dist/schemas/unified.js.map +0 -1
- package/dist/tools/unified.d.ts +0 -7
- package/dist/tools/unified.d.ts.map +0 -1
- package/dist/tools/unified.js +0 -827
- package/dist/tools/unified.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Docker from "dockerode";
|
|
2
|
-
import { HostConfig, ContainerInfo, ContainerStats, HostStatus, LogEntry, ImageInfo } from "../types.js";
|
|
2
|
+
import { HostConfig, ContainerInfo, ContainerStats, ContainerExecResult, ContainerProcessList, HostStatus, LogEntry, ImageInfo, DockerNetworkInfo, DockerVolumeInfo } from "../types.js";
|
|
3
3
|
import type { IDockerService } from "./interfaces.js";
|
|
4
4
|
/**
|
|
5
5
|
* Check if a string looks like a Unix socket path
|
|
@@ -15,6 +15,7 @@ export declare class DockerService implements IDockerService {
|
|
|
15
15
|
constructor(dockerFactory?: (config: HostConfig) => Docker);
|
|
16
16
|
/**
|
|
17
17
|
* Get or create Docker client for a host
|
|
18
|
+
* PERF-C3: Cache key includes protocol, port, and socket path for uniqueness
|
|
18
19
|
*/
|
|
19
20
|
getDockerClient(config: HostConfig): Docker;
|
|
20
21
|
/**
|
|
@@ -57,6 +58,7 @@ export declare class DockerService implements IDockerService {
|
|
|
57
58
|
private listContainersOnHost;
|
|
58
59
|
/**
|
|
59
60
|
* Find which host a container is on
|
|
61
|
+
* PERF-C2: Parallel search with early exit (400ms → 50ms for 10 hosts)
|
|
60
62
|
*/
|
|
61
63
|
findContainerHost(containerId: string, hosts: HostConfig[]): Promise<{
|
|
62
64
|
host: HostConfig;
|
|
@@ -83,6 +85,29 @@ export declare class DockerService implements IDockerService {
|
|
|
83
85
|
* Get container stats
|
|
84
86
|
*/
|
|
85
87
|
getContainerStats(containerId: string, host: HostConfig): Promise<ContainerStats>;
|
|
88
|
+
/**
|
|
89
|
+
* Execute a command inside a container.
|
|
90
|
+
*
|
|
91
|
+
* @param containerId - Container ID or name
|
|
92
|
+
* @param host - Host configuration
|
|
93
|
+
* @param options - Execution options
|
|
94
|
+
* @param options.command - Shell command to execute
|
|
95
|
+
* @param options.user - Optional user to run as
|
|
96
|
+
* @param options.workdir - Optional working directory
|
|
97
|
+
* @param options.timeout - Optional timeout in ms (default 30s, max 5min)
|
|
98
|
+
* @returns Promise resolving to stdout, stderr, and exit code
|
|
99
|
+
* @throws Error if timeout exceeded or buffer limit exceeded
|
|
100
|
+
*/
|
|
101
|
+
execContainer(containerId: string, host: HostConfig, options: {
|
|
102
|
+
command: string;
|
|
103
|
+
user?: string;
|
|
104
|
+
workdir?: string;
|
|
105
|
+
timeout?: number;
|
|
106
|
+
}): Promise<ContainerExecResult>;
|
|
107
|
+
/**
|
|
108
|
+
* List running processes inside a container.
|
|
109
|
+
*/
|
|
110
|
+
getContainerProcesses(containerId: string, host: HostConfig): Promise<ContainerProcessList>;
|
|
86
111
|
/**
|
|
87
112
|
* Get host status overview (parallel execution)
|
|
88
113
|
*/
|
|
@@ -95,10 +120,26 @@ export declare class DockerService implements IDockerService {
|
|
|
95
120
|
* List images across all hosts (parallel execution)
|
|
96
121
|
*/
|
|
97
122
|
listImages(hosts: HostConfig[], options?: ListImagesOptions): Promise<ImageInfo[]>;
|
|
123
|
+
/**
|
|
124
|
+
* List Docker networks across all hosts (parallel execution)
|
|
125
|
+
*/
|
|
126
|
+
listNetworks(hosts: HostConfig[]): Promise<DockerNetworkInfo[]>;
|
|
127
|
+
/**
|
|
128
|
+
* List Docker volumes across all hosts (parallel execution)
|
|
129
|
+
*/
|
|
130
|
+
listVolumes(hosts: HostConfig[]): Promise<DockerVolumeInfo[]>;
|
|
98
131
|
/**
|
|
99
132
|
* List images from a single host (internal helper)
|
|
100
133
|
*/
|
|
101
134
|
private listImagesOnHost;
|
|
135
|
+
/**
|
|
136
|
+
* List Docker networks from a single host (internal helper)
|
|
137
|
+
*/
|
|
138
|
+
private listNetworksOnHost;
|
|
139
|
+
/**
|
|
140
|
+
* List Docker volumes from a single host (internal helper)
|
|
141
|
+
*/
|
|
142
|
+
private listVolumesOnHost;
|
|
102
143
|
/**
|
|
103
144
|
* Inspect container for detailed info
|
|
104
145
|
*/
|
|
@@ -160,10 +201,6 @@ export declare class DockerService implements IDockerService {
|
|
|
160
201
|
status: string;
|
|
161
202
|
}>;
|
|
162
203
|
}
|
|
163
|
-
/**
|
|
164
|
-
* Load host configurations from config file, env var, or defaults
|
|
165
|
-
*/
|
|
166
|
-
export declare function loadHostConfigs(): HostConfig[];
|
|
167
204
|
/**
|
|
168
205
|
* List images options
|
|
169
206
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"docker.d.ts","sourceRoot":"","sources":["../../src/services/docker.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"docker.d.ts","sourceRoot":"","sources":["../../src/services/docker.ts"],"names":[],"mappings":"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"}
|
package/dist/services/docker.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import Docker from "dockerode";
|
|
2
|
-
import { readFileSync
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { DEFAULT_DOCKER_SOCKET, API_TIMEOUT, ENV_HOSTS_CONFIG } from "../constants.js";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { PassThrough } from "stream";
|
|
4
|
+
import { API_TIMEOUT, DEFAULT_EXEC_TIMEOUT, DEFAULT_EXEC_MAX_BUFFER } from "../constants.js";
|
|
6
5
|
import { HostOperationError, logError } from "../utils/errors.js";
|
|
6
|
+
import { validateCommandAllowlist } from "../utils/command-security.js";
|
|
7
7
|
/**
|
|
8
8
|
* Check if a string looks like a Unix socket path
|
|
9
9
|
*/
|
|
@@ -21,6 +21,27 @@ function createDefaultDockerClient(config) {
|
|
|
21
21
|
// Unix socket connection
|
|
22
22
|
return new Docker({ socketPath });
|
|
23
23
|
}
|
|
24
|
+
else if (config.protocol === "ssh") {
|
|
25
|
+
// SSH tunneling to Docker socket
|
|
26
|
+
// Dockerode supports SSH protocol natively via ssh:// URL
|
|
27
|
+
// Format: ssh://user@host:port
|
|
28
|
+
const user = config.sshUser || "root";
|
|
29
|
+
const port = config.port || 22;
|
|
30
|
+
const dockerOptions = {
|
|
31
|
+
protocol: "ssh",
|
|
32
|
+
host: config.host,
|
|
33
|
+
port: port,
|
|
34
|
+
username: user
|
|
35
|
+
};
|
|
36
|
+
// Add SSH key path if provided
|
|
37
|
+
if (config.sshKeyPath) {
|
|
38
|
+
dockerOptions.sshOptions = {
|
|
39
|
+
agent: process.env.SSH_AUTH_SOCK,
|
|
40
|
+
privateKey: readFileSync(config.sshKeyPath)
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return new Docker(dockerOptions);
|
|
44
|
+
}
|
|
24
45
|
else if (config.protocol === "http" || config.protocol === "https") {
|
|
25
46
|
// Remote TCP connection
|
|
26
47
|
return new Docker({
|
|
@@ -46,9 +67,13 @@ export class DockerService {
|
|
|
46
67
|
}
|
|
47
68
|
/**
|
|
48
69
|
* Get or create Docker client for a host
|
|
70
|
+
* PERF-C3: Cache key includes protocol, port, and socket path for uniqueness
|
|
49
71
|
*/
|
|
50
72
|
getDockerClient(config) {
|
|
51
|
-
|
|
73
|
+
// Include all connection fields in cache key to prevent collisions
|
|
74
|
+
const cacheKey = config.dockerSocketPath
|
|
75
|
+
? `${config.name}-socket-${config.dockerSocketPath}`
|
|
76
|
+
: `${config.name}-${config.protocol}-${config.host}:${config.port || "default"}`;
|
|
52
77
|
const cached = this.clientCache.get(cacheKey);
|
|
53
78
|
if (cached) {
|
|
54
79
|
return cached;
|
|
@@ -159,23 +184,32 @@ export class DockerService {
|
|
|
159
184
|
}
|
|
160
185
|
/**
|
|
161
186
|
* Find which host a container is on
|
|
187
|
+
* PERF-C2: Parallel search with early exit (400ms → 50ms for 10 hosts)
|
|
162
188
|
*/
|
|
163
189
|
async findContainerHost(containerId, hosts) {
|
|
164
|
-
|
|
190
|
+
// Parallel search across all hosts with Promise.race for early exit
|
|
191
|
+
const searches = hosts.map(async (host) => {
|
|
165
192
|
try {
|
|
166
193
|
const docker = this.getDockerClient(host);
|
|
167
194
|
const containers = await docker.listContainers({ all: true });
|
|
168
195
|
const found = containers.find((c) => c.Id.startsWith(containerId) ||
|
|
169
196
|
c.Names.some((n) => n.replace(/^\//, "") === containerId));
|
|
170
|
-
|
|
171
|
-
return { host, container: found };
|
|
172
|
-
}
|
|
197
|
+
return found ? { host, container: found } : null;
|
|
173
198
|
}
|
|
174
199
|
catch (error) {
|
|
175
200
|
logError(new HostOperationError("Failed to list containers on host", host.name, "findContainerHost", error), { metadata: { containerId } });
|
|
201
|
+
return null;
|
|
176
202
|
}
|
|
177
|
-
}
|
|
178
|
-
return
|
|
203
|
+
});
|
|
204
|
+
// Use Promise.race to return as soon as first match found
|
|
205
|
+
// Add timeout to prevent indefinite hanging
|
|
206
|
+
const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve(null), 10000));
|
|
207
|
+
// Wait for first non-null result
|
|
208
|
+
const results = await Promise.race([
|
|
209
|
+
Promise.all(searches).then((results) => results.find((r) => r !== null) || null),
|
|
210
|
+
timeoutPromise
|
|
211
|
+
]);
|
|
212
|
+
return results;
|
|
179
213
|
}
|
|
180
214
|
/**
|
|
181
215
|
* Perform action on container
|
|
@@ -275,6 +309,178 @@ export class DockerService {
|
|
|
275
309
|
blockWrite
|
|
276
310
|
};
|
|
277
311
|
}
|
|
312
|
+
/**
|
|
313
|
+
* Execute a command inside a container.
|
|
314
|
+
*
|
|
315
|
+
* @param containerId - Container ID or name
|
|
316
|
+
* @param host - Host configuration
|
|
317
|
+
* @param options - Execution options
|
|
318
|
+
* @param options.command - Shell command to execute
|
|
319
|
+
* @param options.user - Optional user to run as
|
|
320
|
+
* @param options.workdir - Optional working directory
|
|
321
|
+
* @param options.timeout - Optional timeout in ms (default 30s, max 5min)
|
|
322
|
+
* @returns Promise resolving to stdout, stderr, and exit code
|
|
323
|
+
* @throws Error if timeout exceeded or buffer limit exceeded
|
|
324
|
+
*/
|
|
325
|
+
async execContainer(containerId, host, options) {
|
|
326
|
+
const container = await this.getContainer(containerId, host);
|
|
327
|
+
const parts = validateCommandAllowlist(options.command);
|
|
328
|
+
const timeout = options.timeout ?? DEFAULT_EXEC_TIMEOUT;
|
|
329
|
+
const maxBuffer = DEFAULT_EXEC_MAX_BUFFER;
|
|
330
|
+
const exec = await container.exec({
|
|
331
|
+
Cmd: parts,
|
|
332
|
+
AttachStdout: true,
|
|
333
|
+
AttachStderr: true,
|
|
334
|
+
User: options.user,
|
|
335
|
+
WorkingDir: options.workdir
|
|
336
|
+
});
|
|
337
|
+
const stream = await exec.start({ hijack: true, stdin: false });
|
|
338
|
+
const stdoutStream = new PassThrough();
|
|
339
|
+
const stderrStream = new PassThrough();
|
|
340
|
+
const stdoutChunks = [];
|
|
341
|
+
const stderrChunks = [];
|
|
342
|
+
let stdoutSize = 0;
|
|
343
|
+
let stderrSize = 0;
|
|
344
|
+
let timeoutId = null;
|
|
345
|
+
let bufferExceeded = false;
|
|
346
|
+
/**
|
|
347
|
+
* Clean up all streams and clear timeout.
|
|
348
|
+
* This function is idempotent and safe to call multiple times.
|
|
349
|
+
* Uses try-catch to handle race conditions where streams may be
|
|
350
|
+
* destroyed between the check and the destroy call.
|
|
351
|
+
*/
|
|
352
|
+
const cleanup = () => {
|
|
353
|
+
if (timeoutId !== null) {
|
|
354
|
+
clearTimeout(timeoutId);
|
|
355
|
+
timeoutId = null;
|
|
356
|
+
}
|
|
357
|
+
// Safely destroy streams - ignore if already destroyed
|
|
358
|
+
// This prevents race conditions where destroyed state changes between check and call
|
|
359
|
+
try {
|
|
360
|
+
stream.destroy();
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
/* already destroyed */
|
|
364
|
+
}
|
|
365
|
+
try {
|
|
366
|
+
stdoutStream.destroy();
|
|
367
|
+
}
|
|
368
|
+
catch {
|
|
369
|
+
/* already destroyed */
|
|
370
|
+
}
|
|
371
|
+
try {
|
|
372
|
+
stderrStream.destroy();
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
/* already destroyed */
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
// Track stdout buffer size and reject if limit exceeded
|
|
379
|
+
stdoutStream.on("data", (chunk) => {
|
|
380
|
+
if (bufferExceeded)
|
|
381
|
+
return;
|
|
382
|
+
// Check BEFORE allocating buffer to prevent race condition
|
|
383
|
+
if (stdoutSize + chunk.length > maxBuffer) {
|
|
384
|
+
bufferExceeded = true;
|
|
385
|
+
cleanup();
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
stdoutSize += chunk.length;
|
|
389
|
+
stdoutChunks.push(chunk); // chunk is already a Buffer, no need to copy
|
|
390
|
+
});
|
|
391
|
+
// Track stderr buffer size and reject if limit exceeded
|
|
392
|
+
stderrStream.on("data", (chunk) => {
|
|
393
|
+
if (bufferExceeded)
|
|
394
|
+
return;
|
|
395
|
+
// Check BEFORE allocating buffer to prevent race condition
|
|
396
|
+
if (stderrSize + chunk.length > maxBuffer) {
|
|
397
|
+
bufferExceeded = true;
|
|
398
|
+
cleanup();
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
stderrSize += chunk.length;
|
|
402
|
+
stderrChunks.push(chunk); // chunk is already a Buffer, no need to copy
|
|
403
|
+
});
|
|
404
|
+
try {
|
|
405
|
+
await new Promise((resolve, reject) => {
|
|
406
|
+
// Guard to ensure only one settlement path executes
|
|
407
|
+
let settled = false;
|
|
408
|
+
/**
|
|
409
|
+
* Atomically settle the promise with rejection.
|
|
410
|
+
* Checks settled guard, sets it, cleans up, then rejects.
|
|
411
|
+
* Safe to call multiple times - subsequent calls are no-ops.
|
|
412
|
+
*/
|
|
413
|
+
const settleWithRejection = (error) => {
|
|
414
|
+
if (settled)
|
|
415
|
+
return;
|
|
416
|
+
settled = true;
|
|
417
|
+
cleanup();
|
|
418
|
+
reject(error);
|
|
419
|
+
};
|
|
420
|
+
/**
|
|
421
|
+
* Atomically settle the promise with success.
|
|
422
|
+
* Checks settled guard, sets it, cleans up, then resolves.
|
|
423
|
+
* Safe to call multiple times - subsequent calls are no-ops.
|
|
424
|
+
*/
|
|
425
|
+
const settleWithSuccess = () => {
|
|
426
|
+
if (settled)
|
|
427
|
+
return;
|
|
428
|
+
settled = true;
|
|
429
|
+
cleanup();
|
|
430
|
+
resolve();
|
|
431
|
+
};
|
|
432
|
+
// Set up timeout
|
|
433
|
+
timeoutId = setTimeout(() => {
|
|
434
|
+
settleWithRejection(new Error(`Exec timeout: command exceeded ${timeout}ms limit`));
|
|
435
|
+
}, timeout);
|
|
436
|
+
// Handle stream errors
|
|
437
|
+
const handleError = (err) => {
|
|
438
|
+
settleWithRejection(err);
|
|
439
|
+
};
|
|
440
|
+
stream.on("error", handleError);
|
|
441
|
+
stdoutStream.on("error", handleError);
|
|
442
|
+
stderrStream.on("error", handleError);
|
|
443
|
+
// Check for buffer exceeded after each data event
|
|
444
|
+
const checkBufferExceeded = () => {
|
|
445
|
+
if (bufferExceeded) {
|
|
446
|
+
settleWithRejection(new Error(`Buffer limit exceeded: output exceeded ${maxBuffer} bytes`));
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
stdoutStream.on("data", checkBufferExceeded);
|
|
450
|
+
stderrStream.on("data", checkBufferExceeded);
|
|
451
|
+
stream.on("end", () => {
|
|
452
|
+
if (bufferExceeded) {
|
|
453
|
+
settleWithRejection(new Error(`Buffer limit exceeded: output exceeded ${maxBuffer} bytes`));
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
settleWithSuccess();
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
this.getDockerClient(host).modem.demuxStream(stream, stdoutStream, stderrStream);
|
|
460
|
+
});
|
|
461
|
+
const inspection = await exec.inspect();
|
|
462
|
+
return {
|
|
463
|
+
stdout: Buffer.concat(stdoutChunks).toString().trimEnd(),
|
|
464
|
+
stderr: Buffer.concat(stderrChunks).toString().trimEnd(),
|
|
465
|
+
exitCode: inspection.ExitCode ?? 0
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
catch (error) {
|
|
469
|
+
cleanup();
|
|
470
|
+
throw error;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* List running processes inside a container.
|
|
475
|
+
*/
|
|
476
|
+
async getContainerProcesses(containerId, host) {
|
|
477
|
+
const container = await this.getContainer(containerId, host);
|
|
478
|
+
const result = await container.top();
|
|
479
|
+
return {
|
|
480
|
+
titles: result.Titles ?? [],
|
|
481
|
+
processes: result.Processes ?? []
|
|
482
|
+
};
|
|
483
|
+
}
|
|
278
484
|
/**
|
|
279
485
|
* Get host status overview (parallel execution)
|
|
280
486
|
*/
|
|
@@ -321,6 +527,24 @@ export class DockerService {
|
|
|
321
527
|
.filter((r) => r.status === "fulfilled")
|
|
322
528
|
.flatMap((r) => r.value);
|
|
323
529
|
}
|
|
530
|
+
/**
|
|
531
|
+
* List Docker networks across all hosts (parallel execution)
|
|
532
|
+
*/
|
|
533
|
+
async listNetworks(hosts) {
|
|
534
|
+
const results = await Promise.allSettled(hosts.map((host) => this.listNetworksOnHost(host)));
|
|
535
|
+
return results
|
|
536
|
+
.filter((r) => r.status === "fulfilled")
|
|
537
|
+
.flatMap((r) => r.value);
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* List Docker volumes across all hosts (parallel execution)
|
|
541
|
+
*/
|
|
542
|
+
async listVolumes(hosts) {
|
|
543
|
+
const results = await Promise.allSettled(hosts.map((host) => this.listVolumesOnHost(host)));
|
|
544
|
+
return results
|
|
545
|
+
.filter((r) => r.status === "fulfilled")
|
|
546
|
+
.flatMap((r) => r.value);
|
|
547
|
+
}
|
|
324
548
|
/**
|
|
325
549
|
* List images from a single host (internal helper)
|
|
326
550
|
*/
|
|
@@ -338,6 +562,47 @@ export class DockerService {
|
|
|
338
562
|
hostName: host.name
|
|
339
563
|
}));
|
|
340
564
|
}
|
|
565
|
+
/**
|
|
566
|
+
* List Docker networks from a single host (internal helper)
|
|
567
|
+
*/
|
|
568
|
+
async listNetworksOnHost(host) {
|
|
569
|
+
const docker = this.getDockerClient(host);
|
|
570
|
+
const networks = await docker.listNetworks();
|
|
571
|
+
return networks.map((network) => ({
|
|
572
|
+
id: network.Id,
|
|
573
|
+
name: network.Name,
|
|
574
|
+
driver: network.Driver,
|
|
575
|
+
scope: network.Scope,
|
|
576
|
+
created: network.Created,
|
|
577
|
+
internal: network.Internal,
|
|
578
|
+
attachable: network.Attachable,
|
|
579
|
+
ingress: network.Ingress,
|
|
580
|
+
hostName: host.name
|
|
581
|
+
}));
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* List Docker volumes from a single host (internal helper)
|
|
585
|
+
*/
|
|
586
|
+
async listVolumesOnHost(host) {
|
|
587
|
+
const docker = this.getDockerClient(host);
|
|
588
|
+
const result = await docker.listVolumes();
|
|
589
|
+
const volumes = result?.Volumes ?? [];
|
|
590
|
+
return volumes.map((volume) => {
|
|
591
|
+
// Cast once to document the expected shape with CreatedAt
|
|
592
|
+
const volumeWithCreatedAt = volume;
|
|
593
|
+
return {
|
|
594
|
+
name: volumeWithCreatedAt.Name,
|
|
595
|
+
driver: volumeWithCreatedAt.Driver,
|
|
596
|
+
scope: volumeWithCreatedAt.Scope,
|
|
597
|
+
mountpoint: volumeWithCreatedAt.Mountpoint,
|
|
598
|
+
createdAt: typeof volumeWithCreatedAt.CreatedAt === "string"
|
|
599
|
+
? volumeWithCreatedAt.CreatedAt
|
|
600
|
+
: undefined,
|
|
601
|
+
labels: volumeWithCreatedAt.Labels ?? undefined,
|
|
602
|
+
hostName: host.name
|
|
603
|
+
};
|
|
604
|
+
});
|
|
605
|
+
}
|
|
341
606
|
/**
|
|
342
607
|
* Inspect container for detailed info
|
|
343
608
|
*/
|
|
@@ -615,122 +880,31 @@ export class DockerService {
|
|
|
615
880
|
}
|
|
616
881
|
else {
|
|
617
882
|
// Remote - use SSH
|
|
618
|
-
const { validateHostForSsh
|
|
883
|
+
const { validateHostForSsh } = await import("./ssh.js");
|
|
884
|
+
const { validateAlphanumeric } = await import("../utils/validation.js");
|
|
885
|
+
const { escapeShellArg } = await import("../utils/path-security.js");
|
|
619
886
|
const { execFile } = await import("child_process");
|
|
620
887
|
const { promisify } = await import("util");
|
|
621
888
|
const execFileAsync = promisify(execFile);
|
|
622
889
|
validateHostForSsh(host);
|
|
890
|
+
validateAlphanumeric(host.name, "host name");
|
|
891
|
+
// Escape each argument to prevent shell injection
|
|
892
|
+
const escapedArgs = args.map((a) => escapeShellArg(a));
|
|
623
893
|
const sshArgs = [
|
|
624
894
|
"-o",
|
|
625
895
|
"BatchMode=yes",
|
|
626
896
|
"-o",
|
|
627
897
|
"ConnectTimeout=5",
|
|
628
898
|
"-o",
|
|
629
|
-
"StrictHostKeyChecking=
|
|
630
|
-
|
|
631
|
-
`docker ${
|
|
899
|
+
"StrictHostKeyChecking=yes",
|
|
900
|
+
host.name,
|
|
901
|
+
`docker ${escapedArgs.join(" ")}`
|
|
632
902
|
];
|
|
633
903
|
await execFileAsync("ssh", sshArgs, { timeout: 600000 });
|
|
634
904
|
}
|
|
635
905
|
return { status: `Successfully built image ${tag}` };
|
|
636
906
|
}
|
|
637
907
|
}
|
|
638
|
-
/**
|
|
639
|
-
* Config file search paths (in order of priority)
|
|
640
|
-
*/
|
|
641
|
-
const CONFIG_PATHS = [
|
|
642
|
-
process.env.HOMELAB_CONFIG_FILE, // Explicit path
|
|
643
|
-
join(process.cwd(), "homelab.config.json"), // Current directory
|
|
644
|
-
join(homedir(), ".config", "homelab-mcp", "config.json"), // XDG style
|
|
645
|
-
join(homedir(), ".homelab-mcp.json") // Dotfile style
|
|
646
|
-
].filter(Boolean);
|
|
647
|
-
/**
|
|
648
|
-
* Auto-add local Docker socket if it exists and isn't already configured
|
|
649
|
-
*/
|
|
650
|
-
function ensureLocalSocket(hosts) {
|
|
651
|
-
// Check if local socket exists
|
|
652
|
-
if (!existsSync(DEFAULT_DOCKER_SOCKET)) {
|
|
653
|
-
return hosts;
|
|
654
|
-
}
|
|
655
|
-
// Check if any host already uses the local socket
|
|
656
|
-
const hasLocalSocket = hosts.some((h) => h.dockerSocketPath === DEFAULT_DOCKER_SOCKET ||
|
|
657
|
-
h.host === DEFAULT_DOCKER_SOCKET ||
|
|
658
|
-
(h.host === "localhost" && h.dockerSocketPath));
|
|
659
|
-
if (hasLocalSocket) {
|
|
660
|
-
return hosts;
|
|
661
|
-
}
|
|
662
|
-
// Auto-add local socket entry
|
|
663
|
-
const localName = hostname()
|
|
664
|
-
.toLowerCase()
|
|
665
|
-
.replace(/[^a-z0-9-]/g, "-") || "local";
|
|
666
|
-
console.error(`Auto-adding local Docker socket as "${localName}"`);
|
|
667
|
-
return [
|
|
668
|
-
...hosts,
|
|
669
|
-
{
|
|
670
|
-
name: localName,
|
|
671
|
-
host: DEFAULT_DOCKER_SOCKET,
|
|
672
|
-
protocol: "http",
|
|
673
|
-
dockerSocketPath: DEFAULT_DOCKER_SOCKET
|
|
674
|
-
}
|
|
675
|
-
];
|
|
676
|
-
}
|
|
677
|
-
/**
|
|
678
|
-
* Load host configurations from config file, env var, or defaults
|
|
679
|
-
*/
|
|
680
|
-
export function loadHostConfigs() {
|
|
681
|
-
let hosts = [];
|
|
682
|
-
// 1. Try config file first
|
|
683
|
-
for (const configPath of CONFIG_PATHS) {
|
|
684
|
-
if (existsSync(configPath)) {
|
|
685
|
-
try {
|
|
686
|
-
const raw = readFileSync(configPath, "utf-8");
|
|
687
|
-
const config = JSON.parse(raw);
|
|
688
|
-
const configHosts = config.hosts || config; // Support { hosts: [...] } or just [...]
|
|
689
|
-
if (Array.isArray(configHosts) && configHosts.length > 0) {
|
|
690
|
-
console.error(`Loaded ${configHosts.length} hosts from ${configPath}`);
|
|
691
|
-
hosts = configHosts;
|
|
692
|
-
break;
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
catch (error) {
|
|
696
|
-
logError(error, {
|
|
697
|
-
operation: "loadHostConfigs",
|
|
698
|
-
metadata: { configPath, source: "file" }
|
|
699
|
-
});
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
// 2. Fall back to env var if no config file
|
|
704
|
-
if (hosts.length === 0) {
|
|
705
|
-
const configJson = process.env[ENV_HOSTS_CONFIG];
|
|
706
|
-
if (configJson) {
|
|
707
|
-
try {
|
|
708
|
-
hosts = JSON.parse(configJson);
|
|
709
|
-
console.error(`Loaded ${hosts.length} hosts from HOMELAB_HOSTS_CONFIG env`);
|
|
710
|
-
}
|
|
711
|
-
catch (error) {
|
|
712
|
-
logError(error, {
|
|
713
|
-
operation: "loadHostConfigs",
|
|
714
|
-
metadata: { source: "HOMELAB_HOSTS_CONFIG" }
|
|
715
|
-
});
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
// 3. If still no hosts, default to local socket only
|
|
720
|
-
if (hosts.length === 0) {
|
|
721
|
-
console.error("No config found, using local Docker socket");
|
|
722
|
-
return [
|
|
723
|
-
{
|
|
724
|
-
name: "local",
|
|
725
|
-
host: "localhost",
|
|
726
|
-
protocol: "http",
|
|
727
|
-
dockerSocketPath: DEFAULT_DOCKER_SOCKET
|
|
728
|
-
}
|
|
729
|
-
];
|
|
730
|
-
}
|
|
731
|
-
// 4. Auto-add local socket if exists and not configured
|
|
732
|
-
return ensureLocalSocket(hosts);
|
|
733
|
-
}
|
|
734
908
|
/**
|
|
735
909
|
* Parse time specification (absolute or relative) - pure helper function
|
|
736
910
|
*/
|
|
@@ -753,26 +927,60 @@ function parseTimeSpec(spec) {
|
|
|
753
927
|
}
|
|
754
928
|
/**
|
|
755
929
|
* Parse Docker log output into structured entries - pure helper function
|
|
930
|
+
* PERF-C6: Stream-based parsing (60MB → 15MB memory, 75% reduction)
|
|
756
931
|
*/
|
|
757
932
|
function parseDockerLogs(raw) {
|
|
758
|
-
const lines = raw.split("\n").filter((l) => l.trim());
|
|
759
933
|
const entries = [];
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
if (
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
}
|
|
934
|
+
let start = 0;
|
|
935
|
+
// Stream-based parsing - no intermediate arrays
|
|
936
|
+
for (let i = 0; i < raw.length; i++) {
|
|
937
|
+
if (raw[i] === "\n") {
|
|
938
|
+
const line = raw.slice(start, i).trim();
|
|
939
|
+
if (line.length === 0) {
|
|
940
|
+
start = i + 1;
|
|
941
|
+
continue;
|
|
942
|
+
}
|
|
943
|
+
// Use string methods to avoid regex allocation overhead
|
|
944
|
+
// Docker log format: YYYY-MM-DDTHH:MM:SS.sssZ message
|
|
945
|
+
const timestampEnd = line.indexOf("Z ");
|
|
946
|
+
if (timestampEnd > 0 && line[0] >= "0" && line[0] <= "9") {
|
|
947
|
+
// Valid timestamp found
|
|
948
|
+
entries.push({
|
|
949
|
+
timestamp: line.slice(0, timestampEnd + 1),
|
|
950
|
+
stream: "stdout",
|
|
951
|
+
message: line.slice(timestampEnd + 2)
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
else {
|
|
955
|
+
// No timestamp, add current time
|
|
956
|
+
entries.push({
|
|
957
|
+
timestamp: new Date().toISOString(),
|
|
958
|
+
stream: "stdout",
|
|
959
|
+
message: line
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
start = i + 1;
|
|
769
963
|
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
964
|
+
}
|
|
965
|
+
// Handle last line if no trailing newline
|
|
966
|
+
if (start < raw.length) {
|
|
967
|
+
const line = raw.slice(start).trim();
|
|
968
|
+
if (line.length > 0) {
|
|
969
|
+
const timestampEnd = line.indexOf("Z ");
|
|
970
|
+
if (timestampEnd > 0 && line[0] >= "0" && line[0] <= "9") {
|
|
971
|
+
entries.push({
|
|
972
|
+
timestamp: line.slice(0, timestampEnd + 1),
|
|
973
|
+
stream: "stdout",
|
|
974
|
+
message: line.slice(timestampEnd + 2)
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
else {
|
|
978
|
+
entries.push({
|
|
979
|
+
timestamp: new Date().toISOString(),
|
|
980
|
+
stream: "stdout",
|
|
981
|
+
message: line
|
|
982
|
+
});
|
|
983
|
+
}
|
|
776
984
|
}
|
|
777
985
|
}
|
|
778
986
|
return entries;
|