@stacksjs/ts-cloud 0.7.20 → 0.7.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/cli.js +552 -551
- package/dist/{chunk-49nmy775.js → chunk-0tfwdnmf.js} +9 -6
- package/dist/{chunk-qgnyzfmx.js → chunk-5mejsd43.js} +1 -1
- package/dist/{chunk-296n8hvt.js → chunk-c3yy2xyq.js} +2 -2
- package/dist/{chunk-qdhn1s7j.js → chunk-js3vrvvn.js} +158 -5
- package/dist/{chunk-xgnxz5dz.js → chunk-kcxxwg3j.js} +89 -12
- package/dist/deploy/dashboard-site-settings.d.ts +55 -0
- package/dist/deploy/dashboard-site-settings.test.d.ts +1 -0
- package/dist/deploy/dashboard-throttle.d.ts +46 -0
- package/dist/deploy/dashboard-throttle.test.d.ts +1 -0
- package/dist/deploy/index.js +4 -4
- package/dist/deploy/management-dashboard.d.ts +45 -15
- package/dist/drivers/index.js +2 -2
- package/dist/drivers/shared/deploy-script.d.ts +7 -0
- package/dist/index.js +8 -4
- package/dist/ui/index.html +3 -3
- package/dist/ui/server/actions.html +3 -3
- package/dist/ui/server/backups.html +3 -3
- package/dist/ui/server/database.html +3 -3
- package/dist/ui/server/deployments.html +3 -3
- package/dist/ui/server/firewall.html +3 -3
- package/dist/ui/server/logs.html +3 -3
- package/dist/ui/server/services.html +3 -3
- package/dist/ui/server/sites.html +3 -3
- package/dist/ui/server/ssh-keys.html +3 -3
- package/dist/ui/server/team.html +3 -3
- package/dist/ui/server/terminal.html +3 -3
- package/dist/ui/server/workers.html +3 -3
- package/dist/ui/serverless/alarms.html +3 -3
- package/dist/ui/serverless/assets.html +3 -3
- package/dist/ui/serverless/data.html +3 -3
- package/dist/ui/serverless/deployments.html +3 -3
- package/dist/ui/serverless/functions.html +3 -3
- package/dist/ui/serverless/logs.html +3 -3
- package/dist/ui/serverless/queues.html +3 -3
- package/dist/ui/serverless/scheduler.html +3 -3
- package/dist/ui/serverless/secrets.html +3 -3
- package/dist/ui/serverless/traces.html +3 -3
- package/dist/ui/serverless.html +3 -3
- package/package.json +3 -3
|
@@ -21901,6 +21901,8 @@ function createDashboardSite(options) {
|
|
|
21901
21901
|
}
|
|
21902
21902
|
};
|
|
21903
21903
|
}
|
|
21904
|
+
var DASHBOARD_STATE_DIR = ".ts-cloud";
|
|
21905
|
+
var DASHBOARD_ENTRY = "./node_modules/@stacksjs/ts-cloud/dist/bin/cli.js";
|
|
21904
21906
|
function apexOf(domain) {
|
|
21905
21907
|
const parts = domain.split(".").filter(Boolean);
|
|
21906
21908
|
return parts.length <= 2 ? domain : parts.slice(-2).join(".");
|
|
@@ -21963,7 +21965,7 @@ function resolveManagementDashboardSites(config, environment, opts) {
|
|
|
21963
21965
|
return [];
|
|
21964
21966
|
const auth2 = opts.password ? { auth: { username: opts.username || "admin", password: opts.password, realm: opts.realm } } : {};
|
|
21965
21967
|
const build = opts.build === false || opts.build === undefined ? {} : { build: opts.build };
|
|
21966
|
-
if (opts.live) {
|
|
21968
|
+
if (opts.live !== false) {
|
|
21967
21969
|
const domain = resolveDashboardDomain(config, environment, opts.domain);
|
|
21968
21970
|
if (!domain)
|
|
21969
21971
|
return [];
|
|
@@ -21972,11 +21974,12 @@ function resolveManagementDashboardSites(config, environment, opts) {
|
|
|
21972
21974
|
root: opts.uiRoot,
|
|
21973
21975
|
deploy: "server",
|
|
21974
21976
|
domain,
|
|
21975
|
-
|
|
21977
|
+
preStart: ["bun install --production --no-save"],
|
|
21978
|
+
start: `bun ${DASHBOARD_ENTRY} dashboard:serve --box --host 127.0.0.1 --port ${port}`,
|
|
21976
21979
|
port,
|
|
21977
|
-
|
|
21978
|
-
|
|
21979
|
-
|
|
21980
|
+
sharedPaths: [DASHBOARD_STATE_DIR],
|
|
21981
|
+
healthCheck: { path: "/login" },
|
|
21982
|
+
ssl: { provider: "letsencrypt" }
|
|
21980
21983
|
};
|
|
21981
21984
|
return [{ name: managementDashboardSiteName(domain), site }];
|
|
21982
21985
|
}
|
|
@@ -46152,4 +46155,4 @@ class ResourceManagementManager {
|
|
|
46152
46155
|
}
|
|
46153
46156
|
var resourceManagementManager = new ResourceManagementManager;
|
|
46154
46157
|
|
|
46155
|
-
export { detectServiceRegion, signRequest, signRequestAsync, createPresignedUrl, createPresignedUrlAsync, makeAWSRequest, makeAWSRequestOnce, makeAWSRequestAsync, parseXMLResponse, parseJSONResponse, clearSigningKeyCache, getSigningKeyCacheSize, isNodeCryptoAvailable, isWebCryptoAvailable, QueuePresets, RealtimePresets, detectDeploymentTargets, resolveDeploymentMode, deploymentCoexistenceError, resolveCloudProvider, TemplateBuilder, Pseudo, generateResourceName, generateLogicalId, getTimestamp, sanitizeName, resolveProjectStackName, resolveSiteStackName, resolveSiteResourceName, resolveSiteBucketName, resolveStorageBucketName, resolveDeployBucketName, resolveServerlessAppStackName, resolveServerlessArtifactBucketName, resolveServerlessAssetBucketName, DependencyGraph, analyzeStackDiff, formatDiff, Storage, Registry, CDN, DNS, Security, Compute, Network, FileSystem, Email, exports_incoming_call, Phone, Queue, JobLoader, StacksIntegration, exports_send, SMS, AI, Database, Cache, Permissions, ApiGateway, Messaging, Workflow, Monitoring, Auth, Deployment, AssetHasher, Secrets, ParameterStore, Search, Redirects, Communication, Fn2, Arn, AWS_PSEUDO_PARAMETERS, CloudFormationBuilder, buildCloudFormationTemplate, createStaticSitePreset, createNodeJsServerPreset, createNodeJsServerlessPreset, createServerlessNodePreset, createFullStackAppPreset, createApiBackendPreset, createWordPressPreset, createJamstackPreset, createMicroservicesPreset, createRealtimeAppPreset, createDataPipelinePreset, createMLApiPreset, createTraditionalWebAppPreset, createLaravelPreset, createServerlessLaravelPreset, createDashboardSite, resolveDashboardDomain, resolveDashboardDomains, hasManagementDashboardSite, MANAGEMENT_DASHBOARD_SITE_PREFIX, managementDashboardSiteName, isManagementDashboardSiteName, resolveManagementDashboardSites, resolveManagementDashboardSite, extendPreset, composePresets, createPreset, mergeInfrastructure, withMonitoring, withSecurity, withDatabase, withCache, withCDN, withQueue, fromEnvironment, fromSharedCredentials, fromEC2Metadata, fromECSMetadata, fromWebIdentity, getCredentials, createCredentialProvider, resolveCredentials, resolveRegion, getAccountId, S3Error, createS3Client, CloudError, ConfigurationError, CredentialError, DeploymentError, ValidationError, AWSAPIError, ErrorCodes, getErrorDetails, createError, DebugLogger, validateConfiguration, detectMisconfigurations, validateCredentials, checkIAMPermissions, getRequiredPermissions, suggestIAMPolicy, DEFAULT_SERVICE_LIMITS, checkServiceQuotas, getQuotaUsageSummary, suggestQuotaIncrease, FileCache, TemplateCache, templateCache, hashFile, hashString, hashBuffer, hashDirectory, hashManifest, quickHash, findChangedFiles, HashCache, parallel, batch, parallelMap, parallelWithRetry, sequence, withTimeout, RateLimiter, chunk, processInChunks, diffTemplates, getDiffSummary, requiresReplacement, categorizeChanges, getDeploymentStrategy, getDiffStats, cloud_config_schema_default, validateAgainstSchema, defaultLocalConfig, getLocalConfig, isLocalDevelopment, getLocalEndpoint, getLocalCredentials, getLocalEnvVars, MockCloudFormation, MockS3, MockDynamoDB, createMockAWS, PreviewEnvironmentManager, previewManager, generatePreviewWorkflow, generateCleanupWorkflow, generateCostReportWorkflow, PreviewNotificationService, previewNotifications, formatTable, formatTree, formatProgressBar, formatBytes, formatDuration, formatList, formatKeyValue, ProgressBar, MultiStepProgress, Spinner, TaskList, suggestCommand, formatSuggestion, isLikelyTypo, getContextualHelp, searchCommands, autocomplete, suggestFlags, formatFlagSuggestions, getCommandUsage, validateCommand, REPL, REPLContext, REPLCommandBuilder, CommandHistory, formatHistory, formatHistoryStats, MultiRegionManager, multiRegionManager, CrossRegionReferenceManager, GlobalResourceManager, RegionPairManager, StackDependencyManager, crossRegionReferenceManager, globalResourceManager, regionPairManager, stackDependencyManager, AWS_REGIONS, getRegion, getAllRegions, getRegionsByLocation, getRegionsByCompliance, getRegionsByPricingTier, isValidRegion, getClosestRegion, suggestRegions, suggestRegionPairs, formatRegion, formatRegionList, getRegionStats, MultiAccountManager, OrganizationManager, multiAccountManager, organizationManager, RECOMMENDED_ACCOUNT_STRUCTURES, RECOMMENDED_SCPS, COMMON_CROSS_ACCOUNT_ROLES, getRecommendedStructure, generateCrossAccountRoleCF, validateAccountStructure, formatAccountStructure, generateDeploymentWorkflow, generateMultiEnvWorkflow, generatePRPreviewWorkflow, generateScheduledWorkflow, generateMatrixWorkflow, generateDeploymentPipeline, generateMultiEnvPipeline, generatePreviewPipeline, generateScheduledPipeline, generateManualPipeline, generateDeploymentConfig, generateMultiEnvConfig, generateScheduledConfig, generateApprovalConfig, generateParallelConfig, BackupManager, backupManager, DisasterRecoveryManager, drManager, AWSConfigManager, awsConfigManager, CloudTrailManager, cloudTrailManager, GuardDutyManager, guardDutyManager, SecurityHubManager, securityHubManager, BlueGreenManager, blueGreenManager, CanaryManager, canaryManager, ABTestManager, abTestManager, ProgressiveDeploymentManager, progressiveDeploymentManager, XRayManager, xrayManager, MetricsManager, metricsManager, LogsManager, logsManager, SyntheticsManager, syntheticsManager, MigrationManager, migrationManager, ReplicaManager, replicaManager, PerformanceManager, performanceManager, DatabaseUserManager, databaseUserManager, SecretsRotationManager, secretsRotationManager, SecretsManager, secretsManager, CertificateManager, certificateManager, SecurityScanningManager, securityScanningManager, ImageScanningManager, imageScanningManager, BuildOptimizationManager, buildOptimizationManager, ContainerRegistryManager, containerRegistryManager, ServiceMeshManager, serviceMeshManager, LambdaLayersManager, lambdaLayersManager, LambdaVersionsManager, lambdaVersionsManager, LambdaConcurrencyManager, lambdaConcurrencyManager, LambdaDestinationsManager, lambdaDestinationsManager, LambdaVPCManager, lambdaVPCManager, LambdaDLQManager, lambdaDLQManager, Route53RoutingManager, route53RoutingManager, DNSSECManager, dnssecManager, Route53ResolverManager, route53ResolverManager, BounceComplaintHandler, bounceComplaintHandler, EmailAnalyticsManager, emailAnalyticsManager, SenderReputationManager, senderReputationManager, EmailTemplateManager, emailTemplateManager, exports_advanced, exports_inbound, exports_advanced2, exports_advanced3, FIFOQueueManager, fifoQueueManager, DLQMonitoringManager, dlqMonitoringManager, BatchProcessingManager, batchProcessingManager, QueueManagementManager, queueManagementManager, createZip, generateBootstrap, runBuildHooks, sha256, artifactKey, packageServerlessApp, MANAGED_NODE_VERSIONS, resolveServerlessRuntime, resolveQueues, resolveQueueNames, composeServerlessAppTemplate, PHP_LAYER_EXTENSIONS, phpLayerPackages, phpLayerBuildStage, generatePhpLayerDockerfile, generateAppImageDockerfile, sharedRuntimeLoop, buildNodeRuntimeLayerZip, buildBunRuntimeLayerZip, resolveApp, eventToRequest, responseToResult, createHttpHandler, createQueueHandler, createCliHandler, createHandlers, generatePhpFpmConfig, phpRuntimeLayerAssets, laravelServerlessEnvDefaults, LARAVEL_SERVERLESS_BUILD_STEPS, buildPhpRuntimeLayerZip, PHP_DEFAULT_EXCLUDES, runPhpBuildHooks, collectPhpAppEntries, packagePhpApp, StaticSiteManager, staticSiteManager, StorageAdvancedManager, storageAdvancedManager, HealthCheckManager, healthCheckManager, NetworkSecurityManager, networkSecurityManager, ResourceManagementManager, resourceManagementManager };
|
|
46158
|
+
export { detectServiceRegion, signRequest, signRequestAsync, createPresignedUrl, createPresignedUrlAsync, makeAWSRequest, makeAWSRequestOnce, makeAWSRequestAsync, parseXMLResponse, parseJSONResponse, clearSigningKeyCache, getSigningKeyCacheSize, isNodeCryptoAvailable, isWebCryptoAvailable, QueuePresets, RealtimePresets, detectDeploymentTargets, resolveDeploymentMode, deploymentCoexistenceError, resolveCloudProvider, TemplateBuilder, Pseudo, generateResourceName, generateLogicalId, getTimestamp, sanitizeName, resolveProjectStackName, resolveSiteStackName, resolveSiteResourceName, resolveSiteBucketName, resolveStorageBucketName, resolveDeployBucketName, resolveServerlessAppStackName, resolveServerlessArtifactBucketName, resolveServerlessAssetBucketName, DependencyGraph, analyzeStackDiff, formatDiff, Storage, Registry, CDN, DNS, Security, Compute, Network, FileSystem, Email, exports_incoming_call, Phone, Queue, JobLoader, StacksIntegration, exports_send, SMS, AI, Database, Cache, Permissions, ApiGateway, Messaging, Workflow, Monitoring, Auth, Deployment, AssetHasher, Secrets, ParameterStore, Search, Redirects, Communication, Fn2, Arn, AWS_PSEUDO_PARAMETERS, CloudFormationBuilder, buildCloudFormationTemplate, createStaticSitePreset, createNodeJsServerPreset, createNodeJsServerlessPreset, createServerlessNodePreset, createFullStackAppPreset, createApiBackendPreset, createWordPressPreset, createJamstackPreset, createMicroservicesPreset, createRealtimeAppPreset, createDataPipelinePreset, createMLApiPreset, createTraditionalWebAppPreset, createLaravelPreset, createServerlessLaravelPreset, createDashboardSite, DASHBOARD_STATE_DIR, DASHBOARD_ENTRY, resolveDashboardDomain, resolveDashboardDomains, hasManagementDashboardSite, MANAGEMENT_DASHBOARD_SITE_PREFIX, managementDashboardSiteName, isManagementDashboardSiteName, resolveManagementDashboardSites, resolveManagementDashboardSite, extendPreset, composePresets, createPreset, mergeInfrastructure, withMonitoring, withSecurity, withDatabase, withCache, withCDN, withQueue, fromEnvironment, fromSharedCredentials, fromEC2Metadata, fromECSMetadata, fromWebIdentity, getCredentials, createCredentialProvider, resolveCredentials, resolveRegion, getAccountId, S3Error, createS3Client, CloudError, ConfigurationError, CredentialError, DeploymentError, ValidationError, AWSAPIError, ErrorCodes, getErrorDetails, createError, DebugLogger, validateConfiguration, detectMisconfigurations, validateCredentials, checkIAMPermissions, getRequiredPermissions, suggestIAMPolicy, DEFAULT_SERVICE_LIMITS, checkServiceQuotas, getQuotaUsageSummary, suggestQuotaIncrease, FileCache, TemplateCache, templateCache, hashFile, hashString, hashBuffer, hashDirectory, hashManifest, quickHash, findChangedFiles, HashCache, parallel, batch, parallelMap, parallelWithRetry, sequence, withTimeout, RateLimiter, chunk, processInChunks, diffTemplates, getDiffSummary, requiresReplacement, categorizeChanges, getDeploymentStrategy, getDiffStats, cloud_config_schema_default, validateAgainstSchema, defaultLocalConfig, getLocalConfig, isLocalDevelopment, getLocalEndpoint, getLocalCredentials, getLocalEnvVars, MockCloudFormation, MockS3, MockDynamoDB, createMockAWS, PreviewEnvironmentManager, previewManager, generatePreviewWorkflow, generateCleanupWorkflow, generateCostReportWorkflow, PreviewNotificationService, previewNotifications, formatTable, formatTree, formatProgressBar, formatBytes, formatDuration, formatList, formatKeyValue, ProgressBar, MultiStepProgress, Spinner, TaskList, suggestCommand, formatSuggestion, isLikelyTypo, getContextualHelp, searchCommands, autocomplete, suggestFlags, formatFlagSuggestions, getCommandUsage, validateCommand, REPL, REPLContext, REPLCommandBuilder, CommandHistory, formatHistory, formatHistoryStats, MultiRegionManager, multiRegionManager, CrossRegionReferenceManager, GlobalResourceManager, RegionPairManager, StackDependencyManager, crossRegionReferenceManager, globalResourceManager, regionPairManager, stackDependencyManager, AWS_REGIONS, getRegion, getAllRegions, getRegionsByLocation, getRegionsByCompliance, getRegionsByPricingTier, isValidRegion, getClosestRegion, suggestRegions, suggestRegionPairs, formatRegion, formatRegionList, getRegionStats, MultiAccountManager, OrganizationManager, multiAccountManager, organizationManager, RECOMMENDED_ACCOUNT_STRUCTURES, RECOMMENDED_SCPS, COMMON_CROSS_ACCOUNT_ROLES, getRecommendedStructure, generateCrossAccountRoleCF, validateAccountStructure, formatAccountStructure, generateDeploymentWorkflow, generateMultiEnvWorkflow, generatePRPreviewWorkflow, generateScheduledWorkflow, generateMatrixWorkflow, generateDeploymentPipeline, generateMultiEnvPipeline, generatePreviewPipeline, generateScheduledPipeline, generateManualPipeline, generateDeploymentConfig, generateMultiEnvConfig, generateScheduledConfig, generateApprovalConfig, generateParallelConfig, BackupManager, backupManager, DisasterRecoveryManager, drManager, AWSConfigManager, awsConfigManager, CloudTrailManager, cloudTrailManager, GuardDutyManager, guardDutyManager, SecurityHubManager, securityHubManager, BlueGreenManager, blueGreenManager, CanaryManager, canaryManager, ABTestManager, abTestManager, ProgressiveDeploymentManager, progressiveDeploymentManager, XRayManager, xrayManager, MetricsManager, metricsManager, LogsManager, logsManager, SyntheticsManager, syntheticsManager, MigrationManager, migrationManager, ReplicaManager, replicaManager, PerformanceManager, performanceManager, DatabaseUserManager, databaseUserManager, SecretsRotationManager, secretsRotationManager, SecretsManager, secretsManager, CertificateManager, certificateManager, SecurityScanningManager, securityScanningManager, ImageScanningManager, imageScanningManager, BuildOptimizationManager, buildOptimizationManager, ContainerRegistryManager, containerRegistryManager, ServiceMeshManager, serviceMeshManager, LambdaLayersManager, lambdaLayersManager, LambdaVersionsManager, lambdaVersionsManager, LambdaConcurrencyManager, lambdaConcurrencyManager, LambdaDestinationsManager, lambdaDestinationsManager, LambdaVPCManager, lambdaVPCManager, LambdaDLQManager, lambdaDLQManager, Route53RoutingManager, route53RoutingManager, DNSSECManager, dnssecManager, Route53ResolverManager, route53ResolverManager, BounceComplaintHandler, bounceComplaintHandler, EmailAnalyticsManager, emailAnalyticsManager, SenderReputationManager, senderReputationManager, EmailTemplateManager, emailTemplateManager, exports_advanced, exports_inbound, exports_advanced2, exports_advanced3, FIFOQueueManager, fifoQueueManager, DLQMonitoringManager, dlqMonitoringManager, BatchProcessingManager, batchProcessingManager, QueueManagementManager, queueManagementManager, createZip, generateBootstrap, runBuildHooks, sha256, artifactKey, packageServerlessApp, MANAGED_NODE_VERSIONS, resolveServerlessRuntime, resolveQueues, resolveQueueNames, composeServerlessAppTemplate, PHP_LAYER_EXTENSIONS, phpLayerPackages, phpLayerBuildStage, generatePhpLayerDockerfile, generateAppImageDockerfile, sharedRuntimeLoop, buildNodeRuntimeLayerZip, buildBunRuntimeLayerZip, resolveApp, eventToRequest, responseToResult, createHttpHandler, createQueueHandler, createCliHandler, createHandlers, generatePhpFpmConfig, phpRuntimeLayerAssets, laravelServerlessEnvDefaults, LARAVEL_SERVERLESS_BUILD_STEPS, buildPhpRuntimeLayerZip, PHP_DEFAULT_EXCLUDES, runPhpBuildHooks, collectPhpAppEntries, packagePhpApp, StaticSiteManager, staticSiteManager, StorageAdvancedManager, storageAdvancedManager, HealthCheckManager, healthCheckManager, NetworkSecurityManager, networkSecurityManager, ResourceManagementManager, resourceManagementManager };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildAndPushServerlessImage
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-5mejsd43.js";
|
|
4
4
|
import"./chunk-hsk6fe6x.js";
|
|
5
|
-
import"./chunk-
|
|
5
|
+
import"./chunk-0tfwdnmf.js";
|
|
6
6
|
import"./chunk-arsh1g5h.js";
|
|
7
7
|
import"./chunk-v0bahtg2.js";
|
|
8
8
|
export {
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
step,
|
|
7
7
|
success,
|
|
8
8
|
warn
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-5mejsd43.js";
|
|
10
10
|
import {
|
|
11
11
|
CloudWatchLogsClient,
|
|
12
12
|
CostExplorerClient,
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
resolveSiteKind,
|
|
29
29
|
resolveUiSource,
|
|
30
30
|
siteInstallBase
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-kcxxwg3j.js";
|
|
32
32
|
import {
|
|
33
33
|
artifactKey,
|
|
34
34
|
composeServerlessAppTemplate,
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
resolveServerlessArtifactBucketName,
|
|
43
43
|
resolveServerlessAssetBucketName,
|
|
44
44
|
resolveServerlessRuntime
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-0tfwdnmf.js";
|
|
46
46
|
import {
|
|
47
47
|
deployStaticSiteWithExternalDnsFull
|
|
48
48
|
} from "./chunk-d7p84vz5.js";
|
|
@@ -347,7 +347,7 @@ async function deployServerlessApp(config, environment, opts = {}) {
|
|
|
347
347
|
}
|
|
348
348
|
if (imageMode) {
|
|
349
349
|
step("Building + pushing container image");
|
|
350
|
-
const { buildAndPushServerlessImage } = await import("./chunk-
|
|
350
|
+
const { buildAndPushServerlessImage } = await import("./chunk-c3yy2xyq.js");
|
|
351
351
|
const built = await buildAndPushServerlessImage({
|
|
352
352
|
app,
|
|
353
353
|
projectRoot,
|
|
@@ -11834,6 +11834,135 @@ function scopeCloudConfig(config6, user) {
|
|
|
11834
11834
|
};
|
|
11835
11835
|
}
|
|
11836
11836
|
|
|
11837
|
+
// src/deploy/dashboard-site-settings.ts
|
|
11838
|
+
var MEMBER_EDITABLE_SITE_FIELDS = new Set([
|
|
11839
|
+
"name",
|
|
11840
|
+
"ssl",
|
|
11841
|
+
"env",
|
|
11842
|
+
"redirects",
|
|
11843
|
+
"aliases",
|
|
11844
|
+
"domain",
|
|
11845
|
+
"path"
|
|
11846
|
+
]);
|
|
11847
|
+
var ADMIN_ONLY_SITE_FIELDS = {
|
|
11848
|
+
build: "build runs as a shell command on the server",
|
|
11849
|
+
start: "start runs as a shell command on the server",
|
|
11850
|
+
root: "root is a filesystem path on the shared server",
|
|
11851
|
+
port: "ports are allocated by the server owner",
|
|
11852
|
+
type: "the deploy type is set by the server owner",
|
|
11853
|
+
php: "the PHP runtime is set by the server owner"
|
|
11854
|
+
};
|
|
11855
|
+
function checkMemberSiteFields(body) {
|
|
11856
|
+
const touched = Object.keys(body).filter((key) => body[key] !== undefined);
|
|
11857
|
+
const forbidden = touched.filter((key) => (key in ADMIN_ONLY_SITE_FIELDS));
|
|
11858
|
+
if (forbidden.length) {
|
|
11859
|
+
const reasons = forbidden.map((key) => `${key} (${ADMIN_ONLY_SITE_FIELDS[key]})`).join(", ");
|
|
11860
|
+
return { ok: false, error: `Only the server owner can change ${reasons}.` };
|
|
11861
|
+
}
|
|
11862
|
+
const unknown = touched.filter((key) => !MEMBER_EDITABLE_SITE_FIELDS.has(key));
|
|
11863
|
+
if (unknown.length)
|
|
11864
|
+
return { ok: false, error: `Only the server owner can change ${unknown.join(", ")}.` };
|
|
11865
|
+
return { ok: true };
|
|
11866
|
+
}
|
|
11867
|
+
function normalizeHost(value) {
|
|
11868
|
+
return String(value ?? "").trim().toLowerCase().replace(/\.$/, "");
|
|
11869
|
+
}
|
|
11870
|
+
function normalizePath(value) {
|
|
11871
|
+
const path = String(value ?? "/").trim();
|
|
11872
|
+
if (!path || path === "/")
|
|
11873
|
+
return "/";
|
|
11874
|
+
return (path.startsWith("/") ? path : `/${path}`).replace(/\/+$/, "") || "/";
|
|
11875
|
+
}
|
|
11876
|
+
function siteHosts(site) {
|
|
11877
|
+
const hosts = [normalizeHost(site?.domain)];
|
|
11878
|
+
if (Array.isArray(site?.aliases))
|
|
11879
|
+
hosts.push(...site.aliases.map(normalizeHost));
|
|
11880
|
+
return hosts.filter(Boolean);
|
|
11881
|
+
}
|
|
11882
|
+
function checkRouteConflict({ siteName, body, sites, ownSites }) {
|
|
11883
|
+
const current = sites[siteName] ?? {};
|
|
11884
|
+
const owned = new Set(ownSites);
|
|
11885
|
+
const wantedHosts = new Set;
|
|
11886
|
+
if (body.domain !== undefined)
|
|
11887
|
+
wantedHosts.add(normalizeHost(body.domain));
|
|
11888
|
+
if (Array.isArray(body.aliases))
|
|
11889
|
+
for (const alias of body.aliases)
|
|
11890
|
+
wantedHosts.add(normalizeHost(alias));
|
|
11891
|
+
wantedHosts.delete("");
|
|
11892
|
+
if (wantedHosts.size === 0)
|
|
11893
|
+
return { ok: true };
|
|
11894
|
+
const wantedPath = normalizePath(body.path !== undefined ? body.path : current.path);
|
|
11895
|
+
for (const [otherName, other] of Object.entries(sites)) {
|
|
11896
|
+
if (otherName === siteName || owned.has(otherName))
|
|
11897
|
+
continue;
|
|
11898
|
+
const otherPath = normalizePath(other?.path);
|
|
11899
|
+
for (const host of siteHosts(other)) {
|
|
11900
|
+
if (!wantedHosts.has(host))
|
|
11901
|
+
continue;
|
|
11902
|
+
if (otherPath === wantedPath) {
|
|
11903
|
+
return {
|
|
11904
|
+
ok: false,
|
|
11905
|
+
error: `${host}${wantedPath === "/" ? "" : wantedPath} is already served by another site on this server.`
|
|
11906
|
+
};
|
|
11907
|
+
}
|
|
11908
|
+
}
|
|
11909
|
+
}
|
|
11910
|
+
return { ok: true };
|
|
11911
|
+
}
|
|
11912
|
+
|
|
11913
|
+
// src/deploy/dashboard-throttle.ts
|
|
11914
|
+
var MAX_ATTEMPTS = 8;
|
|
11915
|
+
var LOCKOUT_MS = 15 * 60 * 1000;
|
|
11916
|
+
var WINDOW_MS = 15 * 60 * 1000;
|
|
11917
|
+
|
|
11918
|
+
class LoginThrottle {
|
|
11919
|
+
maxAttempts;
|
|
11920
|
+
lockoutMs;
|
|
11921
|
+
windowMs;
|
|
11922
|
+
attempts = new Map;
|
|
11923
|
+
constructor(maxAttempts = MAX_ATTEMPTS, lockoutMs = LOCKOUT_MS, windowMs = WINDOW_MS) {
|
|
11924
|
+
this.maxAttempts = maxAttempts;
|
|
11925
|
+
this.lockoutMs = lockoutMs;
|
|
11926
|
+
this.windowMs = windowMs;
|
|
11927
|
+
}
|
|
11928
|
+
key(username, address) {
|
|
11929
|
+
return `${username.trim().toLowerCase()}\x00${address}`;
|
|
11930
|
+
}
|
|
11931
|
+
check(username, address, now = Date.now()) {
|
|
11932
|
+
const entry = this.attempts.get(this.key(username, address));
|
|
11933
|
+
if (!entry?.until)
|
|
11934
|
+
return { allowed: true };
|
|
11935
|
+
if (now >= entry.until) {
|
|
11936
|
+
this.attempts.delete(this.key(username, address));
|
|
11937
|
+
return { allowed: true };
|
|
11938
|
+
}
|
|
11939
|
+
return { allowed: false, retryAfterSeconds: Math.ceil((entry.until - now) / 1000) };
|
|
11940
|
+
}
|
|
11941
|
+
recordFailure(username, address, now = Date.now()) {
|
|
11942
|
+
const key = this.key(username, address);
|
|
11943
|
+
const entry = this.attempts.get(key);
|
|
11944
|
+
const stale = !entry || now - entry.last > this.windowMs;
|
|
11945
|
+
const count = stale ? 1 : entry.count + 1;
|
|
11946
|
+
const next = { count, last: now };
|
|
11947
|
+
if (count >= this.maxAttempts)
|
|
11948
|
+
next.until = now + this.lockoutMs;
|
|
11949
|
+
this.attempts.set(key, next);
|
|
11950
|
+
}
|
|
11951
|
+
recordSuccess(username, address) {
|
|
11952
|
+
this.attempts.delete(this.key(username, address));
|
|
11953
|
+
}
|
|
11954
|
+
prune(now = Date.now()) {
|
|
11955
|
+
for (const [key, entry] of this.attempts) {
|
|
11956
|
+
const expired = entry.until ? now >= entry.until : now - entry.last > this.windowMs;
|
|
11957
|
+
if (expired)
|
|
11958
|
+
this.attempts.delete(key);
|
|
11959
|
+
}
|
|
11960
|
+
}
|
|
11961
|
+
get size() {
|
|
11962
|
+
return this.attempts.size;
|
|
11963
|
+
}
|
|
11964
|
+
}
|
|
11965
|
+
|
|
11837
11966
|
// src/deploy/firewall-config-editor.ts
|
|
11838
11967
|
var ALWAYS_OPEN = new Set([22, 80, 443]);
|
|
11839
11968
|
function isValidPort(port) {
|
|
@@ -12988,6 +13117,9 @@ async function startLocalDashboardServer(options = {}) {
|
|
|
12988
13117
|
const authEnabled = !authDisabled;
|
|
12989
13118
|
const secret = resolveSessionSecret(cwd);
|
|
12990
13119
|
const guard = createDashboardGuard({ cwd, enabled: authEnabled, secret });
|
|
13120
|
+
const throttle = new LoginThrottle;
|
|
13121
|
+
const throttleSweep = setInterval(() => throttle.prune(), 5 * 60 * 1000);
|
|
13122
|
+
throttleSweep.unref?.();
|
|
12991
13123
|
if (authEnabled) {
|
|
12992
13124
|
const bootstrap = ensureAdminUser(cwd, process.env.TS_CLOUD_UI_USERNAME?.trim() || "admin");
|
|
12993
13125
|
if (bootstrap.generated) {
|
|
@@ -13040,11 +13172,19 @@ async function startLocalDashboardServer(options = {}) {
|
|
|
13040
13172
|
const body = await readJsonBody(req);
|
|
13041
13173
|
const username = String(body.username ?? "").trim();
|
|
13042
13174
|
const password = String(body.password ?? "");
|
|
13175
|
+
const address = server2.requestIP(req)?.address ?? "unknown";
|
|
13176
|
+
const gate = throttle.check(username, address);
|
|
13177
|
+
if (!gate.allowed) {
|
|
13178
|
+
return json({ ok: false, error: `Too many failed attempts. Try again in ${Math.ceil((gate.retryAfterSeconds ?? 0) / 60)} minute(s).` }, 429, { "retry-after": String(gate.retryAfterSeconds ?? 60) });
|
|
13179
|
+
}
|
|
13043
13180
|
const user2 = findUser(loadUsers(cwd), username);
|
|
13044
13181
|
const hash = user2?.passwordHash || DUMMY_HASH;
|
|
13045
13182
|
const ok = verifyPassword(password, hash) && !!user2;
|
|
13046
|
-
if (!ok)
|
|
13183
|
+
if (!ok) {
|
|
13184
|
+
throttle.recordFailure(username, address);
|
|
13047
13185
|
return json({ ok: false, error: "Incorrect username or password." }, 401);
|
|
13186
|
+
}
|
|
13187
|
+
throttle.recordSuccess(username, address);
|
|
13048
13188
|
const token = createSessionToken(user2.username, secret);
|
|
13049
13189
|
return json({ ok: true, user: describeUser(user2) }, 200, {
|
|
13050
13190
|
"set-cookie": serializeSessionCookie(token, { secure: cookieSecure })
|
|
@@ -13248,6 +13388,19 @@ async function startLocalDashboardServer(options = {}) {
|
|
|
13248
13388
|
const existing = config6.sites?.[name];
|
|
13249
13389
|
if (!name || !existing)
|
|
13250
13390
|
return json({ ok: false, error: `Site '${name}' was not found.` }, 404);
|
|
13391
|
+
if (user.role === "member") {
|
|
13392
|
+
const fields = checkMemberSiteFields(body);
|
|
13393
|
+
if (!fields.ok)
|
|
13394
|
+
return json({ ok: false, error: fields.error }, 403);
|
|
13395
|
+
const conflict = checkRouteConflict({
|
|
13396
|
+
siteName: name,
|
|
13397
|
+
body,
|
|
13398
|
+
sites: config6.sites ?? {},
|
|
13399
|
+
ownSites: Object.keys(user.sites)
|
|
13400
|
+
});
|
|
13401
|
+
if (!conflict.ok)
|
|
13402
|
+
return json({ ok: false, error: conflict.error }, 409);
|
|
13403
|
+
}
|
|
13251
13404
|
if (body.port !== undefined && body.port !== null && body.port !== "" && (!Number.isInteger(Number(body.port)) || Number(body.port) < 1 || Number(body.port) > 65535))
|
|
13252
13405
|
return json({ ok: false, error: "Port must be a number between 1 and 65535." }, 422);
|
|
13253
13406
|
let text2 = await readFile(configPath, "utf8");
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
resolveCloudProvider,
|
|
6
6
|
resolveManagementDashboardSites,
|
|
7
7
|
resolveProjectStackName
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-0tfwdnmf.js";
|
|
9
9
|
import {
|
|
10
10
|
EC2Client,
|
|
11
11
|
SSMClient
|
|
@@ -3579,7 +3579,9 @@ function releasePaths(base, releaseId) {
|
|
|
3579
3579
|
}
|
|
3580
3580
|
function isFileSharedPath(p) {
|
|
3581
3581
|
const name = p.split("/").pop() || p;
|
|
3582
|
-
|
|
3582
|
+
if (name === ".env" || name.startsWith(".env."))
|
|
3583
|
+
return true;
|
|
3584
|
+
return /\.[a-z0-9]+$/i.test(name);
|
|
3583
3585
|
}
|
|
3584
3586
|
function buildEnsureReleaseLayout(paths, sharedPaths = DEFAULT_SHARED_PATHS) {
|
|
3585
3587
|
const lines = [
|
|
@@ -3706,7 +3708,7 @@ function buildSiteDeployScript(options) {
|
|
|
3706
3708
|
const paths = releasePaths(base, releaseId);
|
|
3707
3709
|
const unitBase = `${slug}-${siteName}`;
|
|
3708
3710
|
const serviceName = `${unitBase}.service`;
|
|
3709
|
-
const sharedPaths = [".env"];
|
|
3711
|
+
const sharedPaths = [...new Set([".env", ...options.sharedPaths ?? []])];
|
|
3710
3712
|
const envFile = formatEnvFile(envEntries);
|
|
3711
3713
|
const preStart = preStartCommands.length > 0 ? [`cd ${paths.release}`, ...preStartCommands] : [];
|
|
3712
3714
|
const stageRelease = [
|
|
@@ -3816,14 +3818,14 @@ function buildLocalArtifactFetch(localPath, siteName) {
|
|
|
3816
3818
|
];
|
|
3817
3819
|
}
|
|
3818
3820
|
// src/drivers/shared/compute-deploy.ts
|
|
3819
|
-
import { copyFileSync } from "node:fs";
|
|
3821
|
+
import { copyFileSync as copyFileSync2 } from "node:fs";
|
|
3820
3822
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
3821
3823
|
import { join as join5 } from "node:path";
|
|
3822
3824
|
|
|
3823
3825
|
// src/deploy/management-dashboard.ts
|
|
3824
3826
|
import { execSync as execSync2 } from "node:child_process";
|
|
3825
3827
|
import { randomBytes } from "node:crypto";
|
|
3826
|
-
import { chmodSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "node:fs";
|
|
3828
|
+
import { chmodSync, copyFileSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "node:fs";
|
|
3827
3829
|
import { tmpdir } from "node:os";
|
|
3828
3830
|
import { dirname, isAbsolute, join as join4 } from "node:path";
|
|
3829
3831
|
import { fileURLToPath } from "node:url";
|
|
@@ -3862,6 +3864,57 @@ var noopLogger = { info: () => {}, warn: () => {} };
|
|
|
3862
3864
|
function truthy(v) {
|
|
3863
3865
|
return v != null && v !== "" && v !== "0" && v.toLowerCase() !== "false";
|
|
3864
3866
|
}
|
|
3867
|
+
var LIVE_STAGE_DIR = join4(".ts-cloud", "dashboard-release");
|
|
3868
|
+
var CONFIG_CANDIDATES = [
|
|
3869
|
+
["config", "cloud.ts"],
|
|
3870
|
+
["config", "cloud.js"],
|
|
3871
|
+
["cloud.config.ts"],
|
|
3872
|
+
["cloud.config.js"]
|
|
3873
|
+
];
|
|
3874
|
+
function resolveDashboardVersion() {
|
|
3875
|
+
const explicit = process.env.TS_CLOUD_UI_VERSION?.trim();
|
|
3876
|
+
if (explicit)
|
|
3877
|
+
return explicit;
|
|
3878
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
3879
|
+
for (const candidate of [join4(here, "..", "..", "package.json"), join4(here, "..", "package.json")]) {
|
|
3880
|
+
try {
|
|
3881
|
+
const pkg = JSON.parse(readFileSync3(candidate, "utf8"));
|
|
3882
|
+
if (pkg.name === "@stacksjs/ts-cloud" && pkg.version)
|
|
3883
|
+
return `^${pkg.version}`;
|
|
3884
|
+
} catch {}
|
|
3885
|
+
}
|
|
3886
|
+
return "latest";
|
|
3887
|
+
}
|
|
3888
|
+
function stageLiveDashboardRoot(cwd, logger) {
|
|
3889
|
+
const source = CONFIG_CANDIDATES.map((parts) => join4(cwd, ...parts)).find((file) => existsSync2(file));
|
|
3890
|
+
if (!source) {
|
|
3891
|
+
logger.warn("Management dashboard: no cloud config found to ship — skipping the live dashboard.");
|
|
3892
|
+
return null;
|
|
3893
|
+
}
|
|
3894
|
+
const stage = join4(cwd, LIVE_STAGE_DIR);
|
|
3895
|
+
try {
|
|
3896
|
+
mkdirSync(stage, { recursive: true });
|
|
3897
|
+
const isStacksLayout = source.endsWith(join4("config", "cloud.ts")) || source.endsWith(join4("config", "cloud.js"));
|
|
3898
|
+
if (isStacksLayout) {
|
|
3899
|
+
mkdirSync(join4(stage, "config"), { recursive: true });
|
|
3900
|
+
copyFileSync(source, join4(stage, "config", source.endsWith(".ts") ? "cloud.ts" : "cloud.js"));
|
|
3901
|
+
} else {
|
|
3902
|
+
copyFileSync(source, join4(stage, source.endsWith(".ts") ? "cloud.config.ts" : "cloud.config.js"));
|
|
3903
|
+
}
|
|
3904
|
+
const pkg = {
|
|
3905
|
+
name: "ts-cloud-dashboard",
|
|
3906
|
+
private: true,
|
|
3907
|
+
type: "module",
|
|
3908
|
+
dependencies: { "@stacksjs/ts-cloud": resolveDashboardVersion() }
|
|
3909
|
+
};
|
|
3910
|
+
writeFileSync(join4(stage, "package.json"), `${JSON.stringify(pkg, null, 2)}
|
|
3911
|
+
`);
|
|
3912
|
+
return LIVE_STAGE_DIR;
|
|
3913
|
+
} catch (error) {
|
|
3914
|
+
logger.warn(`Management dashboard: could not stage the live release (${error?.message ?? error}) — skipping.`);
|
|
3915
|
+
return null;
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3865
3918
|
function resolveUiSource(cwd) {
|
|
3866
3919
|
if (existsSync2(join4(cwd, "packages", "ui", "pages")) || existsSync2(join4(cwd, "packages", "ui", "package.json"))) {
|
|
3867
3920
|
return { uiRoot: "packages/ui/dist", build: "cd packages/ui && bun install && bun run build" };
|
|
@@ -3888,6 +3941,32 @@ function ensureManagementDashboard(config, options = {}) {
|
|
|
3888
3941
|
}
|
|
3889
3942
|
if (hasManagementDashboardSite(config))
|
|
3890
3943
|
return config;
|
|
3944
|
+
const environment = config.environments && Object.keys(config.environments)[0];
|
|
3945
|
+
const domain = process.env.TS_CLOUD_UI_DOMAIN?.trim() || undefined;
|
|
3946
|
+
const port = Number(process.env.TS_CLOUD_UI_PORT) || undefined;
|
|
3947
|
+
const sites = { ...config.sites ?? {} };
|
|
3948
|
+
if (!truthy(process.env.TS_CLOUD_UI_STATIC)) {
|
|
3949
|
+
const uiRoot = stageLiveDashboardRoot(cwd, logger);
|
|
3950
|
+
if (!uiRoot)
|
|
3951
|
+
return config;
|
|
3952
|
+
const resolved2 = resolveManagementDashboardSites(config, environment ?? "production", {
|
|
3953
|
+
uiRoot,
|
|
3954
|
+
build: false,
|
|
3955
|
+
domain,
|
|
3956
|
+
port,
|
|
3957
|
+
live: true
|
|
3958
|
+
});
|
|
3959
|
+
if (resolved2.length === 0) {
|
|
3960
|
+
logger.info("Management dashboard: no domain resolved (set TS_CLOUD_UI_DOMAIN or configure a site domain) — skipping.");
|
|
3961
|
+
return config;
|
|
3962
|
+
}
|
|
3963
|
+
for (const { name, site } of resolved2) {
|
|
3964
|
+
sites[name] = site;
|
|
3965
|
+
logger.info(`Management dashboard → https://${site.domain} (sign in with your ts-cloud account; the first deploy prints an admin password once)`);
|
|
3966
|
+
}
|
|
3967
|
+
config.sites = sites;
|
|
3968
|
+
return config;
|
|
3969
|
+
}
|
|
3891
3970
|
const ui = resolveUiSource(cwd);
|
|
3892
3971
|
if (!ui) {
|
|
3893
3972
|
logger.info("Management dashboard: UI not found (no local ui/ or packaged dist/ui) — skipping auto-deploy.");
|
|
@@ -3895,17 +3974,14 @@ function ensureManagementDashboard(config, options = {}) {
|
|
|
3895
3974
|
}
|
|
3896
3975
|
const username = process.env.TS_CLOUD_UI_USERNAME?.trim() || "admin";
|
|
3897
3976
|
const auth = resolveDashboardAuth(cwd, username, logger);
|
|
3898
|
-
const environment = config.environments && Object.keys(config.environments)[0];
|
|
3899
|
-
const live = truthy(process.env.TS_CLOUD_UI_LIVE);
|
|
3900
|
-
const port = Number(process.env.TS_CLOUD_UI_PORT) || undefined;
|
|
3901
3977
|
const resolved = resolveManagementDashboardSites(config, environment ?? "production", {
|
|
3902
3978
|
uiRoot: ui.uiRoot,
|
|
3903
3979
|
build: ui.build,
|
|
3904
|
-
domain
|
|
3980
|
+
domain,
|
|
3905
3981
|
username,
|
|
3906
3982
|
password: auth.password,
|
|
3907
3983
|
realm: process.env.TS_CLOUD_UI_REALM?.trim() || undefined,
|
|
3908
|
-
live,
|
|
3984
|
+
live: false,
|
|
3909
3985
|
port
|
|
3910
3986
|
});
|
|
3911
3987
|
if (resolved.length === 0) {
|
|
@@ -3913,7 +3989,7 @@ function ensureManagementDashboard(config, options = {}) {
|
|
|
3913
3989
|
return config;
|
|
3914
3990
|
}
|
|
3915
3991
|
const authNote = auth.source === "public" ? "NO AUTH — TS_CLOUD_UI_PUBLIC is set (dashboard is publicly reachable)" : auth.source === "env" ? "htpasswd-protected (TS_CLOUD_UI_PASSWORD)" : `htpasswd-protected (auto-generated — see ${DASHBOARD_CREDENTIALS_FILE})`;
|
|
3916
|
-
|
|
3992
|
+
logger.warn("Management dashboard: static mode — one shared password, and no per-site collaborators. Unset TS_CLOUD_UI_STATIC for the live dashboard.");
|
|
3917
3993
|
for (const { name, site } of resolved) {
|
|
3918
3994
|
sites[name] = site;
|
|
3919
3995
|
logger.info(`Management dashboard → https://${site.domain} (${authNote})`);
|
|
@@ -4566,6 +4642,7 @@ async function deploySiteRelease(driver, options, logger = noopLogger2) {
|
|
|
4566
4642
|
envEntries: site.env || {},
|
|
4567
4643
|
port: site.port,
|
|
4568
4644
|
preStartCommands: site.preStart,
|
|
4645
|
+
sharedPaths: site.sharedPaths,
|
|
4569
4646
|
zeroDowntime: site.zeroDowntime !== false,
|
|
4570
4647
|
healthCheckPath: site.healthCheck?.path
|
|
4571
4648
|
});
|
|
@@ -4652,7 +4729,7 @@ async function deployAllComputeSites(options) {
|
|
|
4652
4729
|
}
|
|
4653
4730
|
try {
|
|
4654
4731
|
const copy = join5(tmpdir2(), `${slug}-${name}-${sha}.tar.gz`);
|
|
4655
|
-
|
|
4732
|
+
copyFileSync2(dashboardTarball, copy);
|
|
4656
4733
|
dashboardTarballBySite.set(name, copy);
|
|
4657
4734
|
} catch {
|
|
4658
4735
|
dashboardTarballBySite.set(name, dashboardTarball);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which site settings a member may change, and route-conflict checks.
|
|
3
|
+
*
|
|
4
|
+
* `site:settings` lets a site owner edit their own site — but a site's config
|
|
5
|
+
* is not all equally harmless on a box shared with other tenants:
|
|
6
|
+
*
|
|
7
|
+
* - `build` and `start` are **shell commands run on the box at deploy time**,
|
|
8
|
+
* as root. A member who could set them would own the whole server, and every
|
|
9
|
+
* other tenant's site with it.
|
|
10
|
+
* - `root` is a filesystem path. Pointed at another site's directory (or `/`),
|
|
11
|
+
* it serves someone else's files to the internet.
|
|
12
|
+
* - `port`, `type` and `php` pick runtime and bind ports, which are the box's
|
|
13
|
+
* to allocate, not one tenant's.
|
|
14
|
+
*
|
|
15
|
+
* Those stay with the box owner. What is left is genuinely the tenant's own:
|
|
16
|
+
* TLS, their app's env, their redirects, and their routing — and routing is
|
|
17
|
+
* still checked against other tenants, since claiming `example.com` when
|
|
18
|
+
* someone else already serves it is hijacking their traffic.
|
|
19
|
+
*
|
|
20
|
+
* An allowlist, not a blocklist: a site field added later is admin-only until
|
|
21
|
+
* someone decides it is safe to hand over.
|
|
22
|
+
*/
|
|
23
|
+
/** Fields a member holding `site:settings` on the site may change. */
|
|
24
|
+
export declare const MEMBER_EDITABLE_SITE_FIELDS: ReadonlySet<string>;
|
|
25
|
+
/**
|
|
26
|
+
* Fields only the box owner may change, with why — used to explain the refusal
|
|
27
|
+
* rather than a bare 403.
|
|
28
|
+
*/
|
|
29
|
+
export declare const ADMIN_ONLY_SITE_FIELDS: Readonly<Record<string, string>>;
|
|
30
|
+
export interface FieldCheck {
|
|
31
|
+
ok: boolean;
|
|
32
|
+
error?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Whether a member may apply `body` to a site. Refuses the whole request if it
|
|
36
|
+
* touches any admin-only field, rather than silently applying the safe subset —
|
|
37
|
+
* a caller who asked to set `start` should be told it did not happen.
|
|
38
|
+
*/
|
|
39
|
+
export declare function checkMemberSiteFields(body: Record<string, any>): FieldCheck;
|
|
40
|
+
export interface RouteConflictInput {
|
|
41
|
+
/** The site being edited. */
|
|
42
|
+
siteName: string;
|
|
43
|
+
/** The requested changes (domain / path / aliases). */
|
|
44
|
+
body: Record<string, any>;
|
|
45
|
+
/** All sites on the box, by name. */
|
|
46
|
+
sites: Record<string, any>;
|
|
47
|
+
/** Site names the editor holds a grant on — conflicts with these are theirs. */
|
|
48
|
+
ownSites: string[];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Refuse a routing change that would claim a host another tenant already
|
|
52
|
+
* serves. Conflicts with the editor's own sites are allowed: moving a domain
|
|
53
|
+
* between two sites you control is your business.
|
|
54
|
+
*/
|
|
55
|
+
export declare function checkRouteConflict({ siteName, body, sites, ownSites }: RouteConflictInput): FieldCheck;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Login throttling.
|
|
3
|
+
*
|
|
4
|
+
* The dashboard's login is internet-facing, and it is the door to a box that
|
|
5
|
+
* hosts other people's sites. scrypt makes each guess cost ~50ms, which slows
|
|
6
|
+
* an attacker but does not stop one — and while generated passwords are strong,
|
|
7
|
+
* an operator can set their own via `TS_CLOUD_UI_PASSWORD`.
|
|
8
|
+
*
|
|
9
|
+
* Failures are counted per key (username + client address) and lock that key
|
|
10
|
+
* out for a window once they pass a threshold. In-memory on purpose: the
|
|
11
|
+
* dashboard is a single process per box, and a restart clearing the counters is
|
|
12
|
+
* an acceptable trade for having no store to keep.
|
|
13
|
+
*
|
|
14
|
+
* Keyed on username *and* address so one attacker cannot lock a legitimate
|
|
15
|
+
* operator out of their own box by failing logins against their username
|
|
16
|
+
* (a denial of service dressed up as a security control).
|
|
17
|
+
*/
|
|
18
|
+
/** Failures before a key is locked out. */
|
|
19
|
+
export declare const MAX_ATTEMPTS: number;
|
|
20
|
+
/** How long a locked key stays locked. */
|
|
21
|
+
export declare const LOCKOUT_MS: number;
|
|
22
|
+
/** Failures older than this stop counting, so occasional typos never lock. */
|
|
23
|
+
export declare const WINDOW_MS: number;
|
|
24
|
+
export interface ThrottleDecision {
|
|
25
|
+
allowed: boolean;
|
|
26
|
+
/** Seconds until the caller may try again. Only set when blocked. */
|
|
27
|
+
retryAfterSeconds?: number;
|
|
28
|
+
}
|
|
29
|
+
export declare class LoginThrottle {
|
|
30
|
+
private readonly maxAttempts;
|
|
31
|
+
private readonly lockoutMs;
|
|
32
|
+
private readonly windowMs;
|
|
33
|
+
private attempts;
|
|
34
|
+
constructor(maxAttempts?: number, lockoutMs?: number, windowMs?: number);
|
|
35
|
+
private key;
|
|
36
|
+
/** Whether a login attempt for this key may proceed. */
|
|
37
|
+
check(username: string, address: string, now?: number): ThrottleDecision;
|
|
38
|
+
/** Record a failed attempt, locking the key once it passes the threshold. */
|
|
39
|
+
recordFailure(username: string, address: string, now?: number): void;
|
|
40
|
+
/** Clear the counter for a key after a successful login. */
|
|
41
|
+
recordSuccess(username: string, address: string): void;
|
|
42
|
+
/** Drop expired entries so the map cannot grow without bound. */
|
|
43
|
+
prune(now?: number): void;
|
|
44
|
+
/** Tracked keys. For tests and diagnostics. */
|
|
45
|
+
get size(): number;
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/deploy/index.js
CHANGED
|
@@ -12,10 +12,10 @@ import {
|
|
|
12
12
|
sanitizeCloudConfig,
|
|
13
13
|
setMaintenance,
|
|
14
14
|
startLocalDashboardServer
|
|
15
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-js3vrvvn.js";
|
|
16
16
|
import {
|
|
17
17
|
buildAndPushServerlessImage
|
|
18
|
-
} from "../chunk-
|
|
18
|
+
} from "../chunk-5mejsd43.js";
|
|
19
19
|
import"../chunk-b82pbxyp.js";
|
|
20
20
|
import"../chunk-hsk6fe6x.js";
|
|
21
21
|
import {
|
|
@@ -30,8 +30,8 @@ import {
|
|
|
30
30
|
resolveUiSource,
|
|
31
31
|
siteInstallBase,
|
|
32
32
|
validateDeploymentConfig
|
|
33
|
-
} from "../chunk-
|
|
34
|
-
import"../chunk-
|
|
33
|
+
} from "../chunk-kcxxwg3j.js";
|
|
34
|
+
import"../chunk-0tfwdnmf.js";
|
|
35
35
|
import"../chunk-93hjhs78.js";
|
|
36
36
|
import {
|
|
37
37
|
deleteStaticSite,
|