goploy/main.go

291 lines
7.7 KiB
Go
Raw Normal View History

2020-08-04 14:28:25 +08:00
package main
import (
"bufio"
2020-09-25 20:05:25 +08:00
"bytes"
2021-02-11 15:18:46 +08:00
"context"
2020-08-04 14:28:25 +08:00
"database/sql"
2021-07-17 11:12:17 +08:00
"encoding/json"
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"
2020-08-04 14:28:25 +08:00
"github.com/joho/godotenv"
"github.com/zhenorzz/goploy/core"
"github.com/zhenorzz/goploy/model"
"github.com/zhenorzz/goploy/route"
"github.com/zhenorzz/goploy/task"
"github.com/zhenorzz/goploy/utils"
"github.com/zhenorzz/goploy/ws"
2021-02-11 15:18:46 +08:00
"io/ioutil"
2020-08-04 14:28:25 +08:00
"log"
"net/http"
"os"
2020-09-25 20:05:25 +08:00
"os/exec"
2021-02-11 15:18:46 +08:00
"os/signal"
"path"
"strconv"
"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
)
2021-07-17 11:12:17 +08:00
const appVersion = "1.3.3"
2021-01-07 18:32:17 +08:00
func init() {
flag.StringVar(&core.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")
2021-01-07 18:32:17 +08:00
// 改变默认的 Usage
flag.Usage = usage
}
func usage() {
2021-07-17 11:12:17 +08:00
fmt.Fprintf(os.Stderr, "Options:\n")
2021-01-07 18:32:17 +08:00
flag.PrintDefaults()
}
2020-08-04 14:28:25 +08:00
func main() {
2021-03-10 15:12:21 +08:00
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()
2021-02-11 15:18:46 +08:00
pid := strconv.Itoa(os.Getpid())
2021-07-17 11:12:17 +08:00
_ = godotenv.Load(core.GetEnvFile())
_ = ioutil.WriteFile(path.Join(core.GetAssetDir(), "goploy.pid"), []byte(pid), 0755)
2021-01-13 17:12:40 +08:00
println("Start at " + time.Now().String())
2021-02-26 16:45:13 +08:00
println("goploy -h for more help")
println("Current pid: " + pid)
2021-01-13 17:12:40 +08:00
println("Config Loaded: " + core.GetEnvFile())
println("Log: " + os.Getenv("LOG_PATH"))
println("Listen: " + os.Getenv("PORT"))
println("Running...")
2020-08-04 14:28:25 +08:00
core.CreateValidator()
model.Init()
ws.Init()
route.Init()
task.Init()
2021-02-11 15:18:46 +08:00
// server
srv := http.Server{
Addr: ":" + os.Getenv("PORT"),
}
2021-07-17 11:12:17 +08:00
go checkUpdate()
2021-02-12 11:48:36 +08:00
core.Gwg.Add(1)
2021-02-11 15:18:46 +08:00
go func() {
2021-02-12 11:48:36 +08:00
defer core.Gwg.Done()
2021-02-11 15:18:46 +08:00
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
println("Received the signal: " + (<-c).String())
println("Server is trying to shutdown, wait for a minute")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
println("Server shutdown failed, err: %v\n", err)
}
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 {
println("Task shutdown failed, err: %v\n", err)
}
println("Task shutdown gracefully")
2021-02-11 15:18:46 +08:00
}()
2021-02-12 16:21:05 +08:00
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
2021-02-11 15:18:46 +08:00
log.Fatal("ListenAndServe: ", err.Error())
}
2021-07-17 11:12:17 +08:00
_ = os.Remove(path.Join(core.GetAssetDir(), "goploy.pid"))
2021-02-12 11:48:36 +08:00
println("Goroutine is trying to shutdown, wait for a minute")
core.Gwg.Wait()
println("Goroutine shutdown gracefully")
println("Success")
2021-02-11 15:18:46 +08:00
return
2020-08-04 14:28:25 +08:00
}
func install() {
2021-01-07 18:32:17 +08:00
_, err := os.Stat(core.GetEnvFile())
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-01-13 17:12:40 +08:00
println("Installation guide ↓")
2020-09-25 20:05:25 +08:00
var stdout bytes.Buffer
var stderr bytes.Buffer
cmd := exec.Command("rsync", "--version")
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
println(err.Error() + ", detail: " + stderr.String())
panic("Please check if rsync is installed correctly, see https://rsync.samba.org/download.html")
}
git := utils.GIT{}
if err := git.Run("--version"); err != nil {
println(err.Error() + ", detail: " + git.Err.String())
panic("Please check if git is installed correctly, see https://git-scm.com/downloads")
}
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)")
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.")
}
mysqlUser = utils.ClearNewline(mysqlUser)
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.")
}
mysqlPassword = utils.ClearNewline(mysqlPassword)
if len(mysqlPassword) != 0 {
mysqlPassword = ":" + mysqlPassword
}
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.")
}
mysqlHost = utils.ClearNewline(mysqlHost)
if len(mysqlHost) == 0 {
mysqlHost = "127.0.0.1"
}
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.")
}
mysqlPort = utils.ClearNewline(mysqlPort)
if len(mysqlPort) == 0 {
mysqlPort = "3306"
}
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.")
}
logPath = utils.ClearNewline(logPath)
if len(logPath) == 0 {
2021-05-30 20:21:18 +08:00
logPath = "stdout"
2020-08-04 14:28:25 +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.")
}
port = utils.ClearNewline(port)
if len(port) == 0 {
port = "80"
}
2020-08-15 13:38:06 +08:00
println("Start to install the database...")
2020-08-04 14:28:25 +08:00
db, err := sql.Open("mysql", fmt.Sprintf(
"%s%s@tcp(%s:%s)/?charset=utf8mb4,utf8\n",
mysqlUser,
mysqlPassword,
mysqlHost,
mysqlPort))
if err != nil {
panic(err)
}
defer db.Close()
2021-07-17 11:12:17 +08:00
if err := model.ImportSQL(db, "sql/goploy.sql"); err != nil {
2020-08-04 14:28:25 +08:00
panic(err)
}
2020-08-15 13:38:06 +08:00
println("Database installation is complete")
2021-05-19 16:39:33 +08:00
envContent := "# when you edit its value, you need to restart\n"
2020-08-04 14:28:25 +08:00
envContent += "DB_TYPE=mysql\n"
envContent += fmt.Sprintf(
"DB_CONN=%s%s@tcp(%s:%s)/goploy?charset=utf8mb4,utf8\n",
mysqlUser,
mysqlPassword,
mysqlHost,
mysqlPort)
envContent += fmt.Sprintf("SIGN_KEY=%d\n", time.Now().Unix())
envContent += fmt.Sprintf("LOG_PATH=%s\n", logPath)
envContent += "ENV=production\n"
envContent += fmt.Sprintf("PORT=%s\n", port)
2020-08-15 13:38:06 +08:00
println("Start writing configuration file...")
2021-01-07 18:32:17 +08:00
file, err := os.Create(core.GetEnvFile())
2020-08-04 14:28:25 +08:00
if err != nil {
panic(err)
}
defer file.Close()
file.WriteString(envContent)
2020-08-15 13:38:06 +08:00
println("Write configuration file completed")
2020-08-04 14:28:25 +08:00
}
2021-02-11 15:18:46 +08:00
func handleClientSignal() {
switch s {
case "stop":
pidStr, err := ioutil.ReadFile(path.Join(core.GetAssetDir(), "goploy.pid"))
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())
}
os.Exit(1)
}
}
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()
body, err := ioutil.ReadAll(resp.Body)
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
}
tagName := result["tag_name"].(string)
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))
}
}