Files
apiServer-service/initializer.go
T

170 lines
4.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"bufio"
"embed"
"errors"
"flag"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"
)
// deploymentAssets contains everything needed to register and manage the services.
//
//go:embed .env.example install.sh start.sh stop.sh restart.sh deploy/*.service
var deploymentAssets embed.FS
var validServicePrefix = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9_.-]*$`)
var assetModes = map[string]os.FileMode{
"install.sh": 0o755,
"start.sh": 0o755,
"stop.sh": 0o755,
"restart.sh": 0o755,
".env.example": 0o644,
"deploy/server.service": 0o644,
"deploy/cli.service": 0o644,
"deploy/scheduler.service": 0o644,
}
func main() {
prefixFlag := flag.String("prefix", "", "systemd 服务名前缀;未指定时交互输入")
flag.Parse()
if runtime.GOOS != "linux" {
fatalf("初始化工具只能在 Linux 服务器上运行")
}
if err := requireRoot(); err != nil {
fatalf("%v", err)
}
appDir, err := executableDir()
if err != nil {
fatalf("获取程序目录失败: %v", err)
}
prefix, err := resolvePrefix(*prefixFlag, os.Stdin, os.Stdout)
if err != nil {
fatalf("读取项目前缀失败: %v", err)
}
if err := extractAssets(appDir); err != nil {
fatalf("释放部署文件失败: %v", err)
}
envCreated, err := initializeEnv(appDir)
if err != nil {
fatalf("初始化 .env 失败: %v", err)
}
if err := os.WriteFile(filepath.Join(appDir, ".service-prefix"), []byte(prefix+"\n"), 0o644); err != nil {
fatalf("保存项目前缀失败: %v", err)
}
fmt.Printf("部署文件已释放到 %s\n", appDir)
if envCreated {
fmt.Println("已根据 .env.example 初始化 .env,请按实际环境修改配置")
} else {
fmt.Println("检测到已有 .env,已保留现有配置")
}
fmt.Printf("正在注册服务: %s-server, %s-cli, %s-scheduler\n", prefix, prefix, prefix)
install := exec.Command("bash", filepath.Join(appDir, "install.sh"))
install.Dir = appDir
install.Stdin = os.Stdin
install.Stdout = os.Stdout
install.Stderr = os.Stderr
if err := install.Run(); err != nil {
fatalf("install.sh 执行失败: %v", err)
}
}
func requireRoot() error {
uid, err := exec.Command("id", "-u").Output()
if err != nil {
return fmt.Errorf("无法检查当前用户权限: %w", err)
}
if strings.TrimSpace(string(uid)) != "0" {
return errors.New("请使用 root 权限运行: sudo ./initializer")
}
return nil
}
func executableDir() (string, error) {
filename, err := os.Executable()
if err != nil {
return "", err
}
filename, err = filepath.Abs(filename)
if err != nil {
return "", err
}
return filepath.Dir(filename), nil
}
func resolvePrefix(value string, in io.Reader, out io.Writer) (string, error) {
prefix := strings.TrimSpace(value)
if prefix == "" {
if _, err := fmt.Fprint(out, "请输入项目前缀(例如 myapp: "); err != nil {
return "", err
}
line, err := bufio.NewReader(in).ReadString('\n')
if err != nil && !errors.Is(err, io.EOF) {
return "", err
}
prefix = strings.TrimSpace(line)
}
if !validServicePrefix.MatchString(prefix) {
return "", fmt.Errorf("%q 不是有效前缀,只允许字母、数字、点、下划线和连字符,且必须以字母或数字开头", prefix)
}
return prefix, nil
}
func extractAssets(appDir string) error {
for name, mode := range assetModes {
content, err := deploymentAssets.ReadFile(name)
if err != nil {
return fmt.Errorf("读取 %s: %w", name, err)
}
target := filepath.Join(appDir, filepath.FromSlash(name))
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
return fmt.Errorf("创建 %s: %w", filepath.Dir(target), err)
}
if err := os.WriteFile(target, content, mode); err != nil {
return fmt.Errorf("写入 %s: %w", target, err)
}
if err := os.Chmod(target, mode); err != nil {
return fmt.Errorf("设置 %s 权限: %w", target, err)
}
}
return nil
}
func initializeEnv(appDir string) (bool, error) {
target := filepath.Join(appDir, ".env")
if _, err := os.Stat(target); err == nil {
return false, nil
} else if !errors.Is(err, os.ErrNotExist) {
return false, err
}
content, err := deploymentAssets.ReadFile(".env.example")
if err != nil {
return false, err
}
if err := os.WriteFile(target, content, 0o600); err != nil {
return false, err
}
return true, nil
}
func fatalf(format string, args ...any) {
fmt.Fprintf(os.Stderr, "错误: "+format+"\n", args...)
os.Exit(1)
}