// Package main — bj-installer. // // Web-инсталлятор для bj-server: на машине клиента после установки // Debian/Astra поднимает локальный HTTP на 127.0.0.1:8181, проводит // через 5-страничный wizard (welcome → precheck → config → install → done) // и за кадром выполняет 20+ шагов установки Валидаты + bj-server + ИШ. // // Прогресс шагов прилетает в UI через Server-Sent Events. Каждый шаг // идемпотентен — можно повторно запускать инсталлятор на уже настроенной // машине, он пропустит то, что сделано. // // Запуск: sudo ./bj-installer [--addr 127.0.0.1:8181] [--no-browser] // Артефакты ожидаются рядом с бинарём в каталоге ./artifacts/: // // artifacts/ClientL_Other/zpki-*.deb // artifacts/ClientL_Other/zsdk-*.deb // artifacts/bj-server (Go-бинарь) // artifacts/crypto-service.jar (Java-сайдкар) // artifacts/ish/igate_*.deb (ИШ НРД) package main import ( "context" "flag" "fmt" "log" "net/http" "os" "os/exec" "os/signal" "runtime" "syscall" "time" ) const banner = ` ====================================================================== bj-installer — мастер установки Bridge-and-Join-s ====================================================================== ` func main() { addr := flag.String("addr", "127.0.0.1:8181", "адрес web-инсталлятора") noBrowser := flag.Bool("no-browser", false, "не пытаться открыть браузер автоматически") artifactsDir := flag.String("artifacts", "./artifacts", "каталог с дистрибутивами (Validata deb, bj-server, ish)") flag.Parse() if os.Geteuid() != 0 { fmt.Fprintln(os.Stderr, "Установщик должен быть запущен от root (sudo).") os.Exit(1) } fmt.Print(banner) fmt.Printf(" адрес: http://%s\n", *addr) fmt.Printf(" артефакты: %s\n", *artifactsDir) fmt.Println("======================================================================") st := newState(*artifactsDir) srv := newServer(st) httpSrv := &http.Server{ Addr: *addr, Handler: srv, ReadHeaderTimeout: 10 * time.Second, } // SIGINT/SIGTERM → корректный shutdown ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() go func() { if err := httpSrv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("HTTP-сервер упал: %v", err) } }() url := "http://" + *addr log.Printf("Откройте в браузере: %s", url) if !*noBrowser { tryOpenBrowser(url) } <-ctx.Done() log.Println("Завершаем работу...") shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _ = httpSrv.Shutdown(shutdownCtx) } // tryOpenBrowser — без фанатизма. Если xdg-open/sensible-browser есть и // $DISPLAY поднят (xrdp, Fly DE) — откроем. Иначе пользователь увидит URL // в выводе и перейдёт сам с другого компа (типичный сценарий headless). func tryOpenBrowser(url string) { if os.Getenv("DISPLAY") == "" && os.Getenv("WAYLAND_DISPLAY") == "" { return } var bin string switch runtime.GOOS { case "linux": for _, cand := range []string{"xdg-open", "sensible-browser", "x-www-browser"} { if p, err := exec.LookPath(cand); err == nil { bin = p break } } } if bin == "" { return } _ = exec.Command(bin, url).Start() }