netpulse-migrate замість PowerShell-скрипта: у контейнері немає ані psql, ані PowerShell, а тягнути клієнт Postgres в образ заради одного запуску — це половина дистрибутива на порожньому місці. Міграції вшиті через embed і переїхали в server/migrations: embed не бачить нічого за межами кореня свого модуля, а міграції поруч із бінарником, який їх накочує, не можуть розійтися версіями. Накочування під advisory-блокуванням: два інстанси при rolling update інакше застосували б ту саму міграцію двічі. Кожен файл в одній транзакції разом із записом у schema_migrations; виняток — continuous aggregates, які TimescaleDB забороняє в транзакції. Змінена вже застосована міграція зупиняє запуск: у різних інсталяціях інакше опиниться різна схема під одним номером. Перевірено на чистій базі: 23 міграції, 101 таблиця, повторний запуск каже «схема актуальна». Веб віддає сам API через embed: на self-hosted це прибирає з інструкції встановлення цілий компонент. Три політики кешування — назавжди для assets із хешем у імені, ніколи для index.html, коротко для решти. Знайдено живим прогоном: невідомий шлях під /api/ віддавав 200 з index.html, і клієнт падав на розборі HTML як JSON замість чесного 404. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
34 lines
1.4 KiB
Go
34 lines
1.4 KiB
Go
// Package webui тримає зібраний фронтенд, вшитий у бінарник API.
|
||
//
|
||
// Каталог dist/ порожній у репозиторії й наповнюється при збірці:
|
||
// коміт зібраного бандла означав би тримати в git артефакт, який
|
||
// розходиться з кодом при кожній правці й роздуває історію.
|
||
//
|
||
// Порожній dist — робочий стан: API стартує без інтерфейсу, а розробка
|
||
// йде проти vite з гарячим перезавантаженням.
|
||
package webui
|
||
|
||
import (
|
||
"embed"
|
||
"io/fs"
|
||
)
|
||
|
||
//go:embed all:dist
|
||
var dist embed.FS
|
||
|
||
// FS повертає корінь зібраного інтерфейсу.
|
||
//
|
||
// Другий результат — чи є що віддавати. У збірці без фронтенду в dist
|
||
// лежить лише заглушка з поясненням; ознака справжнього бандла —
|
||
// каталог assets/, який робить vite. Перевіряти наявність index.html
|
||
// марно: він там завжди, інакше embed не скомпілюється.
|
||
func FS() (fs.FS, bool) {
|
||
sub, err := fs.Sub(dist, "dist")
|
||
if err != nil {
|
||
return nil, false
|
||
}
|
||
if _, err := fs.Stat(sub, "assets"); err != nil {
|
||
return nil, false
|
||
}
|
||
return sub, true
|
||
}
|