package nginx import ( "fmt" "io" "os" "path/filepath" "github.com/havline/havline/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(``, code) } return fmt.Sprintf(` %d - %s
%s
%d

%s

%s

`, code, title, bgPos, imgBlock, code, title, message) }