Download
111 lines 3.6 KiBGoModified 2026-10-28 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
package render

import (
	"errors"
	"fmt"
	"io"
	"os"
	"strings"

	"code.bruce-hill.com/repo-renderer/internal/config"
	"code.bruce-hill.com/repo-renderer/internal/gitx"
	"code.bruce-hill.com/repo-renderer/internal/mirror"
)

// Check validates the configuration (loaded from cfgPath), lexers,
// templates and the directories and tools a render needs, reporting on w.
// Problems that would break a render are returned; the rest are warnings.
func (s *Site) Check(w io.Writer, cfgPath string) error {
	var problems []error
	// Run git under the same sandbox the renderer uses, so a missing
	// sandbox entry shows up here and not in the first post-receive.
	if err := s.restrict(); err != nil {
		problems = append(problems, fmt.Errorf("sandbox: %w", err))
	}
	v, err := gitx.Version()
	if err != nil {
		problems = append(problems, fmt.Errorf("git does not run (missing from the sandbox?): %w", err))
	} else {
		fmt.Fprintln(w, "git:", v)
	}
	probe := gitx.Repo{Dir: s.Lay.GitBase}
	if _, err := probe.Run("rev-parse", "--is-bare-repository"); err != nil && !strings.Contains(err.Error(), "not a git repository") {
		problems = append(problems, fmt.Errorf("git cannot run inside %s: %w", s.Lay.GitBase, err))
	}
	fmt.Fprintln(w, "config:", cfgPath)
	lay := s.Lay
	logPath := "(stderr)"
	if s.Cfg.Log == config.LogSyslog {
		logPath = "syslog, and stderr"
	}
	for _, kv := range [][2]string{
		{"repositories", lay.GitBase}, {"site", lay.Htdocs}, {"spool", lay.QueueDir},
		{"state", lay.State}, {"log", logPath}, {"lexers", strings.Join(s.Cfg.LexerDirs, " ")},
	} {
		fmt.Fprintf(w, "%s: %s\n", kv[0], kv[1])
	}
	fmt.Fprintln(w, "host:", s.Cfg.Host)
	fmt.Fprintln(w, "style:", s.HL.StyleName())
	fmt.Fprintln(w, "template hash:", s.TmplHash)
	for _, name := range s.Cfg.LexerNames() {
		lx := s.HL.ByName(name)
		if s.HL.IsPlain(lx) {
			problems = append(problems, fmt.Errorf("lexer %q resolves to plain text", name))
		} else {
			fmt.Fprintf(w, "lexer %s: ok (%v)\n", name, s.Cfg.Lexers[name].Globs)
		}
	}
	for _, d := range []string{lay.GitBase, lay.Htdocs, lay.QueueDir, lay.State} {
		if st, err := os.Stat(d); err != nil || !st.IsDir() {
			fmt.Fprintf(w, "warning: %s does not exist yet\n", d)
		}
	}
	repos, err := s.Repos()
	if err != nil {
		problems = append(problems, err)
	}
	have := map[string]bool{}
	for _, r := range repos {
		have[r] = true
	}
	var private []string
	for _, r := range repos {
		g := gitx.Repo{Dir: s.Lay.GitDir(r)}
		for _, f := range mirror.Failures(g.Dir) {
			fmt.Fprintf(w, "warning: %s: the last mirror push failed: %s\n", r, f)
		}
		section, tags, err := g.Placement()
		if err != nil {
			problems = append(problems, fmt.Errorf("%s: %w", r, err))
			continue
		}
		if section != "" && s.Cfg.SectionIndex(section) < 0 {
			fmt.Fprintf(w, "warning: %s's section is %q, which no section in the config is named, so it is in none\n", r, section)
		}
		for _, t := range tags {
			if !config.ValidTag(t) {
				fmt.Fprintf(w, "warning: %s's tag %q is not usable (not empty, no control characters or surrounding spaces), so it isn't shown\n", r, t)
			}
		}
		if s.private(r) {
			private = append(private, r)
		}
	}
	if len(private) > 0 {
		where := "not on the site: set private_htdocs to serve them behind a password"
		if s.priv != nil {
			where = "on the private site, " + s.Cfg.BaseURL() + config.PrivatePrefix + "/"
		}
		fmt.Fprintf(w, "private (in no section, so %s): %s\n", where, strings.Join(private, ", "))
	}
	for r := range s.Cfg.Repos {
		if !have[r] {
			fmt.Fprintf(w, "warning: repos.%s configured but not under %s\n", r, s.Lay.GitBase)
		}
	}
	if len(problems) > 0 {
		return errors.Join(problems...)
	}
	fmt.Fprintln(w, "ok")
	return nil
}