Preview-Router: öffentliche Seiten überstehen Server-Neustart
Nach einem Neustart startet der Kundenbereich oft vor Appwrite. Bisher lieferte getPublicSubdomains dann ein leeres Set, syncPreviewTls setzte alle Preview-Hosts hinter die Login-Weiche (preview-chain) und wiederholte das nie. Öffentliche Seiten wie anelia-wolf und emmas-schatzkiste waren bis zum nächsten Deploy nur noch mit Login erreichbar bzw. 403. - Antwortet Appwrite nicht, übernimmt syncPreviewTls die öffentlichen Seiten aus der zuletzt geschriebenen preview-hosts.yml. Neue Subdomains bleiben login-geschützt, ohne alte Datei ist alles geschützt. - Beim Start wird der Abgleich alle 15 s wiederholt (max. 10 min), bis er mit Appwrite-Daten geklappt hat. - Tests: Datei lesen, Abgleich mit/ohne Appwrite, Startup-Wiederholung. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ba9916cfe6
commit
d5eb908cb6
3 files changed
+218
-10
No files matched your search
@@ -10,8 +10,8 @@ const execFileAsync = promisify(execFile)
|
||||
/**
|
||||
* Subdomains, die als "oeffentliche Website" (ohne Login) laufen sollen.
|
||||
* Diese bekommen in syncPreviewTls einen Router OHNE preview-auth-ForwardAuth.
|
||||
* Faellt bei Appwrite-Fehlern bewusst auf ein leeres Set zurueck -> alle Seiten
|
||||
* bleiben dann login-geschuetzt (sicherer Default).
|
||||
* Liefert null, wenn Appwrite nicht antwortet (z.B. direkt nach einem
|
||||
* Server-Neustart, solange Appwrite noch hochfaehrt).
|
||||
*/
|
||||
async function getPublicSubdomains() {
|
||||
try {
|
||||
@@ -22,10 +22,38 @@ async function getPublicSubdomains() {
|
||||
return new Set(docs.map((d) => d.subdomain).filter(Boolean))
|
||||
} catch (err) {
|
||||
console.error('[previewDeploy] getPublicSubdomains fehlgeschlagen:', err.message)
|
||||
return new Set()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Oeffentliche Subdomains aus einer von syncPreviewTls geschriebenen
|
||||
* preview-hosts.yml: alle HTTPS-Router ohne preview-chain.
|
||||
*/
|
||||
export function publicSubdomainsFromHostsFile(content) {
|
||||
const result = new Set()
|
||||
let current = null
|
||||
let isProtected = false
|
||||
const finish = () => {
|
||||
if (current && !isProtected) result.add(current)
|
||||
current = null
|
||||
}
|
||||
for (const line of String(content || '').split('\n')) {
|
||||
if (/^ {4}\S/.test(line)) {
|
||||
finish()
|
||||
const match = line.match(/^ {4}preview-([a-z0-9-]+)-https:\s*$/)
|
||||
if (match) {
|
||||
current = match[1]
|
||||
isProtected = false
|
||||
}
|
||||
} else if (current && line.includes('preview-chain@docker')) {
|
||||
isProtected = true
|
||||
}
|
||||
}
|
||||
finish()
|
||||
return result
|
||||
}
|
||||
|
||||
export async function fetchPreviewConfig(repoFullName, branch) {
|
||||
const [owner, repo] = repoFullName.split('/')
|
||||
if (!owner || !repo || !config.gitea.apiToken) {
|
||||
@@ -305,7 +333,16 @@ export async function syncPreviewTls() {
|
||||
.map((entry) => entry.name)
|
||||
.sort()
|
||||
|
||||
const publicSet = await getPublicSubdomains()
|
||||
const hostsFile = path.join(outDir, 'preview-hosts.yml')
|
||||
let publicSet = await getPublicSubdomains()
|
||||
let publicSource = 'appwrite'
|
||||
if (!publicSet) {
|
||||
// Appwrite antwortet nicht: oeffentliche Seiten aus der letzten Datei
|
||||
// uebernehmen, statt sie alle hinter den Login zu setzen. Neue
|
||||
// Subdomains bleiben login-geschuetzt (sicherer Default).
|
||||
publicSet = await fs.readFile(hostsFile, 'utf8').then(publicSubdomainsFromHostsFile, () => new Set())
|
||||
publicSource = 'previous-file'
|
||||
}
|
||||
|
||||
const blocks = slugs.map((name) => {
|
||||
const host = `${name}.${baseHost}`
|
||||
@@ -347,8 +384,34 @@ export async function syncPreviewTls() {
|
||||
].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 }
|
||||
await fs.writeFile(hostsFile, content, 'utf8')
|
||||
return { synced: true, hosts: slugs.length, publicSource }
|
||||
}
|
||||
|
||||
/**
|
||||
* Nach einem Server-Neustart startet der Kundenbereich oft vor Appwrite. Dann
|
||||
* kennt syncPreviewTls die oeffentlichen Seiten nur aus der letzten Datei; der
|
||||
* Abgleich wird wiederholt, bis er mit Appwrite-Daten geklappt hat.
|
||||
*/
|
||||
export async function syncPreviewTlsOnStartup({
|
||||
sync = syncPreviewTls,
|
||||
retryMs = 15_000,
|
||||
maxAttempts = 40,
|
||||
} = {}) {
|
||||
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
||||
try {
|
||||
const result = await sync()
|
||||
console.log('[startup] Preview-TLS-Sync:', JSON.stringify(result))
|
||||
if (!result.synced || result.publicSource === 'appwrite') return result
|
||||
} catch (err) {
|
||||
console.error('[startup] Preview-TLS-Sync fehlgeschlagen:', err.message)
|
||||
}
|
||||
if (attempt < maxAttempts) await new Promise((resolve) => setTimeout(resolve, retryMs))
|
||||
}
|
||||
console.error(
|
||||
`[startup] Preview-TLS-Sync: Appwrite nach ${maxAttempts} Versuchen nicht erreichbar, oeffentliche Seiten bleiben wie in der letzten Datei`
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
export async function runDeploy(repoFullName, branch, previewConfig) {
|
||||
|
||||
Reference in new issue
Block a user