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
}