From 244c83a0c3102a797658d35d087bf47366f6df75 Mon Sep 17 00:00:00 2001 From: Anso Date: Sun, 29 Mar 2026 12:56:30 -0400 Subject: [PATCH] feat(registries): add private registry credential management (Team Pro) (#240) Add centralized credential storage for private Docker registries with support for Docker Hub, GHCR, AWS ECR, and self-hosted registries. - New `registries` table with AES-256-GCM encrypted secrets - RegistryService with CRUD, test connectivity, Docker config generation - 5 API endpoints gated by requireTeamPro + requireAdmin - ComposeService injects credentials via temp DOCKER_CONFIG on deploy/pull - ImageUpdateService passes stored credentials for private registry checks - AWS ECR just-in-time token refresh via @aws-sdk/client-ecr - RegistriesSection UI in Settings Hub with type-aware form - Documentation with screenshots --- CHANGELOG.md | 4 + backend/package-lock.json | 1274 ++++++++++++++++- backend/package.json | 1 + backend/src/index.ts | 124 ++ backend/src/services/ComposeService.ts | 56 +- backend/src/services/DatabaseService.ts | 70 + backend/src/services/ImageUpdateService.ts | 30 +- backend/src/services/RegistryService.ts | 263 ++++ docs/docs.json | 1 + docs/features/overview.mdx | 4 + docs/features/private-registries.mdx | 114 ++ .../registries-add-form.png | Bin 0 -> 57435 bytes .../registries-overview.png | Bin 0 -> 47706 bytes .../registries-with-entry.png | Bin 0 -> 49021 bytes frontend/src/components/RegistriesSection.tsx | 384 +++++ frontend/src/components/SettingsModal.tsx | 12 +- 16 files changed, 2317 insertions(+), 20 deletions(-) create mode 100644 backend/src/services/RegistryService.ts create mode 100644 docs/features/private-registries.mdx create mode 100644 docs/images/private-registries/registries-add-form.png create mode 100644 docs/images/private-registries/registries-overview.png create mode 100644 docs/images/private-registries/registries-with-entry.png create mode 100644 frontend/src/components/RegistriesSection.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index fd60d7a3..edd15464 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +* **registries:** add private registry credential management (Team Pro). Store encrypted credentials for Docker Hub, GHCR, AWS ECR, and self-hosted registries. Credentials are automatically injected during deploy/pull via temporary DOCKER_CONFIG. ECR short-lived tokens are refreshed on every operation. Image update checks now authenticate against private registries. + ### Changed * **ui:** redesign top bar into three-zone layout — node pill (left), animated navigation group (center), utilities (right). Replaces flat row of individual buttons with a cohesive tab-style navigation using animated sliding highlight. Includes responsive behavior: icon+text at xl, icons-only at md, sheet drawer on mobile. diff --git a/backend/package-lock.json b/backend/package-lock.json index 2ddaa86e..62d8c7e3 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "SEE LICENSE IN LICENSE", "dependencies": { + "@aws-sdk/client-ecr": "^3.1019.0", "@types/cors": "^2.8.19", "@types/dockerode": "^4.0.1", "@types/express": "^5.0.6", @@ -55,6 +56,612 @@ "vitest": "^4.1.0" } }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-ecr": { + "version": "3.1019.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ecr/-/client-ecr-3.1019.0.tgz", + "integrity": "sha512-lt/BsmFm2JUcqvgK5ifEZxVmOGmzFl5eGtS4QzhSf7zsN1u55qU3K7iTP5nMfR+eQO02I/72x5ZbexSCQumK/A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.25", + "@aws-sdk/credential-provider-node": "^3.972.27", + "@aws-sdk/middleware-host-header": "^3.972.8", + "@aws-sdk/middleware-logger": "^3.972.8", + "@aws-sdk/middleware-recursion-detection": "^3.972.9", + "@aws-sdk/middleware-user-agent": "^3.972.26", + "@aws-sdk/region-config-resolver": "^3.972.10", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-endpoints": "^3.996.5", + "@aws-sdk/util-user-agent-browser": "^3.972.8", + "@aws-sdk/util-user-agent-node": "^3.973.12", + "@smithy/config-resolver": "^4.4.13", + "@smithy/core": "^3.23.12", + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/hash-node": "^4.2.12", + "@smithy/invalid-dependency": "^4.2.12", + "@smithy/middleware-content-length": "^4.2.12", + "@smithy/middleware-endpoint": "^4.4.27", + "@smithy/middleware-retry": "^4.4.44", + "@smithy/middleware-serde": "^4.2.15", + "@smithy/middleware-stack": "^4.2.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/node-http-handler": "^4.5.0", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.7", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.43", + "@smithy/util-defaults-mode-node": "^4.2.47", + "@smithy/util-endpoints": "^3.3.3", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-retry": "^4.2.12", + "@smithy/util-utf8": "^4.2.2", + "@smithy/util-waiter": "^4.2.13", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.973.25", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.25.tgz", + "integrity": "sha512-TNrx7eq6nKNOO62HWPqoBqPLXEkW6nLZQGwjL6lq1jZtigWYbK1NbCnT7mKDzbLMHZfuOECUt3n6CzxjUW9HWQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/xml-builder": "^3.972.16", + "@smithy/core": "^3.23.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/signature-v4": "^5.3.12", + "@smithy/smithy-client": "^4.12.7", + "@smithy/types": "^4.13.1", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.23", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.23.tgz", + "integrity": "sha512-EamaclJcCEaPHp6wiVknNMM2RlsPMjAHSsYSFLNENBM8Wz92QPc6cOn3dif6vPDQt0Oo4IEghDy3NMDCzY/IvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.25", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.25", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.25.tgz", + "integrity": "sha512-qPymamdPcLp6ugoVocG1y5r69ScNiRzb0hogX25/ij+Wz7c7WnsgjLTaz7+eB5BfRxeyUwuw5hgULMuwOGOpcw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.25", + "@aws-sdk/types": "^3.973.6", + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/node-http-handler": "^4.5.0", + "@smithy/property-provider": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.7", + "@smithy/types": "^4.13.1", + "@smithy/util-stream": "^4.5.20", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.26", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.26.tgz", + "integrity": "sha512-xKxEAMuP6GYx2y5GET+d3aGEroax3AgGfwBE65EQAUe090lzyJ/RzxPX9s8v7Z6qAk0XwfQl+LrmH05X7YvTeg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.25", + "@aws-sdk/credential-provider-env": "^3.972.23", + "@aws-sdk/credential-provider-http": "^3.972.25", + "@aws-sdk/credential-provider-login": "^3.972.26", + "@aws-sdk/credential-provider-process": "^3.972.23", + "@aws-sdk/credential-provider-sso": "^3.972.26", + "@aws-sdk/credential-provider-web-identity": "^3.972.26", + "@aws-sdk/nested-clients": "^3.996.16", + "@aws-sdk/types": "^3.973.6", + "@smithy/credential-provider-imds": "^4.2.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.26", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.26.tgz", + "integrity": "sha512-EFcM8RM3TUxnZOfMJo++3PnyxFu1fL/huzmn3Vh+8IWRgqZawUD3cRwwOr+/4bE9DpyHaLOWFAjY0lfK5X9ZkQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.25", + "@aws-sdk/nested-clients": "^3.996.16", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.27", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.27.tgz", + "integrity": "sha512-jXpxSolfFnPVj6GCTtx3xIdWNoDR7hYC/0SbetGZxOC9UnNmipHeX1k6spVstf7eWJrMhXNQEgXC0pD1r5tXIg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.23", + "@aws-sdk/credential-provider-http": "^3.972.25", + "@aws-sdk/credential-provider-ini": "^3.972.26", + "@aws-sdk/credential-provider-process": "^3.972.23", + "@aws-sdk/credential-provider-sso": "^3.972.26", + "@aws-sdk/credential-provider-web-identity": "^3.972.26", + "@aws-sdk/types": "^3.973.6", + "@smithy/credential-provider-imds": "^4.2.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.23", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.23.tgz", + "integrity": "sha512-IL/TFW59++b7MpHserjUblGrdP5UXy5Ekqqx1XQkERXBFJcZr74I7VaSrQT5dxdRMU16xGK4L0RQ5fQG1pMgnA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.25", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.26", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.26.tgz", + "integrity": "sha512-c6ghvRb6gTlMznWhGxn/bpVCcp0HRaz4DobGVD9kI4vwHq186nU2xN/S7QGkm0lo0H2jQU8+dgpUFLxfTcwCOg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.25", + "@aws-sdk/nested-clients": "^3.996.16", + "@aws-sdk/token-providers": "3.1019.0", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.26", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.26.tgz", + "integrity": "sha512-cXcS3+XD3iwhoXkM44AmxjmbcKueoLCINr1e+IceMmCySda5ysNIfiGBGe9qn5EMiQ9Jd7pP0AGFtcd6OV3Lvg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.25", + "@aws-sdk/nested-clients": "^3.996.16", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.8.tgz", + "integrity": "sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.8.tgz", + "integrity": "sha512-CWl5UCM57WUFaFi5kB7IBY1UmOeLvNZAZ2/OZ5l20ldiJ3TiIz1pC65gYj8X0BCPWkeR1E32mpsCk1L1I4n+lA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.9.tgz", + "integrity": "sha512-/Wt5+CT8dpTFQxEJ9iGy/UGrXr7p2wlIOEHvIr/YcHYByzoLjrqkYqXdJjd9UIgWjv7eqV2HnFJen93UTuwfTQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.972.26", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.26.tgz", + "integrity": "sha512-AilFIh4rI/2hKyyGN6XrB0yN96W2o7e7wyrPWCM6QjZM1mcC/pVkW3IWWRvuBWMpVP8Fg+rMpbzeLQ6dTM4gig==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.25", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-endpoints": "^3.996.5", + "@smithy/core": "^3.23.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-retry": "^4.2.12", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.996.16", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.16.tgz", + "integrity": "sha512-L7Qzoj/qQU1cL5GnYLQP5LbI+wlLCLoINvcykR3htKcQ4tzrPf2DOs72x933BM7oArYj1SKrkb2lGlsJHIic3g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.25", + "@aws-sdk/middleware-host-header": "^3.972.8", + "@aws-sdk/middleware-logger": "^3.972.8", + "@aws-sdk/middleware-recursion-detection": "^3.972.9", + "@aws-sdk/middleware-user-agent": "^3.972.26", + "@aws-sdk/region-config-resolver": "^3.972.10", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-endpoints": "^3.996.5", + "@aws-sdk/util-user-agent-browser": "^3.972.8", + "@aws-sdk/util-user-agent-node": "^3.973.12", + "@smithy/config-resolver": "^4.4.13", + "@smithy/core": "^3.23.12", + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/hash-node": "^4.2.12", + "@smithy/invalid-dependency": "^4.2.12", + "@smithy/middleware-content-length": "^4.2.12", + "@smithy/middleware-endpoint": "^4.4.27", + "@smithy/middleware-retry": "^4.4.44", + "@smithy/middleware-serde": "^4.2.15", + "@smithy/middleware-stack": "^4.2.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/node-http-handler": "^4.5.0", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.7", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.43", + "@smithy/util-defaults-mode-node": "^4.2.47", + "@smithy/util-endpoints": "^3.3.3", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-retry": "^4.2.12", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.10.tgz", + "integrity": "sha512-1dq9ToC6e070QvnVhhbAs3bb5r6cQ10gTVc6cyRV5uvQe7P138TV2uG2i6+Yok4bAkVAcx5AqkTEBUvWEtBlsQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@smithy/config-resolver": "^4.4.13", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1019.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1019.0.tgz", + "integrity": "sha512-OF+2RfRmUKyjzrRWlDcyju3RBsuqcrYDQ8TwrJg8efcOotMzuZN4U9mpVTIdATpmEc4lWNZBMSjPzrGm6JPnAQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.25", + "@aws-sdk/nested-clients": "^3.996.16", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.973.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.6.tgz", + "integrity": "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.996.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.5.tgz", + "integrity": "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-endpoints": "^3.3.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", + "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.8.tgz", + "integrity": "sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.973.12", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.12.tgz", + "integrity": "sha512-8phW0TS8ntENJgDcFewYT/Q8dOmarpvSxEjATu2GUBAutiHr++oEGCiBUwxslCMNvwW2cAPZNT53S/ym8zm/gg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "^3.972.26", + "@aws-sdk/types": "^3.973.6", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-config-provider": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.16", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.16.tgz", + "integrity": "sha512-iu2pyvaqmeatIJLURLqx9D+4jKAdTH20ntzB6BFwjyN7V960r4jK32mx0Zf7YbtOYAbmbtQfDNuL60ONinyw7A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "fast-xml-parser": "5.5.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", + "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@balena/dockerignore": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", @@ -751,6 +1358,601 @@ "dev": true, "license": "MIT" }, + "node_modules/@smithy/abort-controller": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.12.tgz", + "integrity": "sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.13.tgz", + "integrity": "sha512-iIzMC5NmOUP6WL6o8iPBjFhUhBZ9pPjpUpQYWMUFQqKyXXzOftbfK8zcQCz/jFV1Psmf05BK5ypx4K2r4Tnwdg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-config-provider": "^4.2.2", + "@smithy/util-endpoints": "^3.3.3", + "@smithy/util-middleware": "^4.2.12", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.23.12", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.12.tgz", + "integrity": "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-stream": "^4.5.20", + "@smithy/util-utf8": "^4.2.2", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.12.tgz", + "integrity": "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.15", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.15.tgz", + "integrity": "sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.12", + "@smithy/querystring-builder": "^4.2.12", + "@smithy/types": "^4.13.1", + "@smithy/util-base64": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.12.tgz", + "integrity": "sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.12.tgz", + "integrity": "sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.12.tgz", + "integrity": "sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.4.27", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.27.tgz", + "integrity": "sha512-T3TFfUgXQlpcg+UdzcAISdZpj4Z+XECZ/cefgA6wLBd6V4lRi0svN2hBouN/be9dXQ31X4sLWz3fAQDf+nt6BA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.12", + "@smithy/middleware-serde": "^4.2.15", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-middleware": "^4.2.12", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.4.44", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.44.tgz", + "integrity": "sha512-Y1Rav7m5CFRPQyM4CI0koD/bXjyjJu3EQxZZhtLGD88WIrBrQ7kqXM96ncd6rYnojwOo/u9MXu57JrEvu/nLrA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/service-error-classification": "^4.2.12", + "@smithy/smithy-client": "^4.12.7", + "@smithy/types": "^4.13.1", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-retry": "^4.2.12", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.15", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.15.tgz", + "integrity": "sha512-ExYhcltZSli0pgAKOpQQe1DLFBLryeZ22605y/YS+mQpdNWekum9Ujb/jMKfJKgjtz1AZldtwA/wCYuKJgjjlg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.12.tgz", + "integrity": "sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.12", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.12.tgz", + "integrity": "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.0.tgz", + "integrity": "sha512-Rnq9vQWiR1+/I6NZZMNzJHV6pZYyEHt2ZnuV3MG8z2NNenC4i/8Kzttz7CjZiHSmsN5frhXhg17z3Zqjjhmz1A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/querystring-builder": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.12.tgz", + "integrity": "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.12.tgz", + "integrity": "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "@smithy/util-uri-escape": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.12.tgz", + "integrity": "sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.12.tgz", + "integrity": "sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.7.tgz", + "integrity": "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.12.tgz", + "integrity": "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.2", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-uri-escape": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.12.7", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.7.tgz", + "integrity": "sha512-q3gqnwml60G44FECaEEsdQMplYhDMZYCtYhMCzadCnRnnHIobZJjegmdoUo6ieLQlPUzvrMdIJUpx6DoPmzANQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.12", + "@smithy/middleware-endpoint": "^4.4.27", + "@smithy/middleware-stack": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-stream": "^4.5.20", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.12.tgz", + "integrity": "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.2.tgz", + "integrity": "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.2.tgz", + "integrity": "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.3.tgz", + "integrity": "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.2.tgz", + "integrity": "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.43", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.43.tgz", + "integrity": "sha512-Qd/0wCKMaXxev/z00TvNzGCH2jlKKKxXP1aDxB6oKwSQthe3Og2dMhSayGCnsma1bK/kQX1+X7SMP99t6FgiiQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.12", + "@smithy/smithy-client": "^4.12.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.47", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.47.tgz", + "integrity": "sha512-qSRbYp1EQ7th+sPFuVcVO05AE0QH635hycdEXlpzIahqHHf2Fyd/Zl+8v0XYMJ3cgDVPa0lkMefU7oNUjAP+DQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.4.13", + "@smithy/credential-provider-imds": "^4.2.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/smithy-client": "^4.12.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.3.tgz", + "integrity": "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", + "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.12.tgz", + "integrity": "sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.5.20", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.20.tgz", + "integrity": "sha512-4yXLm5n/B5SRBR2p8cZ90Sbv4zL4NKsgxdzCzp/83cXw2KxLEumt5p+GAVyRNZgQOSrzXn9ARpO0lUe8XSlSDw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/node-http-handler": "^4.5.0", + "@smithy/types": "^4.13.1", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-waiter": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.13.tgz", + "integrity": "sha512-2zdZ9DTHngRtcYxJK1GUDxruNr53kv5W2Lupe0LMU+Imr6ohQg8M2T14MNkj1Y0wS3FFwpgpGQyvuaMF7CiTmQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.2.tgz", + "integrity": "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -1568,6 +2770,12 @@ "url": "https://opencollective.com/express" } }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", @@ -2575,6 +3783,41 @@ ], "license": "BSD-3-Clause" }, + "node_modules/fast-xml-builder": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", + "integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.1.3" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.5.8", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.8.tgz", + "integrity": "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "fast-xml-builder": "^1.1.4", + "path-expression-matcher": "^1.2.0", + "strnum": "^2.2.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -4111,6 +5354,21 @@ "node": ">=8" } }, + "node_modules/path-expression-matcher": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.2.0.tgz", + "integrity": "sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -4803,6 +6061,18 @@ "node": ">=0.10.0" } }, + "node_modules/strnum": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.2.tgz", + "integrity": "sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/superagent": { "version": "10.3.0", "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz", @@ -5073,9 +6343,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true + "license": "0BSD" }, "node_modules/tunnel-agent": { "version": "0.6.0", diff --git a/backend/package.json b/backend/package.json index b23dd205..8a1a612e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -33,6 +33,7 @@ "vitest": "^4.1.0" }, "dependencies": { + "@aws-sdk/client-ecr": "^3.1019.0", "@types/cors": "^2.8.19", "@types/dockerode": "^4.0.1", "@types/express": "^5.0.6", diff --git a/backend/src/index.ts b/backend/src/index.ts index 0ca8d8e7..35628d9c 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -29,6 +29,7 @@ import { LicenseService } from './services/LicenseService'; import { WebhookService } from './services/WebhookService'; import { SSOService } from './services/SSOService'; import { SchedulerService } from './services/SchedulerService'; +import { RegistryService } from './services/RegistryService'; import { CronExpressionParser } from 'cron-parser'; import { isValidStackName, isValidRemoteUrl } from './utils/validation'; import YAML from 'yaml'; @@ -691,6 +692,9 @@ const AUDIT_ROUTE_SUMMARIES: Record = { 'PUT /scheduled-tasks': 'Updated scheduled task', 'DELETE /scheduled-tasks': 'Deleted scheduled task', 'PATCH /scheduled-tasks': 'Toggled scheduled task', + 'POST /registries': 'Created registry credential', + 'PUT /registries': 'Updated registry credential', + 'DELETE /registries': 'Deleted registry credential', }; function getAuditSummary(method: string, apiPath: string): string { @@ -3664,6 +3668,126 @@ app.get('/api/scheduled-tasks/:id/runs', (req: Request, res: Response): void => } }); +// --- Private Registry Routes (Team Pro, admin-only, local-only) --- + +const VALID_REGISTRY_TYPES = ['dockerhub', 'ghcr', 'ecr', 'custom'] as const; + +app.get('/api/registries', (req: Request, res: Response): void => { + if (req.apiTokenScope) { res.status(403).json({ error: 'API tokens cannot manage registry credentials.', code: 'SCOPE_DENIED' }); return; } + if (!requireAdmin(req, res)) return; + if (!requireTeamPro(req, res)) return; + try { + res.json(RegistryService.getInstance().getAll()); + } catch (error) { + console.error('[Registries] List error:', error); + res.status(500).json({ error: 'Failed to fetch registries' }); + } +}); + +app.post('/api/registries', (req: Request, res: Response): void => { + if (req.apiTokenScope) { res.status(403).json({ error: 'API tokens cannot manage registry credentials.', code: 'SCOPE_DENIED' }); return; } + if (!requireAdmin(req, res)) return; + if (!requireTeamPro(req, res)) return; + try { + const { name, url, type, username, secret, aws_region } = req.body; + + if (!name || typeof name !== 'string' || name.length > 100) { + res.status(400).json({ error: 'Name is required (max 100 characters).' }); return; + } + if (!url || typeof url !== 'string' || url.length > 500) { + res.status(400).json({ error: 'URL is required (max 500 characters).' }); return; + } + if (!type || !VALID_REGISTRY_TYPES.includes(type)) { + res.status(400).json({ error: `Type must be one of: ${VALID_REGISTRY_TYPES.join(', ')}` }); return; + } + if (!username || typeof username !== 'string') { + res.status(400).json({ error: 'Username is required.' }); return; + } + if (!secret || typeof secret !== 'string') { + res.status(400).json({ error: 'Secret/token is required.' }); return; + } + if (type === 'ecr' && (!aws_region || typeof aws_region !== 'string')) { + res.status(400).json({ error: 'AWS region is required for ECR registries.' }); return; + } + + const id = RegistryService.getInstance().create({ name, url, type, username, secret, aws_region: aws_region ?? null }); + res.status(201).json({ id }); + } catch (error) { + console.error('[Registries] Create error:', error); + res.status(500).json({ error: 'Failed to create registry' }); + } +}); + +app.put('/api/registries/:id', (req: Request, res: Response): void => { + if (req.apiTokenScope) { res.status(403).json({ error: 'API tokens cannot manage registry credentials.', code: 'SCOPE_DENIED' }); return; } + if (!requireAdmin(req, res)) return; + if (!requireTeamPro(req, res)) return; + try { + const id = parseInt(req.params.id as string, 10); + if (isNaN(id)) { res.status(400).json({ error: 'Invalid registry ID' }); return; } + + const existing = RegistryService.getInstance().getById(id); + if (!existing) { res.status(404).json({ error: 'Registry not found' }); return; } + + const { name, url, type, username, secret, aws_region } = req.body; + + if (name !== undefined && (typeof name !== 'string' || name.length > 100)) { + res.status(400).json({ error: 'Name must be a string (max 100 characters).' }); return; + } + if (url !== undefined && (typeof url !== 'string' || url.length > 500)) { + res.status(400).json({ error: 'URL must be a string (max 500 characters).' }); return; + } + if (type !== undefined && !VALID_REGISTRY_TYPES.includes(type)) { + res.status(400).json({ error: `Type must be one of: ${VALID_REGISTRY_TYPES.join(', ')}` }); return; + } + const effectiveType = type ?? existing.type; + if (effectiveType === 'ecr' && aws_region !== undefined && (typeof aws_region !== 'string' || !aws_region)) { + res.status(400).json({ error: 'AWS region is required for ECR registries.' }); return; + } + + RegistryService.getInstance().update(id, { name, url, type, username, secret, aws_region }); + res.json({ success: true }); + } catch (error) { + console.error('[Registries] Update error:', error); + res.status(500).json({ error: 'Failed to update registry' }); + } +}); + +app.delete('/api/registries/:id', (req: Request, res: Response): void => { + if (req.apiTokenScope) { res.status(403).json({ error: 'API tokens cannot manage registry credentials.', code: 'SCOPE_DENIED' }); return; } + if (!requireAdmin(req, res)) return; + if (!requireTeamPro(req, res)) return; + try { + const id = parseInt(req.params.id as string, 10); + if (isNaN(id)) { res.status(400).json({ error: 'Invalid registry ID' }); return; } + + const existing = RegistryService.getInstance().getById(id); + if (!existing) { res.status(404).json({ error: 'Registry not found' }); return; } + + RegistryService.getInstance().delete(id); + res.json({ success: true }); + } catch (error) { + console.error('[Registries] Delete error:', error); + res.status(500).json({ error: 'Failed to delete registry' }); + } +}); + +app.post('/api/registries/:id/test', async (req: Request, res: Response): Promise => { + if (req.apiTokenScope) { res.status(403).json({ error: 'API tokens cannot manage registry credentials.', code: 'SCOPE_DENIED' }); return; } + if (!requireAdmin(req, res)) return; + if (!requireTeamPro(req, res)) return; + try { + const id = parseInt(req.params.id as string, 10); + if (isNaN(id)) { res.status(400).json({ error: 'Invalid registry ID' }); return; } + + const result = await RegistryService.getInstance().testConnection(id); + res.json(result); + } catch (error) { + console.error('[Registries] Test error:', error); + res.status(500).json({ error: 'Failed to test registry connection' }); + } +}); + // --- System Maintenance Routes (The System Janitor) --- app.get('/api/system/orphans', async (req: Request, res: Response) => { diff --git a/backend/src/services/ComposeService.ts b/backend/src/services/ComposeService.ts index ea3223d4..0f3bc2a8 100644 --- a/backend/src/services/ComposeService.ts +++ b/backend/src/services/ComposeService.ts @@ -1,10 +1,14 @@ import { spawn } from 'child_process'; +import fs from 'fs'; +import os from 'os'; import path from 'path'; import WebSocket from 'ws'; import DockerController from './DockerController'; +import { DatabaseService } from './DatabaseService'; import { FileSystemService } from './FileSystemService'; import { LogFormatter } from './LogFormatter'; import { NodeRegistry } from './NodeRegistry'; +import { RegistryService } from './RegistryService'; /** * ComposeService - local docker compose CLI execution. @@ -30,12 +34,13 @@ export class ComposeService { args: string[], cwd: string, ws?: WebSocket, - throwOnError = true + throwOnError = true, + env?: Record ): Promise { return new Promise((resolve, reject) => { const child = spawn(command, args, { cwd, - env: { + env: env ?? { ...process.env, PATH: process.env.PATH || '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' } @@ -73,6 +78,31 @@ export class ComposeService { }); } + private async withRegistryAuth(fn: (env: Record) => Promise): Promise { + const registries = DatabaseService.getInstance().getRegistries(); + if (registries.length === 0) { + return fn({ + ...process.env, + PATH: process.env.PATH || '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', + }); + } + + const dockerConfig = await RegistryService.getInstance().resolveDockerConfig(); + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sencho-docker-')); + const configPath = path.join(tmpDir, 'config.json'); + + try { + fs.writeFileSync(configPath, JSON.stringify(dockerConfig), { mode: 0o600 }); + return await fn({ + ...process.env, + DOCKER_CONFIG: tmpDir, + PATH: process.env.PATH || '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', + }); + } finally { + try { fs.unlinkSync(configPath); fs.rmdirSync(tmpDir); } catch { /* best-effort cleanup */ } + } + } + async runCommand(stackName: string, action: 'down' | 'start' | 'stop' | 'restart', ws?: WebSocket): Promise { const stackDir = path.join(this.baseDir, stackName); await this.execute('docker', ['compose', action], stackDir, ws); @@ -107,7 +137,9 @@ export class ComposeService { console.warn(`Failed to clean up legacy containers for ${stackName}:`, e); } - await this.execute('docker', ['compose', 'up', '-d', '--remove-orphans'], stackDir, ws); + await this.withRegistryAuth(async (env) => { + await this.execute('docker', ['compose', 'up', '-d', '--remove-orphans'], stackDir, ws, true, env); + }); // Post-Deploy Health Probe await new Promise(resolve => setTimeout(resolve, 3000)); @@ -138,7 +170,9 @@ export class ComposeService { try { const fsSvc = FileSystemService.getInstance(this.nodeId); await fsSvc.restoreStackFiles(stackName); - await this.execute('docker', ['compose', 'up', '-d', '--remove-orphans'], stackDir, ws); + await this.withRegistryAuth(async (env) => { + await this.execute('docker', ['compose', 'up', '-d', '--remove-orphans'], stackDir, ws, true, env); + }); sendOutput('=== Rolled back successfully ===\n'); } catch (rollbackError) { console.error(`Rollback failed for ${stackName}:`, rollbackError); @@ -289,11 +323,13 @@ export class ComposeService { console.warn(`Failed to clean up legacy containers for ${stackName}:`, e); } - sendOutput('=== Pulling latest images ===\n'); - await this.execute('docker', ['compose', 'pull'], stackDir, ws); + await this.withRegistryAuth(async (env) => { + sendOutput('=== Pulling latest images ===\n'); + await this.execute('docker', ['compose', 'pull'], stackDir, ws, true, env); - sendOutput('=== Recreating containers ===\n'); - await this.execute('docker', ['compose', 'up', '-d', '--remove-orphans'], stackDir, ws); + sendOutput('=== Recreating containers ===\n'); + await this.execute('docker', ['compose', 'up', '-d', '--remove-orphans'], stackDir, ws, true, env); + }); // Post-Update Health Probe await new Promise(resolve => setTimeout(resolve, 3000)); @@ -326,7 +362,9 @@ export class ComposeService { try { const fsSvc = FileSystemService.getInstance(this.nodeId); await fsSvc.restoreStackFiles(stackName); - await this.execute('docker', ['compose', 'up', '-d', '--remove-orphans'], stackDir, ws); + await this.withRegistryAuth(async (env) => { + await this.execute('docker', ['compose', 'up', '-d', '--remove-orphans'], stackDir, ws, true, env); + }); sendOutput('=== Rolled back successfully ===\n'); } catch (rollbackError) { console.error(`Rollback failed for ${stackName}:`, rollbackError); diff --git a/backend/src/services/DatabaseService.ts b/backend/src/services/DatabaseService.ts index 0d858681..036fcbf9 100644 --- a/backend/src/services/DatabaseService.ts +++ b/backend/src/services/DatabaseService.ts @@ -167,6 +167,20 @@ export interface ScheduledTaskRun { triggered_by: 'scheduler' | 'manual'; } +export type RegistryType = 'dockerhub' | 'ghcr' | 'ecr' | 'custom'; + +export interface Registry { + id: number; + name: string; + url: string; + type: RegistryType; + username: string; + secret: string; + aws_region: string | null; + created_at: number; + updated_at: number; +} + export class DatabaseService { private static instance: DatabaseService; private db: Database.Database; @@ -186,6 +200,7 @@ export class DatabaseService { this.migrateAdminToUsersTable(); this.migrateEncryptNodeTokens(); this.migrateSSOColumns(); + this.migrateRegistries(); } public static getInstance(): DatabaseService { @@ -498,6 +513,22 @@ export class DatabaseService { `); } + private migrateRegistries(): void { + this.db.exec(` + CREATE TABLE IF NOT EXISTS registries ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + url TEXT NOT NULL, + type TEXT NOT NULL DEFAULT 'custom', + username TEXT NOT NULL DEFAULT '', + secret TEXT NOT NULL DEFAULT '', + aws_region TEXT DEFAULT NULL, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL + ); + `); + } + // --- Agents --- public getAgents(): Agent[] { @@ -1083,6 +1114,45 @@ export class DatabaseService { this.db.prepare('UPDATE api_tokens SET last_used_at = ? WHERE id = ?').run(Date.now(), id); } + // --- Registries --- + + public getRegistries(): Registry[] { + return this.db.prepare('SELECT * FROM registries ORDER BY name ASC').all() as Registry[]; + } + + public getRegistry(id: number): Registry | undefined { + return this.db.prepare('SELECT * FROM registries WHERE id = ?').get(id) as Registry | undefined; + } + + public addRegistry(reg: Omit): number { + const result = this.db.prepare( + 'INSERT INTO registries (name, url, type, username, secret, aws_region, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)' + ).run(reg.name, reg.url, reg.type, reg.username, reg.secret, reg.aws_region, reg.created_at, reg.updated_at); + return result.lastInsertRowid as number; + } + + public updateRegistry(id: number, updates: Partial>): void { + const fields: string[] = []; + const values: unknown[] = []; + + if (updates.name !== undefined) { fields.push('name = ?'); values.push(updates.name); } + if (updates.url !== undefined) { fields.push('url = ?'); values.push(updates.url); } + if (updates.type !== undefined) { fields.push('type = ?'); values.push(updates.type); } + if (updates.username !== undefined) { fields.push('username = ?'); values.push(updates.username); } + if (updates.secret !== undefined) { fields.push('secret = ?'); values.push(updates.secret); } + if (updates.aws_region !== undefined) { fields.push('aws_region = ?'); values.push(updates.aws_region); } + if (updates.updated_at !== undefined) { fields.push('updated_at = ?'); values.push(updates.updated_at); } + + if (fields.length === 0) return; + + values.push(id); + this.db.prepare(`UPDATE registries SET ${fields.join(', ')} WHERE id = ?`).run(...values); + } + + public deleteRegistry(id: number): void { + this.db.prepare('DELETE FROM registries WHERE id = ?').run(id); + } + // --- Scheduled Tasks --- public getScheduledTasks(): ScheduledTask[] { diff --git a/backend/src/services/ImageUpdateService.ts b/backend/src/services/ImageUpdateService.ts index 8c663f74..da93b7b1 100644 --- a/backend/src/services/ImageUpdateService.ts +++ b/backend/src/services/ImageUpdateService.ts @@ -2,6 +2,7 @@ import https from 'https'; import http from 'http'; import DockerController from './DockerController'; import { DatabaseService } from './DatabaseService'; +import { RegistryService } from './RegistryService'; // ─── Image ref parsing ──────────────────────────────────────────────────────── @@ -73,15 +74,24 @@ function httpGet(url: string, headers: Record = {}, timeoutMs = // ─── Registry auth ──────────────────────────────────────────────────────────── -async function getAuthToken(registry: string, repo: string): Promise { +async function getAuthToken( + registry: string, + repo: string, + credentials?: { username: string; password: string } | null +): Promise { try { + const basicHeaders: Record = {}; + if (credentials) { + basicHeaders['Authorization'] = `Basic ${Buffer.from(`${credentials.username}:${credentials.password}`).toString('base64')}`; + } + let tokenUrl: string; if (registry === 'registry-1.docker.io') { tokenUrl = `https://auth.docker.io/token?service=registry.docker.io&scope=repository:${repo}:pull`; } else { // Ping /v2/ to get the WWW-Authenticate challenge - const ping = await httpGet(`https://${registry}/v2/`); + const ping = await httpGet(`https://${registry}/v2/`, basicHeaders); const wwwAuth = ping.headers['www-authenticate'] as string | undefined; if (!wwwAuth) return null; @@ -96,7 +106,7 @@ async function getAuthToken(registry: string, repo: string): Promise { +async function getRemoteDigest( + registry: string, + repo: string, + tag: string, + credentials?: { username: string; password: string } | null +): Promise { try { - const token = await getAuthToken(registry, repo); + const token = await getAuthToken(registry, repo, credentials); const headers: Record = { Accept: MANIFEST_ACCEPT }; if (token) headers['Authorization'] = `Bearer ${token}`; @@ -259,6 +274,9 @@ export class ImageUpdateService { const parsed = parseImageRef(imageRef); if (!parsed) return false; + // Look up stored credentials for this registry + const credentials = await RegistryService.getInstance().getAuthForRegistry(parsed.registry); + // Get local digest from RepoDigests let localDigest: string | null = null; try { @@ -281,7 +299,7 @@ export class ImageUpdateService { if (!localDigest) return false; // Locally built or never pulled with a digest - const remoteDigest = await getRemoteDigest(parsed.registry, parsed.repo, parsed.tag); + const remoteDigest = await getRemoteDigest(parsed.registry, parsed.repo, parsed.tag, credentials); if (!remoteDigest) return false; // Registry unreachable - no false positives const hasUpdate = localDigest !== remoteDigest; diff --git a/backend/src/services/RegistryService.ts b/backend/src/services/RegistryService.ts new file mode 100644 index 00000000..d8445c1e --- /dev/null +++ b/backend/src/services/RegistryService.ts @@ -0,0 +1,263 @@ +import https from 'https'; +import http from 'http'; +import { CryptoService } from './CryptoService'; +import { DatabaseService, type Registry, type RegistryType } from './DatabaseService'; + +// ─── Types ─────────────────────────────────────────────────────────────────── + +export interface RegistryCreateInput { + name: string; + url: string; + type: RegistryType; + username: string; + secret: string; + aws_region?: string | null; +} + +export interface RegistryUpdateInput { + name?: string; + url?: string; + type?: RegistryType; + username?: string; + secret?: string; + aws_region?: string | null; +} + +interface DockerConfigJson { + auths: Record; +} + +interface HttpResult { + statusCode: number; + headers: Record; + body: string; +} + +// ─── HTTP helper ───────────────────────────────────────────────────────────── + +function httpGet(url: string, headers: Record = {}, timeoutMs = 10000): Promise { + return new Promise((resolve, reject) => { + const lib = url.startsWith('https:') ? https : http; + const req = lib.get(url, { headers }, (res) => { + let body = ''; + res.on('data', (chunk: Buffer) => { body += chunk.toString(); }); + res.on('end', () => resolve({ + statusCode: res.statusCode ?? 0, + headers: res.headers as Record, + body, + })); + }); + req.on('error', reject); + req.setTimeout(timeoutMs, () => req.destroy(new Error('Request timed out'))); + }); +} + +// ─── Service ───────────────────────────────────────────────────────────────── + +export class RegistryService { + private static instance: RegistryService; + private crypto: CryptoService; + + private constructor() { + this.crypto = CryptoService.getInstance(); + } + + public static getInstance(): RegistryService { + if (!RegistryService.instance) { + RegistryService.instance = new RegistryService(); + } + return RegistryService.instance; + } + + // ─── CRUD ──────────────────────────────────────────────────────────────── + + public getAll(): (Omit & { has_secret: boolean })[] { + const db = DatabaseService.getInstance(); + return db.getRegistries().map(r => { + const { secret, ...rest } = r; + return { ...rest, has_secret: !!secret }; + }); + } + + public getById(id: number): (Omit & { has_secret: boolean }) | undefined { + const db = DatabaseService.getInstance(); + const r = db.getRegistry(id); + if (!r) return undefined; + const { secret, ...rest } = r; + return { ...rest, has_secret: !!secret }; + } + + public create(input: RegistryCreateInput): number { + const db = DatabaseService.getInstance(); + const now = Date.now(); + return db.addRegistry({ + name: input.name, + url: input.url, + type: input.type, + username: input.username, + secret: this.crypto.encrypt(input.secret), + aws_region: input.aws_region ?? null, + created_at: now, + updated_at: now, + }); + } + + public update(id: number, input: RegistryUpdateInput): void { + const db = DatabaseService.getInstance(); + const existing = db.getRegistry(id); + if (!existing) throw new Error('Registry not found'); + + const updates: Partial> = { + updated_at: Date.now(), + }; + + if (input.name !== undefined) updates.name = input.name; + if (input.url !== undefined) updates.url = input.url; + if (input.type !== undefined) updates.type = input.type; + if (input.username !== undefined) updates.username = input.username; + if (input.secret !== undefined && input.secret !== '') { + updates.secret = this.crypto.encrypt(input.secret); + } + if (input.aws_region !== undefined) updates.aws_region = input.aws_region; + + db.updateRegistry(id, updates); + } + + public delete(id: number): void { + DatabaseService.getInstance().deleteRegistry(id); + } + + // ─── Test connectivity ─────────────────────────────────────────────────── + + public async testConnection(id: number): Promise<{ success: boolean; error?: string }> { + const db = DatabaseService.getInstance(); + const reg = db.getRegistry(id); + if (!reg) return { success: false, error: 'Registry not found' }; + + try { + const username = reg.username; + const password = this.crypto.decrypt(reg.secret); + + if (reg.type === 'ecr') { + await this.getEcrToken(username, password, reg.aws_region!); + return { success: true }; + } + + // Standard registry: attempt /v2/ ping with Basic auth + const registryUrl = this.normalizeRegistryUrl(reg.url); + const basicAuth = Buffer.from(`${username}:${password}`).toString('base64'); + const res = await httpGet(`${registryUrl}/v2/`, { Authorization: `Basic ${basicAuth}` }); + + if (res.statusCode === 200 || res.statusCode === 401) { + // 401 with valid challenge means registry is reachable + // Try token-based auth if we got 401 + if (res.statusCode === 401) { + const wwwAuth = res.headers['www-authenticate'] as string | undefined; + if (!wwwAuth) return { success: false, error: 'Registry returned 401 without auth challenge' }; + + const realmMatch = wwwAuth.match(/realm="([^"]+)"/); + if (!realmMatch) return { success: false, error: 'Could not parse auth challenge' }; + + const serviceMatch = wwwAuth.match(/service="([^"]+)"/); + const params = new URLSearchParams(); + if (serviceMatch) params.set('service', serviceMatch[1]); + const tokenUrl = `${realmMatch[1]}?${params.toString()}`; + + const tokenRes = await httpGet(tokenUrl, { Authorization: `Basic ${basicAuth}` }); + if (tokenRes.statusCode !== 200) { + return { success: false, error: `Authentication failed (${tokenRes.statusCode})` }; + } + } + return { success: true }; + } + + return { success: false, error: `Registry returned HTTP ${res.statusCode}` }; + } catch (e) { + return { success: false, error: (e as Error).message }; + } + } + + // ─── Docker config resolution (for ComposeService) ─────────────────────── + + public async resolveDockerConfig(): Promise { + const db = DatabaseService.getInstance(); + const registries = db.getRegistries(); + const auths: Record = {}; + + for (const reg of registries) { + try { + const decryptedSecret = this.crypto.decrypt(reg.secret); + let username = reg.username; + let password = decryptedSecret; + + if (reg.type === 'ecr') { + const ecrCreds = await this.getEcrToken(reg.username, decryptedSecret, reg.aws_region!); + username = ecrCreds.username; + password = ecrCreds.password; + } + + const auth = Buffer.from(`${username}:${password}`).toString('base64'); + auths[reg.url] = { auth }; + } catch (e) { + console.error(`[RegistryService] Failed to resolve credentials for ${reg.name}:`, e); + } + } + + return { auths }; + } + + // ─── Registry auth for ImageUpdateService ──────────────────────────────── + + public async getAuthForRegistry(registryHost: string): Promise<{ username: string; password: string } | null> { + const db = DatabaseService.getInstance(); + const registries = db.getRegistries(); + + // Match by URL containing the registry host + const match = registries.find(r => { + const normalizedUrl = r.url.replace(/^https?:\/\//, '').replace(/\/$/, ''); + return normalizedUrl === registryHost || normalizedUrl.includes(registryHost) || registryHost.includes(normalizedUrl); + }); + + if (!match) return null; + + try { + const decryptedSecret = this.crypto.decrypt(match.secret); + + if (match.type === 'ecr') { + return await this.getEcrToken(match.username, decryptedSecret, match.aws_region!); + } + + return { username: match.username, password: decryptedSecret }; + } catch (e) { + console.error(`[RegistryService] Failed to resolve auth for ${registryHost}:`, e); + return null; + } + } + + // ─── ECR token fetch ───────────────────────────────────────────────────── + + private async getEcrToken(accessKeyId: string, secretAccessKey: string, region: string): Promise<{ username: string; password: string }> { + const { ECRClient, GetAuthorizationTokenCommand } = await import('@aws-sdk/client-ecr'); + const client = new ECRClient({ + region, + credentials: { accessKeyId, secretAccessKey }, + }); + const response = await client.send(new GetAuthorizationTokenCommand({})); + const authData = response.authorizationData?.[0]; + if (!authData?.authorizationToken) throw new Error('ECR returned no authorization token'); + + const decoded = Buffer.from(authData.authorizationToken, 'base64').toString(); + const [username, ...passwordParts] = decoded.split(':'); + return { username, password: passwordParts.join(':') }; + } + + // ─── Helpers ───────────────────────────────────────────────────────────── + + private normalizeRegistryUrl(url: string): string { + // Ensure URL has a protocol + if (!url.startsWith('http://') && !url.startsWith('https://')) { + url = `https://${url}`; + } + return url.replace(/\/$/, ''); + } +} diff --git a/docs/docs.json b/docs/docs.json index 6dc99053..9ef96bfb 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -98,6 +98,7 @@ "features/fleet-backups", "features/audit-log", "features/api-tokens", + "features/private-registries", "features/scheduled-operations", "features/sso", "features/licensing" diff --git a/docs/features/overview.mdx b/docs/features/overview.mdx index 73cbbaf8..995985d7 100644 --- a/docs/features/overview.mdx +++ b/docs/features/overview.mdx @@ -67,6 +67,10 @@ Pro users get automatic backup and rollback on every deployment. Before applying Create point-in-time snapshots of every compose file and environment file across all nodes. Snapshots are stored centrally and can be browsed by node and stack. Restore individual stacks from any snapshot with optional one-click redeploy - even to remote nodes. [Learn more →](/features/fleet-backups) +## Private registries + +Store credentials for private Docker registries - Docker Hub organizations, GHCR, AWS ECR, and self-hosted registries. Sencho injects them automatically during deploy and pull operations. ECR short-lived tokens are refreshed on every operation. Team Pro only. [Learn more →](/features/private-registries) + ## Audit log Track every mutating action across your Sencho instance with a searchable audit trail. See who deployed, stopped, deleted, or changed settings - with timestamps, user attribution, and node context. Team Pro only. [Learn more →](/features/audit-log) diff --git a/docs/features/private-registries.mdx b/docs/features/private-registries.mdx new file mode 100644 index 00000000..23585737 --- /dev/null +++ b/docs/features/private-registries.mdx @@ -0,0 +1,114 @@ +--- +title: Private Registries +description: Store credentials for private Docker registries so Sencho can automatically authenticate during deploy, pull, and image update checks. +--- + + + Private Registry Management requires a Sencho **Team Pro** license. Personal Pro and Community Edition do not include this feature. + + +Sencho can store credentials for your private Docker registries and inject them automatically whenever it runs `docker compose pull` or `docker compose up`. This means your stacks can reference private images without needing to manually `docker login` on the host. + +## Supported registry types + +| Type | Description | Credentials | +|------|-------------|-------------| +| **Docker Hub** | Private Docker Hub organizations and repositories | Username + access token | +| **GHCR** | GitHub Container Registry (`ghcr.io`) | GitHub username + personal access token (PAT) | +| **AWS ECR** | Amazon Elastic Container Registry | AWS Access Key ID + Secret Access Key | +| **Custom** | Any self-hosted Docker V2 registry | Username + password or token | + +## Adding a registry + +1. Open **Settings Hub** and navigate to the **Registries** tab (visible to Team Pro admins only). +2. Click **Add Registry**. +3. Select the registry type. The URL field auto-fills with the standard endpoint for Docker Hub and GHCR. +4. Enter a descriptive name, the registry URL, and your credentials. +5. For **ECR** registries, also provide the AWS region (e.g., `us-east-1`). +6. Click **Add**. + + + Private Registries management view in Settings Hub showing a configured Docker Hub registry + + + + Add Registry form with type selector, credentials, and URL fields + + +## Testing connectivity + +After adding a registry, click the checkmark icon on the registry card to test the connection. Sencho will attempt to authenticate against the registry's `/v2/` endpoint and report success or failure. + +For ECR registries, the test verifies that the AWS credentials can successfully obtain an authorization token. + +## How credentials are applied + +### Deploy and pull operations + +When you deploy or update a stack, Sencho: + +1. Resolves credentials for all configured registries. +2. For ECR registries, fetches a fresh authorization token from AWS (ECR tokens are short-lived, lasting 12 hours). +3. Writes a temporary Docker config file with all registry auth entries. +4. Sets the `DOCKER_CONFIG` environment variable so `docker compose` uses the temporary config. +5. Runs the compose operation (pull and/or up). +6. Cleans up the temporary config file immediately after. + +This approach ensures credentials are never persisted on disk beyond the duration of the operation. + +### Image update checks + +Sencho's background image update checker also uses stored registry credentials. When checking for newer image versions, it passes your credentials to the registry's authentication endpoint so it can compare local and remote digests for private images. + +## ECR setup + +AWS ECR uses short-lived authentication tokens (valid for 12 hours) derived from your IAM credentials. Sencho handles this automatically: + +1. Store your **AWS Access Key ID** and **Secret Access Key** as the username and secret. +2. Specify the **AWS Region** where your ECR registry lives. +3. On every deploy or pull, Sencho calls the AWS `GetAuthorizationToken` API to obtain a fresh token. + + + Use an IAM user or role with only the `ecr:GetAuthorizationToken` and `ecr:BatchGetImage` permissions. Avoid using root account credentials. + + +### IAM policy example + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ecr:GetAuthorizationToken", + "ecr:BatchGetImage", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchCheckLayerAvailability" + ], + "Resource": "*" + } + ] +} +``` + +## Registry URL reference + +| Registry | URL to use | +|----------|-----------| +| Docker Hub | `https://index.docker.io/v1/` | +| GHCR | `ghcr.io` | +| AWS ECR | `{account_id}.dkr.ecr.{region}.amazonaws.com` | +| Self-hosted | Your registry hostname, e.g. `registry.example.com` | + +## Security + +- **Encrypted storage** - Registry secrets are encrypted at rest using AES-256-GCM, the same encryption used for remote node tokens and SSO secrets. +- **No persistent Docker login** - Credentials are written to a temporary file for the duration of each operation and immediately deleted afterward. +- **Secrets never exposed** - The API never returns decrypted secrets. The UI shows only whether a secret is stored. +- **Audit trail** - Registry create, update, and delete operations are recorded in the [Audit Log](/features/audit-log). +- **Admin-only access** - Only admin users with a Team Pro license can manage registry credentials. + +## Multi-node behavior + +Registry credentials are stored per Sencho instance. When managing remote nodes, each node runs its own Sencho instance with its own registry credentials. Configure private registries on each node that needs access to private images. diff --git a/docs/images/private-registries/registries-add-form.png b/docs/images/private-registries/registries-add-form.png new file mode 100644 index 0000000000000000000000000000000000000000..2dc214c995513a257882ac50278ad8b5f2a4db05 GIT binary patch literal 57435 zcmce;WmHyS*9IslARW@uCDPqpQUVIn-7VeHU4lrb0*cazfYRNiv~;6_wA5_g_xsk& zk6H6$)~xyGdVCJ&ocrGQ-q*gaYd_IX)D*BV$T05Qxr3#oD64tr4pP{iJBTr8i15jd z24d-*JGgh0WTl>ZXYXBEz9j6qZpIP#{rmU*dBc#VCe%n=>6dqCaOpdd!l>KuvC%VI z(UI@r&$H1ex_E^&9U9z}Zyq@}^VAIdu5PN?`#BZ6kej*l!LF=v_uBnbHXffL=2*h4 zGYU_@Y5sYYnOr;>pXOV|G+b#knlRk9wpe10T=9T^OKqVdUcW_sk9w67*P_Pgm0op5 z6Kq~zUHD(^&G0Bk=}^nX5N?j-)rZ|)=)ftQoRgl5Htcu$+whz3_qPhs6hhoLn(rQW zEoKQgi~IaVUa&(yursOR`BU|uE3SJEH%F)>0^{)IUUer@-TYo?qQ4G$OsG!CI_ZD^ zKhHwD)_U4`M=qUFH8V9a*}O9bG`JL^6&YEEPltGjO*g~V+_SO zjN4m9y-5=F`D-RB zRdN>lVEs=p(%p|YEMo_&J%6UFIxrr>Kc>Wr{&TC9^~=8l96xT3wpwnk?NhgA>+C=2 z&~2Ii`|LR9w9t6`?|8S~k=^L4p!3hK*+Oo5ZI2H(hByqqNS>!Ks#Z(Ko-PL6q+;DG z-b!G_S#ZB^Nt7Dpj8o+m4_Gx`40D6M=PiEPGDh`sN} z=T(Z<%0+Uq_ml7+NCZ;wefVk(w`kv)Z*coHTj#dcdoa%R6giklJ(oh<-@ES>gI0-J zoivH4MorQqU2axEw8E#pyPxWx6ng#nDaV&UDZ;N)ZXgqZ$)Z)P0&9M8`X}*0fGxTa zN}lx!{LR}VP;H@PJ#f3j2V}_*_^`z5eJMToG^qFziC8f$)+4#r!`W5RsGH&%sio0V zS<_c#xT%~0=YQAZ?~8l=p0~~mh@f9+aI`-Qh3s4NVmp{&vBOb1J*tL&kh-us~L*6ivg#GdEx;o7XC}E!Hf&@^paI!?Goo} z1SO8J2ssi#;(X(uznAIM+fGEZkn=liO}-~sG@Dn;6+NG>GXJQEYQet#T&lfxt$s1E zD4cP9&bh@yxoU6jg{t4nivBb<``aDDLuVNq4Kp0Vu9rqth{n(HS~)gaJSAifS&7S) z2)bVGhfYFcN(fV0ILAeO_W&rgZXR{H>CP$E4GYy5s5A7@gH% zde~Xn)Au2&$`7>A+If}J9#xt5Bv1&wh#_Qou`wtFi!!xK6Lft^r;sq1&b8V@736?| zKY=gH#^ye|mnMB7akhk{XD|K&t;Tv}*hsI=?)B{vki8Q%tQ;M|z31ZTSJ|^09oV$M zv?Dqqd-m^{(*vv+o|u|1K8G8yf$vl@;5He4Cwm#<0lpB~KPrsXpHkl7R1lsLjz!~( zxvn5<_^q5l?hCxW6e_qh`sN#OG^PN-e`QX7ztMgBZ*shK;S}l*t`YwC+VBKk*8PKSS>V;bD3jTj&-&`w^lBxa9kRvz{626Q)WrB= zZKW$#-Ib>D^pmFd(bgE8Dz~?XuNai4&3O~$_29_LcD^jFVvx>Bn*P<7!gT6y)^}D^ zah5wq{gX%7r1d2Pa8Y;_6VDv4!?ep}sRpmj+5U3W*oS9j<2LTgail!BK_A=e1(UZY zN-CZdw3p-s>b+IO)0(WapGvBwp$2-rOyGG?!i(WY|HV^6K)-BnOKiwtrK3M7Mf6aUhA`OxKSU@h)1f53uPC0kCxN?~Ao~LKv z{z+A!PhLgfhz)KRbAXowIi?0hKOzc-MQ;*WPb)Ta%|`K1 zD&C|-g~SJZ15wH~FHiUw7QeW5km_MS5baeo|BWE|_Ggn9hen~S_ms!Zq|l4C-r*dP zmdn4&ExA`EYZE2vP1m${5XfBNpSbuvr&bl%VL z+a7;M&TE4!;^{C#6ps`v9kA^ue6`sSufIY-Uf&62Q>2^;^_SnDZ;=@EuoeR^K27P7WZ+)BGnj4(Y9Z?NG&^Wyz`r`R>%<6Jnl`*{;Uw4n;$phq|N2TV zoD^?{bF4%q!|WV;BrQ+O?<7&;I^Rdz7lTR`nEKy*>ny!?xy(8Reg4v4a~6>e+w9yZFSm!+JIt7& z2w)y=oH89y#@=VM!*|;lBo*^@XBr7XxhH+ zLg3-VFa49y-HJQu)(uIkyx;MIgAIxQ+_bwF+I{~}V>Lv^Ym;=dD~wZ%k^w9EqW8=m z<-tfAlhUiO=%KetDIppRJ<$YAfGVyxO?NzoBT!fcF)$HjH#_z$aw0Ok0-#!>S%%Qc z>wMC){AnD{vm+Xru*02*uq-0(dxS=S#}M;GW+#Ka88XYquKer8o9ok?TI%XI{NAcZ zR3EEVv-nNri&eAk*VI5zefbP0!qX}JY$WJf#-B>$ll@fr?oO3`eARBZOW7E?;F815qp9b5#{sc$#e7khnPiveCx z6-a*BH1Rvma*F&N6!klCz*cSY{MCH@&wls&5^phWg;mfUSG}K5NcTL4WrnfT)D9y) z>lDB;^4b6J-^}q6e0McsYqWCDNn#y$4KeiPA#Z_Bx_o9cZQ$jZc*C2?n+-_mXf&`8 z2Pehqd4an>*#7;7e8n|t^u+~Xky$-gbgQQ2`rkdI7f@W`C^NZ*V-TJEnyWYO!FG6x zXqreRVf5L7bh|_?N4ka<8IZ-$jy$oiDyCq1PPf}T2AN3gE}x zXpY4Fj}tHKe4zHH6N?4>OFb-*jmBq0O1W=#`c%~xGC!%f|ChrMP7|xH9e=r_>|LA7 zvxC}B(dC|qpyTg4HosF@v>IQ_%-IyoSa8>5Y%nxGO8*|MQQ7nSdaEeW<_7Q*^UNnn zEfLihb3D02o}Y#}F3+X7-71+22*$BiTNF-monSAeR})0+Z1Yo z2KDY@Q@H#zRv%BDCLD1SZ%roX{X6j;&+ElThxk$Ndufn%rpwN zZa<5>tc_jERqc<~g}aV#{!D)fjD72pR`m>?HInfGzXQpuHy@Z(Gs9Zir=5Sk_CQ zwV{>Qm8YaFxzDB-W+0;`lu`9-jI~#*C;Fpti#P)(`E48U`r)(j5_M-I8Gi45lNT$5 z<6}$I%@@C$vgy;qdn+IZn>w$$;uA=QX-q-qadA9lOlNV6Ykx?8<`(nY_OsBCbB{tZ zaL1R98I_v*?5I)LYc@&3g7(O4J-ctPs5HkiY!!fM#zukrP`#1z#^u`?_cyhXhuJO8 zEm!^9eNuEsEM?*^=ysV;rt;!CGSc;i0W0k_~DjsaKviqFmKC8(*?G)@^l< zz-WyYlg+GhA=~k{2A|Rv#>gqpHAzIFD77M*-o#tkafsdzAz6W!qw{-Uqw zj}XF>X$mO-0?8%@l2zH_0kdE4jsMc`zK81StEj{E_>J^k>B}CxqPNrxH5Pr+{SflF z6;Qzr$cVAXYLjU0^z1zQjp3*2^SyF1kN?_2GBD#bE&`Dedw?@yrf)O~aide(B_Gi+ zD01nYBer0q!{rD;rXRI`=(S7?&0Rce^|41!{+A0lLfN>M<{9ICLB*OWbcY9bpjo0* z7tgU1ucxq^&0KF#L6lz>y_t>@_pkkMw$K}P!++5DDbbk32gk5Q=H);SnHO|DBYGgi z{8CWl-t#~H;j$OM>K6w)b8OL*EeAz2rHF?8dMG%#gU;895Bmk&nSNRCwDSf)(uE5f zeCRk9aH;y@mO8$5yqBw~rrqRO)U2yQ-!3UdJKt&uiqnbNFMMMWJfxKNh%`Doc;F3( zSMH9?`z$P1PrMD+t{9>r?yNtAHnKbjSmni`{97LGY*>tFOX?ol)XNy}dV2m^5MxmY zCPmORBY7v^|6H|4?uP!G=X+=U(rCWOj0POx<+2ne^=c-TG<2lJ7Z|$IQD{hq>W#i3 z{FwJKNsD{(>u2hnsj3mAwr80}Q#iBaBt{W@*|4x;!cAu=@9~&U1aV8|(`DfBcyczU z|4v4*grYQ=K>QM$ky|D;-LzeHu2N&rA%g_l9K#%!A7SX?>U6PB$*U32qUf8ei&KJ@ zLgRPO>55brw~l*!jf~sBi)nr-pjg0>xWwgo9gWgKA4x?M6;jo?i^Dy9%)-@(?~70y z>k}Jdc`6tgSYl*m~=ozZ#3MU;YiiW!+(^^ynD&5=y32VbAur@SI!h`9KYFd zruy9>@~dqF#U8RF!XkH-a&^0j7ryK~C<+)^0)J_JqK8HF?Ph-GN9U%@Xn2cw?wR2K zc@}3@#J$~PAiIH0DNLB<26e%0bJ*h~(b#*z_IY%-b1{{{SMMq%`Y3g0#S|O;q_V@X zZ#_?fLu&U(QF1u41{oX-G;dt7CUibLWdY1bAwF-MKq>aJA(9B8UjY@}f>&hpP*;XV zRuJ`A{%!b65gxln7CL#n5M;Nl`&&&(0cSb%+;zn|B?{8mY8l*JH|$Z@qw{kz72oUZ zb&5x~x}dY>{NaBxV;!Xz(>|83RhaUY+29U-GV_Mj6@U|R;ycmOsjsU-&q#jF*V+=u z5V1evS5P4j@#rjep(SZSv(rG)5Q!e+9I_)<4pT@Xq?m_hK=(wQ{E=ZJ?^5EIAPWz zw!e8FXK*tFJLrX^y_+~7ODG_(c0k`fcA}rM&NA3uXcA}&A5sFc2AkaKm%Bf}cA0G5 zU}UMi_gyo#8=izu4I!b2z7ux)!xu9@FFW8gZ!5Ezpzpso&2^*W)qb?4)5&$=cJ=R= z_tnW87OjYxa7zhEKog$*34@5g@;*rw8D#_sn0k^ZzZl z2E8UC7Z)-!>M!V)-iJ@)nGx=$N{ya3^t*)SJ|L>V8ho^kf_RaDf|Nsshl?rKz7>|< zFd3~)@O6W=k=&}h-g%KxTJ_vJNml9WHTI|A_dEdz{T}nsrA6Z4DE`{8^(|RP(?*o9 zMWB=)>Eg|d_5i*_l#jyj8FZ3;O6qoIyB@ng-!~11iXGb(_1|`b=`v*CPsC{0y zE#si8?-=xu;sx{w^i%tUX-CQyA*~~vDsXY2+kE+{czC{eEQ6Jer$eR-@#>H!q6GLui6tw+F_6p7-{CJRAJCCUaCP?vB667pm_Eu z;HVN}Eyp?(xy1aP0R7rpBaL>e3C1;`8Iu(_c*HFe)yA|^BDObx{-*7MK6Ie(KN{V| zVOVXcRyNcc{r9vj_dxi<>!ie3oN{a!w51{3H0%XV`<%Q`VdDO#fok zC(LCy71sX>I3v-Ew#TI^Vq=VC@QAd$R;2AlR-?a%{9GleA8Rvr4C0I6yHwY!47Eg! z)@XMImGq?R(Jxu<3v0aU8X}{0Ds$@Nbe;?&bP zj0542Kw-a=kLLxHjCH_@RXX&fHWXX7xaRZ@w1?lb9!zhuNS$GOQKOzG?(eZZ&M?p% z`HF?_GjomRM=kCpd;FdPjaIhxNx z65{fC44JGR2C^LlxVr%7Ws76n;9-Zl^UgD->&mdwqzwqhq*ufGbXzShRNME*zAz8m zxz3+F+MZxZ%T0ZTJ6Uq265Hg_?%@y$wNg{kmU~UM&UK0Ca@PKHm7PPv@3~~{YsWn4Lv2W z`#spQ$Xa?p>88u$ka8opSKwljqkVd{QOS2FN zs6Hnh^D(wz)I&;Mn^9m(U76wb&MW3=S0;Yne2Jw8}j2XU%myt6JJa*|Ht2Z zjr1YIHfB?c59}w3vHH`?BMDce6s;!3%PC`rL{3bEV@2|@HPQOHUzrVV!n22PHCSJz$ zx7Kai2|ilJ-)=_>Fl25Ntm%NHTb=1K9!x zR&VaHJ&ub>TB;)`G9@@-cVfo#7bXl5?9QOlmgU@jf~WcAo0(P^cEyy_oBxP4vWl)} z`=-D<{$D;Z4UE(Z-eSulegT?D_QJKE=ws*qsy(u3<3$QMhp4cO zm1Q%_KkVgFVIQ)LoAGH$qR9n*11<6BmSQKrYyeJpC|B$P*m86NMp=(^szJiG0f4O6 zP?>BeijRNIy*W7LtCm~sNvwGDGZvSI-pE&-Bcl78`}V8A&l*ur=45?W`t$7+n@T*63%8VL`!NuOa;J_)Be!w~Jndq0z#K=k zF;H}nMXums5oU-0nC|`fqcqHxK+kEL_W$KuA?}I`3#_A>@yym&V(zL$rQ+8W&TE z-Iz8^X?roIK;`l0=M}{F&>Ie8ASwszXGcTyw|NI5xOeiWAC?{FRzAtXBuOOT7 zDDAU`&NVE{bLKvL&8ow$SGxs0Xtzht_ee}j#E!vH45U5Kk^mWATbSTfQcaUM(iRehw2Dd-VygnQ+Vd~&+xX3?TEsr(1`R6?A`C#^WF4S;(E}?4 zOW&I7>X?`0JRsgLOE)t&7)2dG6RCWvgYK1@|BjvU* zk!a$7IT7l(EC|Wri^ELy$Jj^3Nf3z*lv>tqg7Xd^OUC3p90nk)qUcr`SJJAipUeR- zP_x%I;stCkC3^w-0dHNHO@YpHuJg0ciz3Vz7&SX zsY|llOf3?``E#~|<@rVxI!QvpYo>`B$;KCLHTShm$tQ|c$r&CKTJafdP_W!oFxFpY z@9YJsm7rpgxl9W3R2Y4;f82YD;AdQ#9*AtID1}6GBrPn((85i<7Fo$=*gd7Z!0e$p^0rqLL%Lss7;;h3m)$ z1KcucHM!*Ucvku(*eWT~(x42G6^}QMAlFMgH8~Z6_SWuS)EvH_KtDkE2-p6B^YgQTxE0~5qQ&xbEQCumpoO}5Kd zIfFVX%Y5<v7;%tR6h6#=wu4o#F|5HvN21<1qQd_Uw~#3OY6EiV|eL z=HnmwHpJt}U)36G$nkvk?td_En*Mf8AyUy4l@qGT@w60oJnZ{eF(kKDmnVpgcRkdq zN`+$>E>%~zBX#pB2!%>8n%=DT+P1Rv*vc>SlJtc~EqnWj35a@$csuepy_v`&$a?rS z_Xc~79tm-P*@G-%$|jXrq<>dVHnhq?Ny>?vN^&SAj|lI1*v0n!FJT!DE|PsC+dT;@ zYcyj*=Sn4Y6z*#HY9yN3_eSd+pN7p_uPh&96yn-})Q^2H(9qY2!>ycgb0-RVznM&4 zTm06tzBQ(BHyoC}2Z}(vl;v~GL>irv*pGRipngc*(7^Kx{k4Lr@WbuH-fZ1K#rqK4 zWmI2>EB+*9s3jZuGEG zopQI6`a(^ejepi_D`_r>97t(jLv=YTXHx|7Rkix`8nk?Q?+K}93@MB)NEux2yV_Wd z)3TV-A$GGU3p$4A5R-L^lEIcHr;r7Ix*tJtzh_85@Y zFV7D@-Q-;Ev3HX0X2wRj4?m-4Wb-m`6cDYT#&BJL;6T!w*|q%b>w$b?#~qzL2i=&p zF-`Nu@vg$Jr^_CRY=(`KrE}05L=!2=>L-%&pk6)hR;VEwDD+ zxXG*pt61&FM_tl2lJ8Fp9a2*e7EuWBI-+0AAj$8X%Sd`=GSz0N@-!k_MjH=A@c8>0 zhJT{N>k1{Yw6PAel9xKClZTun5gTeGm0m5Al;~8>6v`Fq#ZNwvP-b=)|2L+IXivo_ zo~)&}6?I(S4~VxznPLc6J8Le*Jg@g?^=zaYYwx2(Cwz;}>uk~MgMg{&=O;pYX+!ormv;PWGy{UMsoe!o`l8nFPKWhA9_E^)VdoI1 z9lHUGiEH1P8{U|eTYROkNZydFiRl&h0VfltDkZ1MOUqrWgb=rz2)Dm(gwH+MC5L$# zc^FTA)#Y3pO9lSA+rsWPm1TzH$45HdQT%=nL3`8gxMj|D?#GK_Agbvj^4X;;;ttQCy5JRCXKSdO#y@3q8}N`bh!erkn19M?TY)lxDlyo<H9)8W`R6@aQ(D+rlDo;P{q|-PUiCC@F>Z zug)Wo(u{zsWT-b_*6L`2^?^u#+oDGT;RMmF)z8+IrQ@Mk9;?Ap+fXHW`-3MrJ{pqy zvkszan$z;do=^L$TD#qjl32HxPVkM{YL=PZf6r%-phQ#=y^QT8vXf2ML+C!<%11Lk z2#`c*c=`=LtgMd;v92d9>Iw9@mDn;SP#vS%GE8`B&7rU}>itSfNv8AMn|_-`Ok3Tm zP~R4@e0NMna!59q&9Bpg^)72da1TpKX?zwMXLW?^;WRfH9#XXFoTH%|Plm)fm&3fp&+CWe=)IKi0C_%=!%2t!65Wu3MNU#IDyX_@^Y zdh{IE+8ywBTxS%|%5b_HZ?%9aIm|TJU{NjnS&V1I}nYGDBP)`O#49 zmOwK+ZJS1EWZdj8gpai>R~ZO=OojKJpn0-XdfP@xAzray_|`VSqj6LNQ-b0i@?wiA zq60#qgMC?aE}pw(n(r_|uG3W|caFh5P3Ix1JBpr zSS7mla}l)aV)n|deZD(w$Aohi&v6KSRVah^i=nLdCa+*`;$P%_&12t;yC-&kV_Sc7 z(a@6l)}^5B;`Y#Qba6U3ZJR4f{@_f=Y^CTB-3cq0ttP_xeUD$>Ad8I8Zh!2TKr(tR zes-5G{hrNCeIzy+Zz*8Iav$hKy1bQy2xIHyzvqsaXEc_!Plhw^jTPO!2grn)G1jlM z5xTZ~3-g!fuNNymSv5_|%Rj2u@t}TkX0O7?A52nyNF?-rV2_H9uJ>e|MilTV!NU(v z3J}SAtybo$ng+#E1KXxY&Q!k;WKD=}5q$41rSRl-o)1iTMk;OhcZzbyKe;kgr)|9+bSRX`Z;slgku?vv`gF}J7});Hc_0`vQ2GlI>~ z1X*g!JaA7xxHo1BEn99G5>~0ji3Kps@sH)Zs@Z9|sfm*}A21s?i>^;teUSGdcy+?s zGKX>te2ec}oe&{k&f)|bZj{Q`a$L;+Gj?9R`M#F9$br}d;cwGRN@Pk&-&Jcn0gb_U zy|XdTKW-C{14q&cwooGup?o>bea4y!zGDW(mJ^%JFQ1`b>Cv3e+YZTAJo_-jp#{HN zhnEj-Cqjo60Pi?W(DAJ%OTwU9^bI|dt@i%m@jYVPWzq&85z#TGVpTn`{<9G0Q*b4Q z;-x10wS}N02-D*c;PJ^U^V&ka-6FyyzfYojakS0zVolLt4Ft#ZnBI=hXXb0(j_I3f zZb&7ORis|=0X82@O3qs_E=W-#*l85|6?m~e)Pm>1fi8UMra)`PId#=jg^N(=z73z- zqvo4yKlRnm`SF%nE?>MHLVQ{hjrFZ()LR;{*q!dwyvb+HDge(&i zXOk1y3%^`AU^DU4zDA0f8nB)C>f<(5H?5+*CVRD(qCQw$fbIHRcWk#|Il{Pvg6N%6 zO2iS+_?nYowVLSwAYyKwf%GMKpef=E_pkQ$Z$suI! z+bni2G4)-s-akpFo3{t2Cf*#`y4doTWbJk+;c8Gbymgn$_r&>&KAY!ORT>mH)nMo2#Y&95}q4@ zsMhA&e&9@UJR}qiFB;%71Fz*C=~qvEt|wF(cQ7@_GJw{5wv5SdZ zYIHys7Pa(0##yvH8m|BPg8d^vPq{U|?vwt9FaqnS_5aP60<(<51Zd@AQo-a1!sv4v z)Nrlv!+|AA5zot|P@IpFuYef=TA&eJZc^+B&vT>^ubWcKU!xLNXj&x+hvc z^l$HmLX?pR3e|k2Y3Q-A3$TVjEaVD%^y4q50)jjJy+AHzC*Ca2RDw&}z7onQ#$EfY zOdIJgw#B&{xiVT%+s6AR1=0YLsle3&><=8v$WBQ3R{g2AP}9KT1H`Z|7}Ee1dv1-= zJZc1|Hwd2Z;LzM^DDA9Dujt_~Lc;k4qIHs)1y31|30Ufi9>0DFyr*8Z1$s0%q|~wn z4{ohPP78K{t(g;b(67@!0HYg7GeDwP%3+kmfcHR)5X;5!!&Ku^yueS%_7y$2z!_R7 zqoC!?Aq!TUaY&=87#{0`8E()FPwra80^6qw>?v!OCpes$s(_3BXcPE^4GLOqXKg&? zi<7+>r~)8Wjj+tDEpud@LvaO@mks0`Vs-;g< z5URKTy{_pIR4LQ`WTq@6T#U6vKd}c^qc2X$2qg`$2;HXZxO_cmuZ5++| z>;qvCINw(4YzyktciWTBA zBxjVMNItasCFZ+{kI%~P4}c$o9^hcSdnV9t)nWU05T?N53Vvy7#<<<{!x0Jd6Ka+u z##?URj2AFUTt6H~QGHT{tN-N!pww8Cg}PQ-^gU00HcA1D20mJ$5L8R8S{sewMu@p; z;WEw>I_z5#dqbxB*3nBQ#Dw0-^N{Y3-e-sC{I5Yb;+YW+@J%%<86w5;vxC*54CWNV zPK1XAz@el{8bTxZNb_wkxb#ZPz%~B;!?U7&c#g>m<2;AAeoibQq1s@vef3Gw@%d*# zs}$&2RvQJTDUgv1k)I_4LByZu{`4O08AOtN{TO|!ut=mOe@fl79FtnMq?_POZ)ie+ zQY|MiQUU*tAc!$Z4eA?#o{(?WR8>*| zCyF|{`I&cnrp>hUn)#B!K+K&0?@(?DQQwx@r_gV`0D3S@?zH@b5WRYCwpbsw*MhB4 zBA(g6&8H%t+D?ZBu&3n@!eflJ!MP1yeY+Jp5}f(DjbOdTaoW;CUSsA+<_4J>?F3}l8JBi^| z+ag#-VdbBTqqv4@OFsK@E>BxoNlw71(kmJDK)U)X=pld$_gVi&MZk=OQ3NI_)FUf3 zAyfzXBjDo;MMx9jSporpdd9l2Oft4kZ0A?IfJ>< zxthSW^OoS$!d>Am#Mn8QV9V=@*h;(J+qb?%U`DUSmJTMJ0;01o^!{?Kg1(=l#I+Ux zg(qdasK3HHVotbUOtf~rVmB1dR7K6abx5wgS?5qB81DGfa+CXM|7(*w9K2Ghhh_?{ z#JxZa;`||KQMVC^b_ia(@S^skb%>7>CA1elCZ<)d^Kgd_E6w=^c zy}uW|tv9v5+%cpBjV)*5vx3MZ*HjrbbA-m=_@13fIUx%xHUT=^oOpjO1ntbmpqs$X zm}BnYJc&QwC(Jgi_LLrW<#qh-QtR+8dY(d6KZu?zl5ach0fnLdyjL2k2HVAbl#gad z7}xJCl^cO?9WyENw&A%5LmA{hB6u}hry@Rnr$pr~$*Y{0X&lCwA;hL|tOBKBy=yAE zkxxb8M>xHFH6DA80{W9}H$PA@YV_0R(jPBi9DVG)S=gfJUvzxZ*x)E=*Q*wcjz-LoF^vR^o1z*-Dc8w326+np zC9~AAH!`6N+ixpXV(^5oLpNn5skkf(3n9tV$B>ma3Zg5`fZInyEsmkRBJD~LPs2N? zp9XX2Y2+uX7j-YzAJ;Ea5o3L8f57R5R@rK6g+iq$Y=N=Kb}0Oz_4C^r)h33kqzvea@ zwM`Q7*xq`6?)A4&?~WlFuvfd$Cug32=)pQ#Lwn;@goF^le^ZnwZWdwwo6Yt2{Bg`& zMn`o2_0gjrt%NxN2~7XMTH@jY`k1swPXeXe?;se;GNI4@YE39W$P=yR1A@?WQmJMH z`?wOlLBgAex`>gZw!2jMBw#umg%%r>WZXL-^l`L}!kCI_V?GDqrV)jCRkxl^;3HXj zL|?7o#32AN_)zmLT97WgPWk-BpB4!a%ytSb!dR%-@Sc(opWCB4V>1`Xq$G4&yYRIq zgsx-mV6JW9cLyefbCE?KW|may=47CTbc;S%lTdj`@+m$=Ru15kOW3_8{%)a5io92ydh%)poa@r%@RAnik^O9q*SB8=?Z&J{hD zl%h3LvB+~btJY3xHPHtyg7{vV#J>pEuDu`~+@L*#@;6`j0b=s`q$g5J1ZFEo%-W)E zmkWffI=$Fa5q>iVhGIj3>@DQTTL4?Ea>7rA&X5wkJ87T|;+$)f^;n+y8WCegSbGL} zdfL-+gN%T6zLKdQ++OQn&HgG0_e|BXZ|>`Qnk>{Y*XRr8&(v5m;j|}+w>9|n{CEGX zIIW{J8dJCD!&PXp_UGB%=~rHh4Q6!sC_agbj@%W)adIJp_CDc7X?vC7!CA(fNV^_D zv~ZV|xRnIXF@@+%t|G`2z1V~gze!v%UNxf+fqMAnB{8;Cj3MGu1#1OHP z+=;*<@5WVh|5>fD4qIBJn@pI?#jUe7ku%q@_2pXxpInku>=GmQgBs^#N(!?Gr45qw zp|?Ck-nWMJNI`)c8ckgk!;psFuV$d$lc)g=Zdu$WIMio?S@I?%xE8v~_SEb<*=#VW z(@iQ(nx7;%pG^WvW#fx=45q}I7;8g-yeo0i*FVa15*anHod{?nctBl@zQyh!skHwF zxhm=v0ut)2O`q8|mDHNUUdY+$#S*I>S6PkBXmNL}B@V9QyE^alr}4e}H`vla(~Tai z*4&@EvN}oVY?jgx#1uO3FA_Ip{;0Oj-dusYFtir0ON9~LN0N-ePFpnF7n&d?KJJid zXC$)TxC|~r8hO)PHuFsaMt%+fLFdq6e)XTo`y@UKym}%)(CbA7+x^ZI^gSw_$~Mwh z#_6yK)k+pqN%^v`YGuvZ48uDH775s-*$VtQ3+--o3FZ3`8z=ITE_1nj)Cqd--ti=TH^4;juz z=qgY3XKc5u5KBk3;z#Fp9U_D$yYH;!}NlZ2_(UQ~~Xhf48 z`4q_FwArf6`eH6VHyus9=ij@{pd=k1TkeX|*k}1f9n8|6 zq;v;c$VQVSvC56}4sJ3gIp&=WA4XbB5>DB+a>J&GpuRgMe^(`I9cCc4eN3V0NB1`E z#trH`;iRIJpq34?)Aa7DsN#8<`LckpsTV_3>iO92!&3*uYGG^hv`4y?{8tSUBNf`o z*D)N6QpKF1?f5e1uD|jRT_z1w#iz0Ze`RFxYro9;6*CvCmM1QjB9kda{`H{-t6~o` zwdP=j8B+P>{QfS*ei?0xXA#ol*_0`X_7}Vsxzh0p`;+NMT1m=?=Ew8R+ z7;{NgW|EXyw#49|D~GCQ3%1p#Li$mc$9B`ozj=aqCEm^B$FenAGcu~k*5!jTzTcZWw*}#>jx)l&a z?n_)9r8Kl)Xng=rB~{R6m1W|6UhnJ6^R$2#mdB~%xJ9WjCM|lSU+1zcP4)!z?o`%r z<+zxsDC*}<(5K3dqJ8s|XM1D(P9YIrlF7x{iDxT;pMQNgvtb`d`0~YJ?lqdm?&=S5 z`VscFt?*5nIF%tRzZXEmn z_rKI-uLLuj=0FzG$z$Fxl#MpK9nYmb^9I5LS`aD#U`uUC_dh1dnmzq$l`jt51~HG7 z3VR}qeML+GV4jm zVb-8!j|MaTe1Zprb z$!QgloBHr< zpcY?_*#t-2SfhfkR7w#pBj#19M81|1p-Knmn+ zw+76Ebq+J!DTt4fY&2D9A5Xr$D*{vwcnfu4@VihUu^B2PjN+RYzsiN5FaQ#an8S!- z6L^d+7+RP6OP2~}oo2^5{=b`p87bK|>tH2&)zY8}_XpDw_f&W4pSM&1FF=e(EBF+Z z4!_ty7xt3@)(NWCUm$5<0Q_zd3D>A2R@g)lxS8t{*IpqnauMB--NKMT%bjR^`B zkY5g9PKNyYq_FfSK&{)cC4lsfv$Yv8UfvcEwgL03!k&BS)KVc(bpm1T88`-$E*Ob| zbzO<&XwDykXSSa#9qUb|V+qR!E4ByBPH5CZ9FvQ9KJw%^kHjQxf00Zpr)W)HqMD^$ zqDHBVOwftwUVnRh@#IaOVE^O{V2&OGzA=MYw3zPkBzkAfOH2>5Q<@`AEyyQ8{@#v+ z!;_F9#Lxg^G69bFgM*fvD`zleVGun84yYWcMv;-TJYkO**|(dpo*K1)V{Z+@&AjCg z8wv7`L71-15GC|j?Z&dBp)`#N{59WD!w?e9tWmf{<9i#lajg&x5}lNf)hEaYCh{(% z;-B)#+g7H~DPW(FIdbB(nYfiduUdiC1@!Ls?}$bF5C#Ju=^y3uEqbqF5fFmirbRU{ zjL|IB`OiXA&*H7IKK8yN5TbzFYWD5~0OW$-PZ;e)btAkG20jah{>5N%{}YTfmyD^M zr9PgyrS-UZP?_*Yl4ks=0t@@1)NB8-%j0Rx+M;&-#H({eR|@$dcRq#DlKC5>Wtcx&e;I|D@G+RKb^R zg8dg8_1gjAIt|gBSKz7scxUzBWeRK1?DPNJm$o%5m!#{MKF0C3I>7HzNYJM7{I~kG z6aGKQ8uAv(Q7*T}$3$?!oCeK79Ft z-L_v~8ogax+OKrhPs8+Kn$#xng4(n_j6pfgAEv~BrsKEh#br(UX!PwL*k5}RsZLpC zU|t+7JG&4su!Hj$qN&bCZyyT*XuS0yn}G?w1jcS}f9)k|KLOkb^(IlCzvpKjdHtF_ z25%7faca+_hGyW%LnSiFhw~2SHlFbc;FsfQKEgfVoE5VLU4b(+3^Kp}5Sm}u8Q|v@ zL9hVhCrq@N0Yd={w$d=z%rqv?4>#Wcwu83@orE3?us{%lIaJ!D_Z~aE5XKsRV-`kP&@g`WjM<0#c>N`RCgd9eW=piQOSxs^n;_FO2ZEIu>mw#P+N#tG%bMg5&Q@*FJqP~|EtDlNaq55db_J1z$Fkp|(+0%LxjOqu%iSAioqe|OB5|;8 z#*n<*2gcpHH^~%$U-3TZQn$&=`Rjo^2(if}%zBeMKqA7At-!W4zyUW!P02&O#R;+` z^5C^~xmW!$eCUE7SQHv+HD(GfFL=WGpT1?FJW&N4;R#p3uZ6(jr~o^`--IKW$i*V( z$86iwre<-!#Oug7lmf|rofIX~0COvsNPf=c$W<7n-JMC#IL>5?&3D!r_*vQ-8 z96`hI)+#oF4X zUt7BjAyO7pDY-E>d~M0IKI00yRQ=GzDiKp_V{zix0*whF$`X(+!tP zm|s$(GATV7h+_$ONxd@NNB!c%5XI9GDoOcVh7oOA(k?J0VsDU7oPHDqacnf~MHz4a zj|~dfBb&$6^Q;Gb@gzm@0he_rpozp1)sn*S;Cx6J6rFgn&<+_ zu;+?gsYVMPWa=BGKaS800fxAW^%ppg{=k#;y)NWjXFE8q{4u|hK%9X43i(>?pA#GI zYnTzoIL%`roqbJWX4z*}HtLNNI+5cuy_xid%Nw`esA}|qUvL_P`m8Qv3P4DadG(&@ ze9NPS8Y3n|`b+pd=0O4*>hYBajgC!R5?pQMQ4P$$c<0!iuyb!K93ol8+cbk-*`Cl6 zHxaS^@WQ*Z?##Lenw{apl_wxWm%;}=D9v&PHR$q>i}eIZI1P6KBZaik55X-wv*#(M zuzJ`PgMQ;Tkr0jZC)Z-}&jDfHB{B~%5T58%s1f&;PPtPSBR2IAJY|JLn|f-B8UV6# z4gNVh9VWE!dfn|@qx>%z6g;~-C|G!$bhKjIF*ynU&U5F`AN2w?SP48T;Yr^>;7-MbsG-1+23b3_maV!ru+nGxd-p{3AWE<3`9M5(#_q@Q#a# zt|+)r7z`V#8tK?4f_fpcFuy$|X4*)4%p(xNO@G3BaLl|BqjCt>y|B6xnD_zH zPMJOtDk8n~q}#oRWeXu`^d;f!$Ek|n?#iu>|TCoES~lE|9Tm9M3yL(v*`@N82TlNV-6w- z6=HGK=;y`D%+5h#QY;u3aR#2jR<)l8(~axt(=%Re9hK0+EQVM#E_S zzc2v&7ncCf;RIL!0Qg$TjF0Cx{~*r4=5aJ$I6?^#5@6=8IN_$tfhsJfn=XyD21q&w z-R!{(ar~>8ZKKx3o@a&_2XKX>2d!H3oba-nKcj+jxa8zNo+C}zRD~p>9oiVCO06EI zL9G?0-PsR6nkP-A{NWyPra=51cas2pB7F*dda`~TJL|}1fdDMH8n3aiHSid0Q)B;p z!Hvd{IGZ)1*d=iKIS~k<$%=jNc4@I3rq|t0I2Hs4%dCy4x9m(s)R8uMm>1!057)BJ ze1+*#6oN-|iXsS%v$D{uFYIbIT%c)%G?4hdzhP;NQQ%3ydVEDy$3o^9C~Ht>LXgKi zU~Kj2mmPF_u)^!{3+1auW+uYTzL8ZVWylI0_2NZ z_Xog5LLG9w-IaAPdhEow0PrSkF&4#UeTj0yck$CMlMme(=g7(-92a3AfAh4E{0qF~fW; zEI~y&)^GRl^Z{-D#^{ke8E)GqYMjUEW%RnN{-wv3e{lhLZb9eo;e0~oRkQRuP6FOw;VPZSxvGHZf?{t3O5vO>_e58Q1{QzZyg%hPPGc@2 z{ZEYXZ)J;J5p~w>!p(co{%whYs{)VSAoGaUT5u z@;II}x?yT>B&g~m(dfd)rX9))<@?9ut%}&9cLA7mjDvp9wqlVJ0X2W=jBEpMHH0EW zy!E*d%-rOI7aHK8K)vKvKpNsa?0fMRtOS+`@hI4U%!>XCeXjBbd?{>DIg|hb zffLD9r~9lQzNiblaAxl;o1*vK&6~PulR*sN1Ei9glz;kP)dUVzkLI|tU0<(XWeCY| z33+g=SOtDRhG$hSg&*if#V)yZhwM{fdjNxpb8K7`;)n%8gGYeTQeqE`u+htumU4GE z+C4G}jhE_Jv3)8lP$V+(k-ttR&R>C=+n(rmzP-5*j=@_a!YXKq>OAuZJDeB@WitO^ zsH|{9D~ayA^_(K0FVHI348hOWem+2R17Q@|ORP5K){PYX@r<{ce=cb=ygzPQFSY%e zO;FR)#h7cV9iPckw;=$wsJAk<9^|*p#{B8(?@7g{MtGh{U>MMuRbfKu#6ur1I^}S^ zbZH`OfNXe5i^b@+@h!j}q>hD$>t-MJYeR9`mgs~wHdXkpRsGQ6muw91Yb8oLXcegI zayuE&P1>PJPfI#c>l=3%g^Z7-42mK4Ajvm$eH+=Z;2gbEHuma;j=SP|PGmzn^24v9 z8~aH=c6AF~cm=s{30|+6(C3>0V(+__ZaTg+=$KO;wHiugab1hGPmr?|La)aHoO{f5 zlOmQzI!7rM7Bwjy<8GEb3HtNBpJr3o4}?tBCkOuehz_HtE}5p@5*bW>t@M;#rifkh zaYO)`hGY7|D+`aa7(N1$eyYAi{^>k8DdxyF`m*@i)(+~5uH%8 zq6o4Whi%k@-C+6f<)pmkT#ul_z@y)P$8_Jk1V;M`YpXuA8BFL+eZ&b8gdQzXl<>ff zB%$g*{AgG}N)bKIr;#Yz?^a8S&91IVEypZ|lO zi*-vJ`DW!!g=fY+W6Y>7(Ap)r3RKQ-Gnx*LdCBa|FK83^+t^5yQ3CsUV`VyO_v3)^ zP-iG2drR#9-JK6ZBr68S7clOQNiF12Rfy*;bN&1ZXqc>OUn$L$?}ka4(nhx^Zs zYlHH}@=7-g9qP;(K4f*kq-ffkVX?Pm z+VrX`MRml6hvx`Bo6OFqm&u;y3&O+rmXXAU#x46liq7}dt|Q6_$ERk>-mIkv2+PPHJH~;ZhWT1MUEsmt1@e4ks6780+pXFsLWnL{^OlcgPA#!)fp-IJy@B72wC0vumLzb{FOFvHT93_2Gt$J;FA2gEn6Qg?>)= ze?w~HI1!uF8oi?*wGM=&*^dGbav%EW<3F-;K{#^HV_FBL?P{}6^y~K^L`4$Mf;tQ_ zej;*KBsBiSduL7|@;e~--$>;vxc5R z__I=fQCfPrZ=h-EV#88_7$W9Y@m=FwNOCQcw#GIDi}-(; zJpS)OVUhP5dgx@WFJsY73V=T{nf5XN}{c^hE*&L-9GU}=Efkr^e8+K)2)d|n5uKXm=& z34A?d?}ac`4BHZm_|bAmsKu;-x*0Wg+8vl5pNYz!C$KZS`u|}# z?*FXDJa(+tPk_SuZ~RCxwm~9-ewUo*Ft7;9c}K`!UX^E09@`D|%S3h<73CE0E9Ow2 z%;8aK0y2!Dbp%c8@Q4GTvm+j15P`o?#K(Ue!-~;L7F~ir4W6E%=?I8;^}e(D{T>8T z=O)oG`r8u0P^U)D?5MP?Jf++0giHj3>*PM*1T*D)u-U-%h`VvXn!#)KT9`7_vH}i!LUp!R^~6AgW!WcDAHWaq! z0mjkGV~E-w6ihooIH4lIIZ9;mV!a^+NMaD&TO!PiASD53wYPl~VS*k6JF;1$GQKr> zxwY6>9#;k+QR|%_0)MPKDz4(B04fDWV@G@|B)axskiXCoQ&~8Iy6)o{sQ42TBdA@E zFRtIPGM}?b5QT5~F6Kmh1rg#6X3VS{)ND;5T0*B+#oI$({%tw9b$l5L&}^X~(2gtu z$fYL|^v(x-*;Eg&V4%XQrmd}*i|3LDB(;DY2E-Y(lJ4&(UxM=kl-cTRfiv_k_zo2x zQI6V+h+43d1GU%$yl?BV7;qZjMQsa+T(0#RqoyEsk=%`e97Ho7#1S1rNm~Z&M!0yy zz5sDjE+_D7pIT#I$qWLA$%a!nh%mJMJc}?rV6d=2&OXyXw^%B{~5`%!6)>TemG$wCM-@VKd+VK0pdi z(VD!)f)=4CE0*ve!!&PRWWYUtL$FHt(l4kAaKrq>{|PeG1bQb!BN}V<_zFu@pw|ss zuwJatcesa}DwUoieqNJEYi+CjW+}|xjTd7lt9}S;l zQ~VAS$Ebx#RkO&jg@k!sfM2G^(>Xw5F|)D;KHFHdiBcejG1Bl?{xc3SjS3suCw}~g z$3AXNFHXbj1XNY7h>8r?B;xRU5xM>JnCJULK2sMvuhrvVT_8#`ao*D~3r9gEORufC#9K!s}^f6XcuUhFl$9LP!tp()V9K$yk>LQ~&hBH~A6)B;Fj z&wPLwp7`xYh&WNPDH%BB3RFTlD4(M#eu*CsF6$r=A=Hv13SfP+7-}D+$w9`Nu&`ag z9fBDLjPYVLD?YzmBplIe^i0ZsneLBSJCkU3i>oKSrc zj8O^0mLQ>BfG)w=cJM`S&V5a!Q8i6__@GsZqR5KDSs1jb)B||6 zLaCyyVLrB>c;HV&{y+LW3|Kb8vAvy-SCc+D#eoZHp`04;pP=#r2mJ#efGkab2%e`B`9mu=D zd7!v$sp{E}2q3o27yrbK5mGA{KsMK=& zHKQq+Kb(FMBz$}md4gZ#9LyIopU-2P` zi3oJT?+gj_zW_%33Lg~+|HSBZam(f3SET-TAIRG612j1!ohd`GXAChl-CRIGH5Mv~zQ=~#bz@cjeJ z(QZUKt?4AzP5W*96N}>VK%5t18}LLoP7_K9ZMkE!V_bx4p%NOP$Ah6y0F!?s3%f$i@h zO%qx-umaZU_21Yd1{!c<;2H+i>!?7dE~z+C>+)D&@vK6KK?6L7{dHA4odVJSwG2bL-tY9hK@-}3 z&zDs%&ej>Q zZcF4Jw^1$QI|DL+u|Su!MzTGt4#nXLA72IjOFTN2B_iQ^>TrS{F&QccL#Ixg`Zf(v z|Fy?2f|Y%F>*&>i{X9vFzQBPvW1{Xo-Yi3XH@pYp;s~6*)Yn)>hjw4wF&UHx!#7hBYJa{4X?@%?va9; zEG-8l>ruCK3fO-@P{{P$u9V%IWyOSD?9LSs+k!3cOVmLvAE0ZLEqax{7yCSu%8F-B znOU=+EVI5e{}P2rBD5?KuaG!#z`ixdaMt5(XJ4`YBtBZvFpN|;<*GTnZJmewO~N|X z1{ZvNfdYnkZ!h5dB?gvOXJ=6x@SY!!H7oGR<1oenuX6h#s|qc+DjunWr9i|}{WzD8 z|9Dm$IM*(CZIVtDBx9_%xPce5aN5zY07NQO@qXB0##e`rq~`MybonPYh^76T^3#}W zAPci+qG}7A0Ul%snd--K|3{YkfD6Zw6_jn?iF(8-p_;-tWT*F;i0Hp<-0l|HHUq8- zQyf1<3*@lfE>h;e;PVev3PE8B?z$3vqN8qkon<&OJnJ#1Z_8##7`M|znNF6oU zdGo{iFOGMPE2$l2TZ^PLr8_o#!xKAlAOAwLkXD?;fRLer?ZJHY?K7G5G)H+ZD<|J1 z1T_ZRHa<~}W(GGcxgn_?8wAya%T0Ig5^2Ev6M@bsHGATmXw=-B5(#H!!ul`20E0(= zb!Y6~%6|}dAe8t7 zk8dAHy?Fr!h}WGYbdjoUMV)I@KtM0k7afrMdzb~{JPz>Ch+_VPNp3odtI}0)Xmvok zTKuT3TIe``QP<}yXT$xa(zu$1joOD`k~ z%DAS=d6~NpR^STnd7t_r3 zuqRxi+Mg&cvf*xcm90eno+N{;u#KCc!8B`8zy# zl9J_`3BfqZT-n1umUoU8j#%k$Q??uidc%Rjk+M0GTw>>r2s z)E$Ou4NJ#%4bG5+XP`L`{9sSuv#pc7-gxNdL6dL&r8$!kiQ!NGU?MYmOhfkV(W+7_ z)}7ri>I2F|)NvT<#blvrQS zS52H#3|h7OqqghgQpDDN^nE*iZ4@I**bRQuA~EJ0L}%MY<=EN{ z8X7ZY4PQ`FYPzA5#`Gw}v{`+D|B`w0-k_W9&tXo^lxiW|Z&J&)>7ymw?&$;KB?TU_ zV^vq5rrMy?R=CC_+w6$>gcn8}R-9nuSCz{~0s1+YJtsWN;sy1)NmK`>_NdvQKJBzL zp1?DR9`)G}chwgBDC3B8)I3g9hO!?wAHrReD$Fd|AKK?NUHl7KG98IIUC z*Q;S(XAK1SE4MigqYp@98~LYzEld6TyBQHhSwS|Mo(+3-P91kmnS5Bxie3e7$rBH} zpA6dDt=m!kMXs?V^^h&C?u=NaDACLNZpUKk$zsoG{&0d>5grPDEz458Y2~17+7skz z+$$s__*I|Ga3#XVc~Gqn^z(&JeZt8Sq7c|^sCI|hv`%nmUsDh-U76UYmFO{>hLkT| zyI=~7@nv@nJ}Unvmv(kj8iRR0nT(6xs_Yof`-QV9nMSN^K~A5bMn-0SC)3hb(vVX9 zMBACW@1W=8`p#ejh=g;h?!&%;J1lK14uClVS8f@jAf*a;?e*d~vxRG8cs!Ib>SJ1z z^%(KYd`3({>~M^fu-GoKXa{At0r#}5bHpInloMLE&F4meu&`Ja;G zk@Alt@OX$kKhw8}5_zOBaP?#43soE5V8ZD#>JS>w{-h5NIL3v}@WTAOtdQa7?M*+G zP4WlCVeg?9V2C@P5F(-3mNEN+Y^Y|CSTxD2zMgV2zLC90Q?j zLIOw5R)xiN7Bm?NhB+5+JfK}~bq{*z_)l$V16PQtjFdu#YO^s4%jA}V4MUhH)eaSh zD3x@);RTw{*o3;rElX{OL(dFkYMktR07wIORY?E^cg8^gqZdi9f5UxnR5Xis?T=(;o(z7tT<42z}? zPbW1psH9-j${J^`&}*CtAD-oK_O!>oM8FATdfBkPA(oLXA+zjfWMqIrdQG_!k~riCR#ONZOMbK_z%%U5VbB~#fhQKel>@$7A(6L()q z6ZTT^9ue17i9++0mXOx|Qrk2@c-KK6>#%b`oz?*^8R+t6qG5D@aE66*_GMC$gJMZ2 z^G6N|lEP@M$t|NRBd_STh?k?7*`6GYw&iMBZWqj{kDs=thyP z=%jD5MxoW#EviK^#cyeOfATMYd_!PqA5V#_%3Dsc>))+x1t+juPoBj)>4ps~J9|IZXYi*jMb*??=?y7)@eO6!i zNE+5bMpOU)<;PEIMzvIha~zA1-8lzZ@09mXr0A(LiA@Eu0zCjDnZu@GFajBup8NxTX<=DRq~hR}zlL1mz?C9sR{HId zt^;N3(`s;)PJby}^^f0!-<$mj_1DDULBNt^eBu@K?<{*QYk+IuvH11rPHC}Hh1Zyx zIZ>SICC58cJTQYx_*VWocX#rBaSb>I(?N#WPjE^Efq(@KEX#R-Id#*Y-}BEf-&aBL z4A+BU=(0)283)@*sph|KY?N`ZmJe9@&_@&AR0Yq zLCFQLuJk!5jGEwpRK!RyxD&YMlILrUf$>kjDsFFrMuYmuf@zig>XqP8qZZnn!h+`r z<{NT9oS{(|L;!J%=LK%=I-2JKEL12|dN}g%B#Caln$nvm41m>UaOgB4?!X#~tEWr~ zU%+PE$<)||Rf`JRs?ronTmFoQs4*61rd=~7bF^k9w3gEA1Q8c4bvJND_N2@~YlV5m zYqxhd3$k%BtwF}P#~nhmYIf0^l5Yszo#ioR8pc(V8Z?Wk8^vgtVn?!TWgK_B6vNmK2|W`2oyTq@D&Nh~Wk1 zWyzAwM(Zwlqr(Ga9W~7}5T<+ZxD^!X1rQ2QSU*9pLEnyrVt|;AsJ(oP&^*M^krP8* zCyL^pLITW<10`3D=;yCbT%`7dclrI-FwNs=3~IT(U6^Eu!@+Ur1-=~MocJo7R|m4e z2Y*D??^#@xr+G(?8}C^El!!HkN?2OonN<>$$R{uYX}1!w*JsU{wSGoP?m^W0?92JZ z4e6zqbnyyj8*nZ+&FE|FswNg6_TsTyMjt@VHz&Cw{90jq`W(*a!g>U{Kz}0XK4c}+ zMlh+aXSk%=&7;Xk*kn{6FwH;N(}}U@usiKheQGu=Sj#WRT%j0IP^oIaY}0M3!AWv` zeX;MC*D|+>X${yVd#Xc|#2Djiy7e&(cMvH=H#XKpO&y^3&|`W*>&I(`n&9KSXm zZ=QWpHc2_$fH0gZA2<5lHZ8pte(i?6!aQu}+|q_c;B3YyOOUpCllefqqU(V87Js}h zjhg{){f}Pw51Vx&eGSvu4Jo_a)|knW5Lk=0$Iu?LTfh8W%TV-4)MIQqb7d-S?YD15 z6@yoH7BF7kgGR0S{r1}F0WJl|{vowWTk?0$?9*jG4+JuGnO(xRW*hnYlh~M(nEYsl zjFBmr%utbCZ5NlGzq3uPZ}((%ECQ`8Qr$jNhBCh5u$4yZK$ut&CuSecaB+!tOOeC- zl&Ed?K`ISdu?Mwg!DK-MGJD|7f0WJG4%?~BFilFl%TxEf2m^DXlphw*VzDUENoQ6~ z8P*ydHY|2mFu(Vu!%F2-rZLMC`wb&5Q99k4dh1RpfZMu9v7wTq+f(b5dNjr$|H)p%v!1Kp)yW%-;MB5tWg*w#EJ^ zYo}qeZzXR1$Ufad~V^ZL|GE>7;sZx323Bi?K$NJVmA zCwAGF7%)mniw4Zcncx^DpF1c>9T%8bG`O?wE;e?)$$buR6*L=xKFUA%g{7otL27z=d2wNX06qizN%jY<74Kef0 zzkW`eFvI3ff1!K(v!6-cC(+~t!?yD@5(1stom6G7l7qdhrsQ8odFME8=EOk3xnVXL zMrr&Vpyl1r1R>0X9tRY_Bu}YE15Rwl{pbH2FzDQNYhqGKB5nESHxH_8FyqO4j&+o> zP)xJuqoL`G9We)O5kAyqQa~*(0HbsZpYW*m>S$ErcLYJd{g+o?>^*_N zShRT$iFdd#o;o5O3=Xr0ksHN zH?jlSmOMlKskK*lVPj}etksEe>e*?yoy;3`@mIJVtVw6xlGEvtDJj7v#0i3*gNa3H zLQhZn{atw-zSJCR$7TL)@06M(cL4?V?(nF+B)o#c>DzTmOO4Jh`9BTzd`KLkYcW(< zz|1t|^gERwdAF2LNIa8beE0~r%Jskh!LnsUKgvJEZ0BKLPXMbf_1B63fEb!GFQtEy zuWXnGviYjttvX`IFVFc$8qTIOF*X-{ub)i&Yp9Qo{pF@l@C?-gzE7MmhP{u(ezoiu z7VlrBv+BRIJmFqSm>xM~ZX3H&HvOLJo7VWZ)}|yMNWl6~JHU5>LkGaDUtps=ay8ef z1G0F{AG0``_nysn`=kG#3U7Xsj?V&B?L}sw9It+_?jTu@n4Q4~Y)%w9Q-?8#+`{Ys zv+JJHJSzVN;70%?laD>*b?Rw$U5vqxn%+MBF++N+v4FUMP?`*J1Iab-6~9oo5bg^6 z4P}JpbdJS8JfWZg0uFTSbeXOWp|yf_1mkoz|7uaUO9$>yq3@6{NVS*dxc~W5v~BJP zlid~X#HP>;g3 zwOH*BVT}1stcG51JXoj`^+ADH`!|%HC6}4tJTYt_ER7pD?!$r^FfMZRP#2i80RSeQ zG@MIg9ym~;i2#0=BCZPK9ouO2I1$ZTI%UEVau4kArMYvMI7=zZXhlGTIV-VX}Ld zh#VP4enZpsg6N4bnG@)I4}KjPkD;CXiHFX+FMI&%0ieI~NoAEW4=7^}!he>1SW~-*aJ|qRPb_y>&AjEPOl$$(4wda7J<*8l3dvZ2plN!?gt)7k#)YF{0@b>C@Tzn*d{S+;4;inq@Z&vQp)Y(+ z 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 literal 0 HcmV?d00001 diff --git a/docs/images/private-registries/registries-overview.png b/docs/images/private-registries/registries-overview.png new file mode 100644 index 0000000000000000000000000000000000000000..e3ac4cecaca5ddebf84357673f0b603c44280aa9 GIT binary patch literal 47706 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 Q=vvD3fQ506;qkEl0A()6-~a#s literal 0 HcmV?d00001 diff --git a/docs/images/private-registries/registries-with-entry.png b/docs/images/private-registries/registries-with-entry.png new file mode 100644 index 0000000000000000000000000000000000000000..bc73510c4adb373421fcb42e8845304fdf7b39bb GIT binary patch literal 49021 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 wpgj9X^wFw9oyj86^p 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≪oa|bAKh)Gi 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 EH}5dYk^lez literal 0 HcmV?d00001 diff --git a/frontend/src/components/RegistriesSection.tsx b/frontend/src/components/RegistriesSection.tsx new file mode 100644 index 00000000..ccd16b86 --- /dev/null +++ b/frontend/src/components/RegistriesSection.tsx @@ -0,0 +1,384 @@ +import { useState, useEffect } from 'react'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Badge } from '@/components/ui/badge'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog'; +import { toast } from 'sonner'; +import { apiFetch } from '@/lib/api'; +import { TeamProGate } from './TeamProGate'; +import { TierBadge } from './TierBadge'; +import { Database, Plus, Trash2, Pencil, RefreshCw, CheckCircle, XCircle, Clock } from 'lucide-react'; + +type RegistryType = 'dockerhub' | 'ghcr' | 'ecr' | 'custom'; + +interface RegistryItem { + id: number; + name: string; + url: string; + type: RegistryType; + username: string; + has_secret: boolean; + aws_region: string | null; + created_at: number; + updated_at: number; +} + +const TYPE_LABELS: Record = { + dockerhub: 'Docker Hub', + ghcr: 'GitHub (GHCR)', + ecr: 'AWS ECR', + custom: 'Custom', +}; + +const TYPE_BADGE_VARIANT: Record = { + dockerhub: 'default', + ghcr: 'secondary', + ecr: 'secondary', + custom: 'outline', +}; + +const TYPE_URL_DEFAULTS: Record = { + dockerhub: 'https://index.docker.io/v1/', + ghcr: 'ghcr.io', + ecr: '', + custom: '', +}; + +const TYPE_USERNAME_HINT: Record = { + dockerhub: 'Docker Hub username', + ghcr: 'GitHub username', + ecr: 'AWS Access Key ID', + custom: 'Username', +}; + +const TYPE_SECRET_HINT: Record = { + dockerhub: 'Access token or password', + ghcr: 'Personal access token (PAT)', + ecr: 'AWS Secret Access Key', + custom: 'Password or token', +}; + +function formatDate(ts: number): string { + return new Date(ts).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' }); +} + +export function RegistriesSection() { + const [registries, setRegistries] = useState([]); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [showForm, setShowForm] = useState(false); + const [editingId, setEditingId] = useState(null); + const [testingId, setTestingId] = useState(null); + + const [formName, setFormName] = useState(''); + const [formUrl, setFormUrl] = useState(''); + const [formType, setFormType] = useState('dockerhub'); + const [formUsername, setFormUsername] = useState(''); + const [formSecret, setFormSecret] = useState(''); + const [formAwsRegion, setFormAwsRegion] = useState(''); + + const fetchRegistries = async () => { + try { + const res = await apiFetch('/registries', { localOnly: true }); + if (res.ok) { + setRegistries(await res.json()); + } + } catch { + toast.error('Failed to load registries.'); + } finally { + setLoading(false); + } + }; + + // eslint-disable-next-line react-hooks/set-state-in-effect + useEffect(() => { fetchRegistries(); }, []); + + const resetForm = () => { + setFormName(''); + setFormUrl(''); + setFormType('dockerhub'); + setFormUsername(''); + setFormSecret(''); + setFormAwsRegion(''); + setEditingId(null); + setShowForm(false); + }; + + const handleTypeChange = (type: RegistryType) => { + setFormType(type); + if (!editingId) { + setFormUrl(TYPE_URL_DEFAULTS[type]); + } + }; + + const startEdit = (reg: RegistryItem) => { + setEditingId(reg.id); + setFormName(reg.name); + setFormUrl(reg.url); + setFormType(reg.type); + setFormUsername(reg.username); + setFormSecret(''); + setFormAwsRegion(reg.aws_region ?? ''); + setShowForm(true); + }; + + const handleSave = async () => { + if (!formName.trim()) { toast.error('Name is required.'); return; } + if (!formUrl.trim()) { toast.error('URL is required.'); return; } + if (!formUsername.trim()) { toast.error('Username is required.'); return; } + if (!editingId && !formSecret.trim()) { toast.error('Secret/token is required.'); return; } + if (formType === 'ecr' && !formAwsRegion.trim()) { toast.error('AWS region is required for ECR.'); return; } + + setSaving(true); + try { + const body: Record = { + name: formName.trim(), + url: formUrl.trim(), + type: formType, + username: formUsername.trim(), + aws_region: formType === 'ecr' ? formAwsRegion.trim() : null, + }; + if (formSecret.trim()) body.secret = formSecret.trim(); + + const url = editingId ? `/registries/${editingId}` : '/registries'; + const method = editingId ? 'PUT' : 'POST'; + + const res = await apiFetch(url, { + method, + localOnly: true, + body: JSON.stringify(body), + }); + + if (res.ok) { + toast.success(editingId ? 'Registry updated.' : 'Registry added.'); + resetForm(); + fetchRegistries(); + } else { + const err = await res.json().catch(() => ({})); + toast.error(err?.error || err?.message || 'Failed to save registry.'); + } + } catch (e: unknown) { + toast.error((e as Error)?.message || 'Network error.'); + } finally { + setSaving(false); + } + }; + + const handleDelete = async (id: number) => { + try { + const res = await apiFetch(`/registries/${id}`, { method: 'DELETE', localOnly: true }); + if (res.ok) { + toast.success('Registry deleted.'); + fetchRegistries(); + } else { + const err = await res.json().catch(() => ({})); + toast.error(err?.error || err?.message || 'Failed to delete registry.'); + } + } catch { + toast.error('Network error.'); + } + }; + + const handleTest = async (id: number) => { + setTestingId(id); + try { + const res = await apiFetch(`/registries/${id}/test`, { method: 'POST', localOnly: true }); + if (res.ok) { + const data = await res.json(); + if (data.success) { + toast.success('Connection successful!'); + } else { + toast.error(data.error || 'Connection failed.'); + } + } else { + const err = await res.json().catch(() => ({})); + toast.error(err?.error || err?.message || 'Test failed.'); + } + } catch { + toast.error('Network error.'); + } finally { + setTestingId(null); + } + }; + + return ( + +
+
+
+

+ Private Registries +

+

+ Store credentials for private Docker registries. Sencho injects them automatically during deploy and pull operations. +

+
+ +
+ + {/* Create / Edit form */} + {showForm && ( +
+
+ + +
+
+ + setFormName(e.target.value)} + maxLength={100} + /> +
+
+ + setFormUrl(e.target.value)} + maxLength={500} + /> +
+
+
+ + setFormUsername(e.target.value)} + /> +
+
+ + setFormSecret(e.target.value)} + /> +
+
+ {formType === 'ecr' && ( +
+ + setFormAwsRegion(e.target.value)} + /> +
+ )} +
+ + +
+
+ )} + + {/* Loading state */} + {loading && ( +
+ + +
+ )} + + {/* Empty state */} + {!loading && registries.length === 0 && !showForm && ( +
+ +

No private registries configured.

+

Add one to pull images from Docker Hub orgs, GHCR, ECR, or self-hosted registries.

+
+ )} + + {/* Registry list */} + {!loading && registries.map(reg => ( +
+
+
+ + {reg.name} + + {TYPE_LABELS[reg.type]} + +
+
+ + + + + + + + + Delete registry? + + Deleting {reg.name} will remove its stored credentials. Stacks using images from this registry will fail to pull until credentials are re-added. + + + + Cancel + handleDelete(reg.id)} className="bg-destructive text-destructive-foreground hover:bg-destructive/90"> + Delete + + + + +
+
+
+ {reg.url} + {reg.username} + + {reg.has_secret ? ( + <> Secret stored + ) : ( + <> No secret + )} + + {reg.aws_region && Region: {reg.aws_region}} + + + {formatDate(reg.created_at)} + +
+
+ ))} +
+
+ ); +} diff --git a/frontend/src/components/SettingsModal.tsx b/frontend/src/components/SettingsModal.tsx index 9f3aa6c7..f74cff0c 100644 --- a/frontend/src/components/SettingsModal.tsx +++ b/frontend/src/components/SettingsModal.tsx @@ -29,6 +29,7 @@ import { TierBadge } from './TierBadge'; import { ProGate } from './ProGate'; import { SSOSection } from './SSOSection'; import { ApiTokensSection } from './ApiTokensSection'; +import { RegistriesSection } from './RegistriesSection'; interface Agent { type: 'discord' | 'slack' | 'webhook'; @@ -50,7 +51,7 @@ interface PatchableSettings { log_retention_days?: string; } -type SectionId = 'account' | 'license' | 'users' | 'sso' | 'api-tokens' | 'system' | 'notifications' | 'webhooks' | 'developer' | 'nodes' | 'appstore' | 'support' | 'about'; +type SectionId = 'account' | 'license' | 'users' | 'sso' | 'api-tokens' | 'registries' | 'system' | 'notifications' | 'webhooks' | 'developer' | 'nodes' | 'appstore' | 'support' | 'about'; interface WebhookItem { id: number; @@ -658,7 +659,7 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) { // When switching to a remote node, reset to a node-scoped section if on a global-only one useEffect(() => { - if (isRemote && (activeSection === 'account' || activeSection === 'license' || activeSection === 'users' || activeSection === 'sso' || activeSection === 'api-tokens' || activeSection === 'notifications' || activeSection === 'webhooks' || activeSection === 'nodes' || activeSection === 'appstore')) { + if (isRemote && (activeSection === 'account' || activeSection === 'license' || activeSection === 'users' || activeSection === 'sso' || activeSection === 'api-tokens' || activeSection === 'registries' || activeSection === 'notifications' || activeSection === 'webhooks' || activeSection === 'nodes' || activeSection === 'appstore')) { setActiveSection('system'); } }, [isRemote]); // eslint-disable-line react-hooks/exhaustive-deps @@ -1005,6 +1006,9 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) { {!isRemote && isAdmin && isPro && license?.variant === 'team' && ( } label="API Tokens" /> )} + {!isRemote && isAdmin && isPro && license?.variant === 'team' && ( + } label="Registries" /> + )} } @@ -1468,6 +1472,10 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) { )} + {activeSection === 'registries' && ( + + )} + {activeSection === 'developer' && (