package nginx
import (
"fmt"
"io"
"os"
"path/filepath"
"github.com/fonu/fonu/internal/config"
)
func EnsureErrorPages(cfg config.Config) error {
dir := cfg.ErrorsDir()
if err := os.MkdirAll(dir, 0o755); err != nil {
return err
}
ensureNginxReadable(dir, true)
pages := []struct {
file, sprite, bgPos, title, msg string
code int
}{
{"403.html", "s403", "0% 0%", "访问被拒绝", "你没有权限访问此资源。", 403},
{"404.html", "s404", "100% 0%", "页面不存在", "请求的地址不存在或已被移除。", 404},
{"500.html", "s500", "0% 100%", "服务异常", "服务器遇到错误,请稍后再试。", 500},
{"503.html", "s503", "100% 100%", "服务维护中", "服务暂时不可用,请稍后再试。", 503},
{"429.html", "429", "", "请求过于频繁", "你的请求次数过多,请稍后再试。", 429},
}
for _, p := range pages {
html := errorPageHTML(p.code, p.title, p.msg, p.sprite, p.bgPos)
target := filepath.Join(dir, p.file)
if err := os.WriteFile(target, []byte(html), 0o644); err != nil {
return err
}
ensureNginxReadable(target, false)
}
for _, name := range []string{"error.png", "429.png"} {
if err := ensureErrorAsset(dir, name); err != nil {
return err
}
}
return nil
}
func ensureErrorAsset(dir, name string) error {
target := filepath.Join(dir, name)
if src := findErrorAsset(name); src != "" {
if err := copyFileIfChanged(src, target); err != nil {
return err
}
} else if err := writeEmbeddedErrorAsset(name, target); err != nil {
return err
}
if err := verifyErrorAsset(target); err != nil {
return err
}
ensureNginxReadable(target, false)
return nil
}
func verifyErrorAsset(path string) error {
info, err := os.Stat(path)
if err != nil {
return fmt.Errorf("error asset missing after write: %s: %w", path, err)
}
if info.Size() == 0 {
return fmt.Errorf("error asset is empty: %s", path)
}
return nil
}
func writeEmbeddedErrorAsset(name, target string) error {
data, err := errorAssetFS.ReadFile("assets/" + name)
if err != nil {
return fmt.Errorf("embedded error asset %s: %w", name, err)
}
return os.WriteFile(target, data, 0o644)
}
func findErrorAsset(name string) string {
candidates := []string{
filepath.Join("web", "assets", "image", name),
filepath.Join("/app", "web", "assets", "image", name),
filepath.Join("internal", "nginx", "assets", name),
}
for _, c := range candidates {
if _, err := os.Stat(c); err == nil {
return c
}
}
return ""
}
func copyFileIfChanged(src, dst string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, in)
return err
}
func errorPageHTML(code int, title, message, sprite, bgPos string) string {
var imgBlock string
if sprite == "429" {
imgBlock = `
`
} else {
imgBlock = fmt.Sprintf(`
%s