feat: HTTPS-TLS-Sync nach Deploy, Projekte ohne Subdomain, portalPassword-Feld
- previewDeploy.syncPreviewTls(): schreibt Traefik-Router (Host-Regeln) fuer alle Preview-Subdomains nach preview-hosts.yml, damit Let's Encrypt Zertifikate ausstellt. Laeuft nach jedem Deploy und beim Serverstart (ersetzt das nie aufgerufene bash-Script sync-preview-tls.sh). - createProjectFromTemplate: Subdomain optional. Ohne Subdomain wird nur das Gitea-Repo angelegt (projectType 'repo', Status 'created'), kein Deploy. - customerAdmin: Portal-Passwort liegt jetzt im Feld customers.portalPassword statt als __portalPassword__-Prefix in den Notizen; Legacy-Notizen werden beim naechsten Update bereinigt. CRM-Stufe (lead/customer) wird beim Setzen des Portal-Passworts nicht mehr auf 'active' ueberschrieben. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -67,6 +67,71 @@ export function buildPreviewUrl(subdomain) {
|
||||
return `https://${subdomain}.${config.preview.baseHost}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Schreibt pro deployter Preview-Subdomain einen Traefik-Router mit Host()-Regel
|
||||
* nach preview-hosts.yml, damit Let's Encrypt (HTTP-01) gueltige Zertifikate
|
||||
* ausstellt. HostRegexp allein liefert nur das Default-Zertifikat ("Nicht sicher").
|
||||
* Ersetzt traefik/scripts/sync-preview-tls.sh (bash fehlt im Alpine-Container).
|
||||
*/
|
||||
export async function syncPreviewTls() {
|
||||
const previewRoot = config.preview.deployRoot
|
||||
const outDir = config.preview.traefikDynamicDir
|
||||
if (!previewRoot || !outDir) {
|
||||
return { synced: false, reason: 'PREVIEW_DEPLOY_ROOT oder TRAEFIK_DYNAMIC_DIR nicht gesetzt' }
|
||||
}
|
||||
|
||||
const baseHost = config.preview.baseHost
|
||||
let entries = []
|
||||
try {
|
||||
entries = await fs.readdir(previewRoot, { withFileTypes: true })
|
||||
} catch {
|
||||
entries = []
|
||||
}
|
||||
|
||||
const slugs = entries
|
||||
.filter((entry) => entry.isDirectory() && /^[a-z0-9-]+$/.test(entry.name))
|
||||
.map((entry) => entry.name)
|
||||
.sort()
|
||||
|
||||
const blocks = slugs.map((name) => {
|
||||
const host = `${name}.${baseHost}`
|
||||
const id = `preview-${name}`
|
||||
return [
|
||||
` ${id}-https:`,
|
||||
` rule: "Host(\`${host}\`)"`,
|
||||
' entryPoints:',
|
||||
' - websecure',
|
||||
' service: preview-nginx@docker',
|
||||
' middlewares:',
|
||||
' - preview-chain@docker',
|
||||
' priority: 100',
|
||||
' tls:',
|
||||
' certResolver: letsencrypt',
|
||||
'',
|
||||
` ${id}-http:`,
|
||||
` rule: "Host(\`${host}\`)"`,
|
||||
' entryPoints:',
|
||||
' - web',
|
||||
' service: preview-nginx@docker',
|
||||
' middlewares:',
|
||||
' - preview-sites-redirect@docker',
|
||||
' priority: 100',
|
||||
].join('\n')
|
||||
})
|
||||
|
||||
const content = [
|
||||
'# Auto-generated by previewDeploy.syncPreviewTls - do not edit manually',
|
||||
'http:',
|
||||
' routers:',
|
||||
...blocks,
|
||||
'',
|
||||
].join('\n')
|
||||
|
||||
await fs.mkdir(outDir, { recursive: true })
|
||||
await fs.writeFile(path.join(outDir, 'preview-hosts.yml'), content, 'utf8')
|
||||
return { synced: true, hosts: slugs.length }
|
||||
}
|
||||
|
||||
export async function runDeploy(repoFullName, branch, previewConfig) {
|
||||
if (!config.preview.deployRoot) {
|
||||
return { deployed: false, reason: 'PREVIEW_DEPLOY_ROOT nicht gesetzt' }
|
||||
@@ -85,5 +150,13 @@ export async function runDeploy(repoFullName, branch, previewConfig) {
|
||||
await deployStatic(workDir, targetDir, previewConfig.index || '')
|
||||
}
|
||||
|
||||
return { deployed: true, subdomain, targetDir }
|
||||
let tls = { synced: false }
|
||||
try {
|
||||
tls = await syncPreviewTls()
|
||||
} catch (err) {
|
||||
console.error('[previewDeploy] TLS-Sync fehlgeschlagen:', err.message)
|
||||
tls = { synced: false, error: err.message }
|
||||
}
|
||||
|
||||
return { deployed: true, subdomain, targetDir, tls }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user