also looking at this
fix(fleet): carry a site's TLS material when it moves
#179
4 files
+328
-2
| @@ -14,10 +14,19 @@ import { siteInstallBase } from '../../src/deploy/site-target' | ||
| 14 | 14 | import { buildBackupScript } from '../../src/deploy/dashboard-database' |
| 15 | 15 | import { buildDatabaseSetupScript, isLocalDatabase } from '../../src/drivers/shared/db-provision' |
| 16 | 16 | import { buildBackupRestoreScript } from '../../src/drivers/shared/backups' |
| 17 | import { buildRpxConfig, buildRpxFragmentRefreshScript } from '../../src/drivers/shared/rpx-gateway' | |
| 17 | import { buildRpxConfig, buildRpxFragmentRefreshScript, DEFAULT_RPX_CERTS_DIR } from '../../src/drivers/shared/rpx-gateway' | |
| 18 | 18 | import { FleetStore, SystemFleetSshTransport } from '../../src/fleet' |
| 19 | 19 | import { applyPlan, formatPlan, resolvePlan } from '../../src/operations/plan' |
| 20 | import { planSiteMove, siteMoveArchivePath } from '../../src/operations/site-move' | |
| 20 | import { | |
| 21 | buildCertificatePackScript, | |
| 22 | buildCertificateStateScript, | |
| 23 | buildCertificateUnpackScript, | |
| 24 | certificatesMatch, | |
| 25 | parseCertificateState, | |
| 26 | planSiteMove, | |
| 27 | siteMoveArchivePath, | |
| 28 | siteMoveCertArchivePath, | |
| 29 | } from '../../src/operations/site-move' | |
| 21 | 30 | import { loadValidatedConfig, resolveDnsProviderConfig } from './shared' |
| 22 | 31 | |
| 23 | 32 | interface SiteAddOptions { |
| @@ -126,6 +135,10 @@ async function runSiteMove(siteName: string, options: SiteMoveCommandOptions): P | ||
| 126 | 135 | const onBoxDatabase = |
| 127 | 136 | appDatabase?.name && isLocalDatabase(appDatabase) ? { name: appDatabase.name } : undefined |
| 128 | 137 | const dumpPath = `/tmp/ts-cloud-move-${slug}-${siteName}.sql.gz` |
| 138 | const certArchive = siteMoveCertArchivePath(slug, siteName) | |
| 139 | const certsDir = proxy?.certsDir ?? DEFAULT_RPX_CERTS_DIR | |
| 140 | // Every hostname this site is served on needs its own certificate. | |
| 141 | const certDomains = [domain, ...(site.aliases ?? [])].filter((value): value is string => !!value) | |
| 129 | 142 | const engine = (appDatabase?.engine ?? 'mysql') as 'mysql' | 'mariadb' | 'postgres' |
| 130 | 143 | |
| 131 | 144 | const effects: SiteMoveEffects = { |
| @@ -233,6 +246,36 @@ async function runSiteMove(siteName: string, options: SiteMoveCommandOptions): P | ||
| 233 | 246 | }, |
| 234 | 247 | } |
| 235 | 248 | : {}), |
| 249 | ...(proxy | |
| 250 | ? { | |
| 251 | certificates: { | |
| 252 | inPlace: async () => { | |
| 253 | const state = buildCertificateStateScript(certsDir, certDomains) | |
| 254 | const [onSource, onTarget] = await Promise.all([ | |
| 255 | transport.exec(source, state), | |
| 256 | transport.exec(target, state), | |
| 257 | ]) | |
| 258 | return certificatesMatch( | |
| 259 | parseCertificateState(onSource.stdout), | |
| 260 | parseCertificateState(onTarget.stdout), | |
| 261 | ) | |
| 262 | }, | |
| 263 | carry: async () => { | |
| 264 | await execOn(transport, source, buildCertificatePackScript(certsDir, certDomains, certArchive)) | |
| 265 | const local = `${process.cwd()}/.ts-cloud-move-${slug}-${siteName}-certs.tar.gz` | |
| 266 | // A site behind on-demand TLS may have no certificate yet; the | |
| 267 | // pack script says so and exits clean rather than failing. | |
| 268 | const staged = await transport.exec(source, `test -s ${certArchive} && echo staged || true`) | |
| 269 | if (!staged.stdout.includes('staged')) return | |
| 270 | await copyFile(source, `${source.sshUser}@${source.endpoint}:${certArchive}`, local) | |
| 271 | await copyFile(target, local, `${target.sshUser}@${target.endpoint}:${certArchive}`) | |
| 272 | await Bun.file(local).delete().catch(() => {}) | |
| 273 | await execOn(transport, target, buildCertificateUnpackScript(certsDir, certArchive)) | |
| 274 | await transport.exec(source, `rm -f ${certArchive}`) | |
| 275 | }, | |
| 276 | }, | |
| 277 | } | |
| 278 | : {}), | |
| 236 | 279 | cutoverDns: async () => { |
| 237 | 280 | if (!domain) return [] |
| 238 | 281 | if (!dnsName) return [`No DNS provider configured — point ${domain} at ${target.endpoint} manually.`] |