goploy/cmd/server/main.go

377 lines
9.9 KiB
Go
Raw Normal View History

2022-04-09 21:53:37 +08:00
// Copyright 2022 The Goploy Authors. All rights reserved.
2020-08-04 14:28:25 +08:00
package main
import (
"bufio"
2021-02-11 15:18:46 +08:00
"context"
2021-07-17 11:12:17 +08:00
"encoding/json"
2021-12-18 11:33:17 +08:00
"errors"
2021-01-07 18:32:17 +08:00
"flag"
2020-08-04 14:28:25 +08:00
"fmt"
2021-07-17 11:12:17 +08:00
"github.com/hashicorp/go-version"
2023-07-26 14:32:09 +08:00
"github.com/zhenorzz/goploy/cmd/server/api/agent"
"github.com/zhenorzz/goploy/cmd/server/api/cron"
"github.com/zhenorzz/goploy/cmd/server/api/deploy"
2023-08-08 09:39:57 +08:00
logApi "github.com/zhenorzz/goploy/cmd/server/api/log"
2023-07-26 14:32:09 +08:00
"github.com/zhenorzz/goploy/cmd/server/api/monitor"
"github.com/zhenorzz/goploy/cmd/server/api/namespace"
"github.com/zhenorzz/goploy/cmd/server/api/project"
"github.com/zhenorzz/goploy/cmd/server/api/repository"
"github.com/zhenorzz/goploy/cmd/server/api/role"
server2 "github.com/zhenorzz/goploy/cmd/server/api/server"
"github.com/zhenorzz/goploy/cmd/server/api/template"
"github.com/zhenorzz/goploy/cmd/server/api/user"
2022-11-23 16:49:55 +08:00
"github.com/zhenorzz/goploy/cmd/server/task"
"github.com/zhenorzz/goploy/cmd/server/ws"
2021-12-14 17:42:12 +08:00
"github.com/zhenorzz/goploy/config"
2023-05-12 10:11:38 +08:00
"github.com/zhenorzz/goploy/database"
"github.com/zhenorzz/goploy/internal/model"
2022-11-23 10:30:02 +08:00
"github.com/zhenorzz/goploy/internal/pkg"
2022-11-23 16:49:55 +08:00
"github.com/zhenorzz/goploy/internal/server"
2023-03-06 12:28:22 +08:00
"io"
2020-08-04 14:28:25 +08:00
"log"
"net/http"
"os"
2021-02-11 15:18:46 +08:00
"os/signal"
2021-12-17 17:55:26 +08:00
"runtime"
2021-02-11 15:18:46 +08:00
"strconv"
2024-01-09 16:48:57 +08:00
"strings"
2021-02-11 15:18:46 +08:00
"syscall"
2020-08-04 14:28:25 +08:00
"time"
_ "github.com/go-sql-driver/mysql"
)
2021-01-07 18:32:17 +08:00
var (
2021-07-17 11:12:17 +08:00
help bool
v bool
s string
2021-01-07 18:32:17 +08:00
)
2024-02-05 15:05:14 +08:00
const appVersion = "1.16.2"
2021-07-17 11:12:17 +08:00
2021-01-07 18:32:17 +08:00
func init() {
2022-11-23 10:30:02 +08:00
flag.StringVar(&config.AssetDir, "asset-dir", "", "default: ./")
2021-02-11 15:18:46 +08:00
flag.StringVar(&s, "s", "", "stop")
flag.BoolVar(&help, "help", false, "list available subcommands and some concept guides")
2021-07-17 11:12:17 +08:00
flag.BoolVar(&v, "version", false, "show goploy version")
2022-11-23 18:13:46 +08:00
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Options:\n")
flag.PrintDefaults()
}
go checkUpdate()
2021-01-07 18:32:17 +08:00
}
2023-07-13 14:18:13 +08:00
// @title Goploy
2024-02-05 15:05:14 +08:00
// @version 1.16.2
2023-07-13 14:18:13 +08:00
// @description A web deployment system tool!
// @contact.name zhenorzz
// @contact.url https://github.com/zhenorzz/goploy
// @contact.email zhenorzz@gmail.com
// @license.name GPLv3
// @license.url https://www.gnu.org/licenses/gpl-3.0.html
// @host 127.0.0.1:3001
// @externalDocs.description Get started
// @externalDocs.url https://docs.goploy.icu/
// @securityDefinitions.apikey ApiKeyHeader
// @in header
// @name X-API-KEY
// @securityDefinitions.apikey ApiKeyQueryParam
// @in query
2023-07-14 10:14:48 +08:00
// @name X-API-KEY
2023-07-13 14:18:13 +08:00
// @securityDefinitions.apikey NamespaceHeader
// @in query
// @name G-N-ID
// @securityDefinitions.apikey NamespaceQueryParam
// @in query
// @name G-N-ID
2020-08-04 14:28:25 +08:00
func main() {
2021-01-07 18:32:17 +08:00
flag.Parse()
if help {
flag.Usage()
return
}
2021-07-17 11:12:17 +08:00
if v {
println(appVersion)
2021-02-26 16:45:13 +08:00
return
}
2021-02-11 15:18:46 +08:00
handleClientSignal()
2020-08-15 13:38:06 +08:00
println(`
______ __
/ ____/___ ____ / /___ __ __
/ / __/ __ \/ __ \/ / __ \/ / / /
/ /_/ / /_/ / /_/ / / /_/ / /_/ /
\____/\____/ .___/_/\____/\__, /
2021-07-17 11:12:17 +08:00
/_/ /____/ ` + appVersion + "\n")
2020-08-15 16:35:00 +08:00
install()
2024-01-19 15:16:01 +08:00
config.Init()
2021-08-20 11:26:37 +08:00
model.Init()
if err := model.Update(appVersion); err != nil {
println(err.Error())
}
2020-08-04 14:28:25 +08:00
task.Init()
2022-11-23 16:49:55 +08:00
srv := server.Server{
Server: http.Server{
Addr: ":" + config.Toml.Web.Port,
},
Router: server.NewRouter(),
2021-02-11 15:18:46 +08:00
}
2022-11-23 16:49:55 +08:00
2023-07-26 14:32:09 +08:00
srv.Router.Register(user.User{})
srv.Router.Register(namespace.Namespace{})
srv.Router.Register(role.Role{})
srv.Router.Register(project.Project{})
srv.Router.Register(repository.Repository{})
srv.Router.Register(monitor.Monitor{})
srv.Router.Register(deploy.Deploy{})
srv.Router.Register(server2.Server{})
2023-08-08 09:39:57 +08:00
srv.Router.Register(logApi.Log{})
2023-07-26 14:32:09 +08:00
srv.Router.Register(cron.Cron{})
srv.Router.Register(agent.Agent{})
srv.Router.Register(template.Template{})
2022-11-23 16:49:55 +08:00
srv.Router.Register(ws.GetHub())
2021-02-11 15:18:46 +08:00
go func() {
c := make(chan os.Signal, 1)
2022-01-08 21:35:57 +08:00
// SIGINT Ctrl+C
// SIGTERM A generic signal used to cause program termination
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
2021-02-11 15:18:46 +08:00
println("Received the signal: " + (<-c).String())
2022-01-08 21:35:57 +08:00
2021-02-11 15:18:46 +08:00
println("Server is trying to shutdown, wait for a minute")
2022-01-08 21:35:57 +08:00
ctx, cancel := context.WithTimeout(context.Background(), config.Toml.APP.ShutdownTimeout*time.Second)
2021-02-11 15:18:46 +08:00
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
2022-01-08 21:35:57 +08:00
println("Server shutdown timeout, err: %v\n", err)
2021-02-11 15:18:46 +08:00
}
println("Server shutdown gracefully")
2021-02-12 10:47:16 +08:00
println("Task is trying to shutdown, wait for a minute")
if err := task.Shutdown(ctx); err != nil {
2022-01-08 21:35:57 +08:00
println("Task shutdown timeout, err: %v\n", err)
2021-02-12 10:47:16 +08:00
}
println("Task shutdown gracefully")
2021-02-11 15:18:46 +08:00
}()
2022-11-23 16:49:55 +08:00
srv.Spin()
2020-08-04 14:28:25 +08:00
}
func install() {
2022-11-23 10:30:02 +08:00
_, err := os.Stat(config.GetConfigFile())
2020-08-04 14:28:25 +08:00
if err == nil || os.IsExist(err) {
2021-01-13 17:12:40 +08:00
println("The configuration file already exists, no need to reinstall (if you need to reinstall, please back up the database `goploy` first, delete the .env file, then restart.)")
2020-08-04 14:28:25 +08:00
return
}
2021-12-14 17:42:12 +08:00
cfg := config.Config{
Env: "production",
2022-01-08 21:35:57 +08:00
APP: config.APPConfig{DeployLimit: int32(runtime.NumCPU()), ShutdownTimeout: 10},
2021-12-14 17:42:12 +08:00
Cookie: config.CookieConfig{Name: "goploy_token", Expire: 86400},
JWT: config.JWTConfig{Key: time.Now().String()},
2021-12-25 16:39:23 +08:00
DB: config.DBConfig{Type: "mysql", Host: "127.0.0.1", Port: "3306", Database: "goploy"},
2021-12-14 17:42:12 +08:00
Log: config.LogConfig{Path: "stdout"},
Web: config.WebConfig{Port: "80"},
}
2024-01-09 16:48:57 +08:00
if !runningInDocker() {
cfg = readStdin(cfg)
} else {
cfg.DB.Host = os.Getenv("DB_HOST")
cfg.DB.User = os.Getenv("DB_USER")
cfg.DB.Password = os.Getenv("DB_USER_PASSWORD")
cfg.DB.Database = os.Getenv("DB_NAME")
cfg.DB.Port = os.Getenv("DB_PORT")
if cfg.DB.Port == "" {
cfg.DB.Port = "3306"
}
}
println("Start to install the database...")
2024-01-19 15:16:01 +08:00
runner, err := model.Open(cfg.DB)
2024-01-09 16:48:57 +08:00
if err != nil {
panic(err)
}
2024-01-19 15:16:01 +08:00
defer runner.Close()
if err := runner.CreateDB(cfg.DB.Database); err != nil {
2024-01-09 16:48:57 +08:00
panic(err)
}
2024-01-19 15:16:01 +08:00
if err := runner.UseDB(cfg.DB.Database); err != nil {
2024-01-09 16:48:57 +08:00
panic(err)
}
2024-01-19 15:16:01 +08:00
if err := runner.ImportSQL(database.GoploySQL); err != nil {
2024-01-09 16:48:57 +08:00
panic(err)
}
println("Database installation is complete")
println("Start writing configuration file...")
err = config.Write(cfg)
if err != nil {
panic("Write config file error, " + err.Error())
}
println("Write configuration file completed")
}
func handleClientSignal() {
switch s {
case "stop":
pidFile := config.GetPidFile()
pidStr, err := os.ReadFile(pidFile)
if err != nil {
log.Fatal("handle stop, ", err.Error(), ", may be the server not start")
}
pid, _ := strconv.Atoi(string(pidStr))
process, err := os.FindProcess(pid)
if err != nil {
log.Fatal("handle stop, ", err.Error(), ", may be the server not start")
}
err = process.Signal(syscall.SIGTERM)
if err != nil {
log.Fatal("handle stop, ", err.Error())
}
println("App is trying to shutdown, wait for a minute")
for i := 0; i < 5; i++ {
time.Sleep(time.Second)
if _, err := os.Stat(pidFile); errors.Is(err, os.ErrNotExist) {
println("Success")
break
} else if err != nil {
log.Fatal("handle stop, ", err.Error())
}
}
os.Exit(1)
}
}
func readStdin(cfg config.Config) config.Config {
2021-01-13 17:12:40 +08:00
println("Installation guide ↓")
2020-08-04 14:28:25 +08:00
inputReader := bufio.NewReader(os.Stdin)
2020-08-15 13:38:06 +08:00
println("Installation guidelines (Enter to confirm input)")
2023-05-12 10:11:38 +08:00
2020-08-15 13:38:06 +08:00
println("Please enter the mysql user:")
2020-08-04 14:28:25 +08:00
mysqlUser, err := inputReader.ReadString('\n')
if err != nil {
panic("There were errors reading, exiting program.")
}
2022-11-23 10:30:02 +08:00
cfg.DB.User = pkg.ClearNewline(mysqlUser)
2023-05-12 10:11:38 +08:00
2020-08-15 13:38:06 +08:00
println("Please enter the mysql password:")
2020-08-04 14:28:25 +08:00
mysqlPassword, err := inputReader.ReadString('\n')
if err != nil {
panic("There were errors reading, exiting program.")
}
2022-11-23 10:30:02 +08:00
mysqlPassword = pkg.ClearNewline(mysqlPassword)
2020-08-04 14:28:25 +08:00
if len(mysqlPassword) != 0 {
2021-12-14 17:42:12 +08:00
cfg.DB.Password = mysqlPassword
2020-08-04 14:28:25 +08:00
}
2023-05-12 10:11:38 +08:00
2020-08-15 13:38:06 +08:00
println("Please enter the mysql host(default 127.0.0.1, without port):")
2020-08-04 14:28:25 +08:00
mysqlHost, err := inputReader.ReadString('\n')
if err != nil {
panic("There were errors reading, exiting program.")
}
2022-11-23 10:30:02 +08:00
mysqlHost = pkg.ClearNewline(mysqlHost)
2021-12-14 17:42:12 +08:00
if len(mysqlHost) != 0 {
cfg.DB.Host = mysqlHost
2020-08-04 14:28:25 +08:00
}
2023-05-12 10:11:38 +08:00
2020-08-15 13:38:06 +08:00
println("Please enter the mysql port(default 3306):")
2020-08-04 14:28:25 +08:00
mysqlPort, err := inputReader.ReadString('\n')
if err != nil {
panic("There were errors reading, exiting program.")
}
2022-11-23 10:30:02 +08:00
mysqlPort = pkg.ClearNewline(mysqlPort)
2021-12-14 17:42:12 +08:00
if len(mysqlPort) != 0 {
2021-12-14 19:04:07 +08:00
cfg.DB.Port = mysqlPort
2020-08-04 14:28:25 +08:00
}
2023-05-12 10:11:38 +08:00
println("Please enter the database name(default goploy):")
mysqlDB, err := inputReader.ReadString('\n')
if err != nil {
panic("There were errors reading, exiting program.")
}
mysqlDB = pkg.ClearNewline(mysqlDB)
if len(mysqlDB) != 0 {
cfg.DB.Database = mysqlDB
}
2021-05-30 20:21:18 +08:00
println("Please enter the absolute path of the log directory(default stdout):")
2020-08-04 14:28:25 +08:00
logPath, err := inputReader.ReadString('\n')
if err != nil {
panic("There were errors reading, exiting program.")
}
2022-11-23 10:30:02 +08:00
logPath = pkg.ClearNewline(logPath)
2021-12-14 17:42:12 +08:00
if len(logPath) != 0 {
cfg.Log.Path = logPath
2020-08-04 14:28:25 +08:00
}
2023-05-12 10:11:38 +08:00
2020-08-15 13:38:06 +08:00
println("Please enter the listening port(default 80):")
2020-08-04 14:28:25 +08:00
port, err := inputReader.ReadString('\n')
if err != nil {
panic("There were errors reading, exiting program.")
}
2022-11-23 10:30:02 +08:00
port = pkg.ClearNewline(port)
2021-12-14 17:42:12 +08:00
if len(port) != 0 {
cfg.Web.Port = port
2020-08-04 14:28:25 +08:00
}
2024-01-09 16:48:57 +08:00
return cfg
2021-02-11 15:18:46 +08:00
}
2021-07-17 11:12:17 +08:00
func checkUpdate() {
resp, err := http.Get("https://api.github.com/repos/zhenorzz/goploy/releases/latest")
if err != nil {
println("Check failed")
println(err.Error())
return
}
defer resp.Body.Close()
2023-03-06 12:28:22 +08:00
body, err := io.ReadAll(resp.Body)
2021-07-17 11:12:17 +08:00
if err != nil {
println("Check failed")
println(err.Error())
return
}
var result map[string]interface{}
if err := json.Unmarshal(body, &result); err != nil {
println("Check failed")
println(err.Error())
return
}
2022-04-21 16:42:26 +08:00
tagName, ok := result["tag_name"].(string)
if !ok {
println("Check failed")
return
}
2021-07-17 11:12:17 +08:00
tagVer, err := version.NewVersion(tagName)
if err != nil {
println("Check version error")
println(err.Error())
return
}
currentVer, _ := version.NewVersion(appVersion)
if tagVer.GreaterThan(currentVer) {
println("New release available")
println(result["html_url"].(string))
}
}
2024-01-09 16:48:57 +08:00
func runningInDocker() bool {
_, err := os.Stat("/.dockerenv")
if err == nil {
return true
}
if _, err := os.Stat("/proc/self/cgroup"); err == nil {
data, err := os.ReadFile("/proc/self/cgroup")
if err == nil {
lines := strings.Split(string(data), "\n")
for _, line := range lines {
if strings.Contains(line, "docker") {
return true
}
}
}
}
return false
}