package main import ( "fmt" "os" "os/exec" "path/filepath" "runtime" "strings" "syscall" ) // runPrechecks — все системные проверки на стадии "Проверка системы". // Возвращает срез результатов, по каждому видно ✓/✗ + объяснение. // // Ничего не модифицирует — просто читает /etc/os-release, проверяет // наличие нужных бинарей, права root, свободное место, артефакты в // artifactsDir и т.п. UI отрисовывает таблицей. func runPrechecks(artifactsDir string) []PrecheckResult { var out []PrecheckResult out = append(out, checkRoot()) out = append(out, checkArch()) out = append(out, checkDistro()) out = append(out, checkAptAvailable()) out = append(out, checkSystemd()) out = append(out, checkDiskSpace()) out = append(out, checkArtifacts(artifactsDir)) return out } func checkRoot() PrecheckResult { if os.Geteuid() == 0 { return PrecheckResult{ID: "root", Title: "Запуск от root", OK: true} } return PrecheckResult{ID: "root", Title: "Запуск от root", OK: false, Message: "Требуется sudo"} } func checkArch() PrecheckResult { if runtime.GOARCH == "amd64" { return PrecheckResult{ID: "arch", Title: "Архитектура amd64", OK: true, Message: runtime.GOARCH} } return PrecheckResult{ID: "arch", Title: "Архитектура amd64", OK: false, Message: "Валидата собрана только под amd64, у вас " + runtime.GOARCH} } func checkDistro() PrecheckResult { id, pretty := readOSRelease() switch id { case "debian", "astra": return PrecheckResult{ID: "distro", Title: "Поддерживаемая ОС", OK: true, Message: pretty} case "ubuntu": return PrecheckResult{ID: "distro", Title: "Поддерживаемая ОС", OK: true, Message: pretty + " (поддерживается на свой страх)"} default: return PrecheckResult{ID: "distro", Title: "Поддерживаемая ОС", OK: false, Message: "ОС не в списке поддерживаемых: " + pretty} } } func checkAptAvailable() PrecheckResult { if _, err := exec.LookPath("apt-get"); err != nil { return PrecheckResult{ID: "apt", Title: "Доступен apt-get", OK: false, Message: "apt-get не найден — это не Debian-семейство"} } return PrecheckResult{ID: "apt", Title: "Доступен apt-get", OK: true} } func checkSystemd() PrecheckResult { if _, err := os.Stat("/run/systemd/system"); err != nil { return PrecheckResult{ID: "systemd", Title: "systemd работает", OK: false, Message: "/run/systemd/system нет"} } return PrecheckResult{ID: "systemd", Title: "systemd работает", OK: true} } func checkDiskSpace() PrecheckResult { var fs syscall.Statfs_t if err := syscall.Statfs("/var", &fs); err != nil { return PrecheckResult{ID: "disk", Title: "Свободное место в /var", OK: false, Message: err.Error()} } freeBytes := fs.Bavail * uint64(fs.Bsize) freeGiB := freeBytes / (1 << 30) if freeGiB < 2 { return PrecheckResult{ID: "disk", Title: "Свободное место в /var", OK: false, Message: fmt.Sprintf("Свободно %d GiB, нужно ≥ 2", freeGiB)} } return PrecheckResult{ID: "disk", Title: "Свободное место в /var", OK: true, Message: fmt.Sprintf("%d GiB свободно", freeGiB)} } func checkArtifacts(dir string) PrecheckResult { required := []struct { Glob string Name string }{ {filepath.Join(dir, "ClientL_Other", "zpki-*.deb"), "zpki (Валидата)"}, {filepath.Join(dir, "bj-server"), "bj-server (Go-бинарь)"}, {filepath.Join(dir, "crypto-service.jar"), "crypto-service.jar"}, } var missing []string for _, r := range required { matches, _ := filepath.Glob(r.Glob) if len(matches) == 0 { missing = append(missing, r.Name) } } if len(missing) > 0 { return PrecheckResult{ ID: "artifacts", Title: "Артефакты дистрибутива", OK: false, Message: "Отсутствуют: " + strings.Join(missing, ", ") + " (положите в " + dir + ")", } } return PrecheckResult{ID: "artifacts", Title: "Артефакты дистрибутива", OK: true, Message: "Все на месте в " + dir} } func readOSRelease() (id, pretty string) { b, err := os.ReadFile("/etc/os-release") if err != nil { return "", "неизвестно" } for _, line := range strings.Split(string(b), "\n") { k, v, ok := strings.Cut(line, "=") if !ok { continue } v = strings.Trim(v, `"`) switch k { case "ID": id = v case "PRETTY_NAME": pretty = v } } return }