482 lines
13 KiB
Go
482 lines
13 KiB
Go
package route
|
|
|
|
import (
|
|
"io/ioutil"
|
|
"net/http"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gokins/gokins/util/httpex"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/gokins/core"
|
|
"github.com/gokins/core/common"
|
|
"github.com/gokins/core/utils"
|
|
"github.com/gokins/gokins/comm"
|
|
"github.com/gokins/gokins/migrates"
|
|
"github.com/gokins/gokins/util"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
type installConfig struct {
|
|
Server struct {
|
|
Host string `json:"host"` //外网访问地址
|
|
RunLimit int `json:"runLimit"`
|
|
HbtpHost string `json:"hbtpHost"`
|
|
Secret string `json:"secret"`
|
|
NoRun bool `json:"noRun"`
|
|
} `json:"server"`
|
|
Datasource struct {
|
|
Driver string `json:"driver"`
|
|
Host string `json:"host"`
|
|
Name string `json:"name"`
|
|
User string `json:"user"`
|
|
Pass string `json:"pass"`
|
|
} `json:"datasource"`
|
|
}
|
|
type InstallController struct{}
|
|
|
|
func (InstallController) GetPath() string {
|
|
return "/api/install"
|
|
}
|
|
func (cs *InstallController) auth(c *gin.Context) {
|
|
if comm.Installed {
|
|
c.String(404, "Not Found")
|
|
c.Abort()
|
|
return
|
|
}
|
|
}
|
|
func (cs *InstallController) Routes(g gin.IRoutes) {
|
|
g.Use(cs.auth)
|
|
g.POST("/check", cs.check)
|
|
g.POST("/", util.GinReqParseJson(cs.install))
|
|
}
|
|
func (InstallController) check(c *gin.Context) {
|
|
c.String(200, "hello gokins!")
|
|
}
|
|
func checkUrl(host string) bool {
|
|
req, err := http.NewRequest("POST", host+"/api/install/check", nil)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
cli := http.Client{}
|
|
cli.Timeout = time.Second * 5
|
|
res, err := cli.Do(req)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
defer res.Body.Close()
|
|
return res.StatusCode == 200
|
|
}
|
|
func (InstallController) install(c *gin.Context, m *installConfig) {
|
|
if strings.HasSuffix(m.Server.Host, "/") {
|
|
ln := len(m.Server.Host)
|
|
m.Server.Host = m.Server.Host[:ln-2]
|
|
}
|
|
if !common.RegUrl.MatchString(m.Server.Host) {
|
|
c.String(500, "host err:%s", m.Server.Host)
|
|
return
|
|
}
|
|
if !checkUrl(m.Server.Host) {
|
|
c.String(511, "can't connect:%s", m.Server.Host)
|
|
return
|
|
}
|
|
if m.Server.HbtpHost != "" && !common.RegHost1.MatchString(m.Server.HbtpHost) {
|
|
c.String(500, "hbtp host err:%s", m.Server.HbtpHost)
|
|
return
|
|
}
|
|
if m.Datasource.Driver == "mysql" {
|
|
if !common.RegHost2.MatchString(m.Datasource.Host) {
|
|
c.String(500, "dbhost err:%s", m.Datasource.Host)
|
|
return
|
|
}
|
|
if m.Datasource.Name == "" {
|
|
c.String(500, "dbname err:%s", m.Datasource.Name)
|
|
return
|
|
}
|
|
if strings.Contains(m.Datasource.Name, ":") || strings.Contains(m.Datasource.Pass, ":") {
|
|
c.String(500, "(dbname & dbport) can't contains ':'")
|
|
return
|
|
}
|
|
} else {
|
|
m.Datasource.Driver = "sqlite"
|
|
}
|
|
|
|
dataul := ""
|
|
var err error
|
|
if m.Datasource.Driver == "mysql" {
|
|
_, dataul, err = migrates.InitMysqlMigrate(m.Datasource.Host, m.Datasource.Name, m.Datasource.User, m.Datasource.Pass)
|
|
} else {
|
|
dataul, err = migrates.InitSqliteMigrate()
|
|
}
|
|
if err != nil {
|
|
c.String(512, "%v", err)
|
|
return
|
|
|
|
}
|
|
if dataul == "" {
|
|
c.String(513, "datasource info err")
|
|
return
|
|
}
|
|
|
|
comm.Cfg.Server.Host = m.Server.Host
|
|
comm.Cfg.Server.LoginKey = utils.RandomString(32)
|
|
comm.Cfg.Server.DownToken = utils.RandomString(32)
|
|
comm.Cfg.Server.Shells = []string{"shell@sh", "shell@bash"}
|
|
if runtime.GOOS == "windows" {
|
|
comm.Cfg.Server.Shells = []string{"shell@cmd", "shell@powershell"}
|
|
}
|
|
if m.Server.NoRun {
|
|
comm.Cfg.Server.Shells = nil
|
|
}
|
|
comm.Cfg.Server.HbtpHost = m.Server.HbtpHost
|
|
comm.Cfg.Server.Secret = m.Server.Secret
|
|
comm.Cfg.Datasource.Driver = m.Datasource.Driver
|
|
comm.Cfg.Datasource.Url = dataul
|
|
err = initConfig()
|
|
if err != nil {
|
|
c.String(500, "init config err:%v", err)
|
|
return
|
|
}
|
|
comm.Installed = true
|
|
c.String(200, "ok")
|
|
}
|
|
|
|
func initConfig() error {
|
|
bts, err := yaml.Marshal(&comm.Cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return ioutil.WriteFile(filepath.Join(comm.WorkPath, "app.yml"), bts, 0644)
|
|
}
|
|
|
|
func Install(c *gin.Context) {
|
|
if comm.Installed {
|
|
httpex.ResMsgUrl(c, "重复操作,跳转中...", "/")
|
|
return
|
|
}
|
|
bts := []byte(`
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>安装</title>
|
|
<style>
|
|
.content {
|
|
position: absolute;
|
|
left: 50%;
|
|
left: 50%;
|
|
top: 10%;
|
|
width: 800px;
|
|
margin-left: -400px;
|
|
background: #eee;
|
|
padding: 10px;
|
|
}
|
|
|
|
.content .layui-card div {
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.headers {
|
|
background: #fff;
|
|
padding-bottom: 10px;
|
|
line-height: 70px;
|
|
display: flex;
|
|
}
|
|
|
|
.headers h1 {
|
|
color: #5360f7;
|
|
}
|
|
|
|
.login-dog {
|
|
width: 100px;
|
|
height: 60px;
|
|
margin: 10px 20px 0 0;
|
|
}
|
|
|
|
#msgDiv {
|
|
color: red;
|
|
text-align: center;
|
|
}
|
|
</style>
|
|
<!-- 引入 layui.css -->
|
|
<link rel="stylesheet" href="gokinsui/plugs/layui.css" />
|
|
<!-- 引入 layui.js -->
|
|
</head>
|
|
|
|
<body>
|
|
<div class="content">
|
|
<div class="headers" style="margin: 0;">
|
|
<div style="flex: 1;"></div>
|
|
<img class="login-dog" src="gokinsui/imgs/logo.png" />
|
|
<div style="padding-top: 5px;">
|
|
<h1>安装Gokins</h1>
|
|
</div>
|
|
<div style="flex: 1;"></div>
|
|
</div>
|
|
|
|
<div class="layui-card">
|
|
<div class="layui-card-body">
|
|
<form class="layui-form" action="javascript:onInstal()">
|
|
<div class="layui-form-item">
|
|
<label class="layui-form-label">访问地址</label>
|
|
<div class="layui-input-inline" style="width:300px">
|
|
<input type="text" id="hostTxt" name="txt1" lay-verify="required" placeholder="请输入标题" autocomplete="off"
|
|
class="layui-input">
|
|
</div>
|
|
<div class="layui-form-mid layui-word-aux">需要 webhook,ssh,制品下载 建议使用<span style="color:red">外网</span></div>
|
|
</div>
|
|
<div class="layui-form-item">
|
|
<label class="layui-form-label">插件服务</label>
|
|
<div class="layui-input-inline">
|
|
<select id="plugServ" lay-filter="plugs">
|
|
<option value="">不启用</option>
|
|
<option value="1">内网</option>
|
|
<option value="2">外网</option>
|
|
</select>
|
|
</div>
|
|
<div class="layui-input-inline" style="width:100px">
|
|
<input type="text" id="plugPort" name="txt2" placeholder="服务端口" autocomplete="off" class="layui-input">
|
|
</div>
|
|
<div class="layui-input-inline" style="width:200px">
|
|
<input type="text" id="plugSecret" name="txt3" placeholder="服务Secret" autocomplete="off"
|
|
class="layui-input">
|
|
</div>
|
|
<!-- <div class="layui-form-mid layui-word-aux">差</div> -->
|
|
</div>
|
|
<div class="layui-form-item">
|
|
<label class="layui-form-label">数据库</label>
|
|
<div class="layui-input-inline">
|
|
<select id="dbDriver" disabled="disabled">
|
|
<option value="sqlite">sqlite</option>
|
|
<option value="mysql" selected>mysql</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="layui-form-item">
|
|
<label class="layui-form-label">数据库地址</label>
|
|
<div class="layui-input-inline" style="width:300px">
|
|
<input type="text" id="dbhostTxt" name="txt4" lay-verify="required" value="localhost:3306"
|
|
autocomplete="off" class="layui-input">
|
|
</div>
|
|
<div class="layui-form-mid layui-word-aux">Mysql链接地址</div>
|
|
</div>
|
|
<div class="layui-form-item">
|
|
<label class="layui-form-label">数据库名称</label>
|
|
<div class="layui-input-inline" style="width:200px">
|
|
<input type="text" id="dbnameTxt" name="txt5" lay-verify="required" value="gokins" autocomplete="off"
|
|
class="layui-input">
|
|
</div>
|
|
</div>
|
|
<div class="layui-form-item">
|
|
<label class="layui-form-label">数据库用户</label>
|
|
<div class="layui-input-inline" style="width:200px">
|
|
<input type="text" id="dbuserTxt" name="txt5" required lay-verify="required" value="root"
|
|
autocomplete="off" class="layui-input">
|
|
</div>
|
|
</div>
|
|
<div class="layui-form-item">
|
|
<label class="layui-form-label">数据库密码</label>
|
|
<div class="layui-input-inline" style="width:200px">
|
|
<input type="text" id="dbpassTxt" name="txt6" value="" autocomplete="off" class="layui-input">
|
|
</div>
|
|
</div>
|
|
<div class="layui-form-item">
|
|
<div style="text-align: center;">
|
|
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="formd" id="subBtn">立即安装</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
<div id="msgDiv"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script src="gokinsui/plugs/axios.js"></script>
|
|
<script src="gokinsui/plugs/jquery.js"></script>
|
|
<script src="gokinsui/plugs/layui.js"></script>
|
|
<script>
|
|
var msgDiv = $('#msgDiv');
|
|
var subBtn = $('#subBtn');
|
|
var service = axios.create({
|
|
baseURL: "/api", // api base_url
|
|
// baseURL: 'http://n.1ydt.com:8072', // api base_url
|
|
//timeout: 5000, // 请求超时时间
|
|
withCredentials: true
|
|
});
|
|
|
|
var regul = /^(https?:)\/\/([\w\.]+)(:\d+)?/;
|
|
var reghost = /^([\w\.]+)(:\d+)?$/;
|
|
|
|
function plugChange() {
|
|
switch ($('#plugServ').val()) {
|
|
case '1':
|
|
if($('#plugPort').val()=='')
|
|
$('#plugPort').val('8031');
|
|
$('#plugSecret').val('');
|
|
$('#plugPort').removeAttr('disabled');
|
|
$('#plugSecret').prop('disabled', 'disabled');
|
|
break
|
|
case '2':
|
|
if($('#plugPort').val()=='')
|
|
$('#plugPort').val('8031');
|
|
$('#plugPort').removeAttr('disabled');
|
|
$('#plugSecret').removeAttr('disabled');
|
|
break
|
|
default:
|
|
$('#plugPort').val('');
|
|
$('#plugSecret').val('');
|
|
$('#plugPort').prop('disabled', 'disabled');
|
|
$('#plugSecret').prop('disabled', 'disabled');
|
|
break
|
|
}
|
|
}
|
|
plugChange()
|
|
|
|
/* function dbChange() {
|
|
switch ($('#dbDriver').val()) {
|
|
case 'mysql':
|
|
$('#mysqlDiv').show();
|
|
break
|
|
default:
|
|
$('#mysqlDiv').hide();
|
|
break
|
|
}
|
|
}
|
|
dbChange() */
|
|
|
|
|
|
var lyform = null;
|
|
layui.use('form', function () {
|
|
lyform = layui.form;
|
|
//各种基于事件的操作,下面会有进一步介绍
|
|
|
|
lyform.on('select(plugs)', function (data) {
|
|
plugChange()
|
|
})
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
function onInstal() {
|
|
try {
|
|
var csjs = {
|
|
"server": {
|
|
"host": $('#hostTxt').val()
|
|
},
|
|
"datasource": {
|
|
"driver": ''
|
|
}
|
|
};
|
|
if (!regul.test(csjs.server.host)) {
|
|
layer.msg('访问地址格式错误', { icon: 2 });
|
|
return
|
|
}
|
|
switch ($('#plugServ').val()) {
|
|
case '1':
|
|
var hbtpPort = $('#plugPort').val();
|
|
if (!/^\d+$/.test(hbtpPort)) {
|
|
layer.msg('插件服务端口格式错误', { icon: 2 });
|
|
return
|
|
}
|
|
csjs.server.hbtpHost = '127.0.0.1:' + hbtpPort;
|
|
break
|
|
case '2':
|
|
var hbtpPort = $('#plugPort').val();
|
|
if (!/^\d+$/.test(hbtpPort)) {
|
|
layer.msg('插件服务端口格式错误', { icon: 2 });
|
|
return
|
|
}
|
|
csjs.server.hbtpHost = ':' + hbtpPort;
|
|
csjs.server.secret = $('#plugSecret').val();
|
|
break
|
|
}
|
|
switch ($('#dbDriver').val()) {
|
|
case 'mysql':
|
|
var dburl = '';
|
|
var dbhost = $('#dbhostTxt').val();
|
|
var dbname = $('#dbnameTxt').val();
|
|
var dbuser = $('#dbuserTxt').val();
|
|
var dbpass = $('#dbpassTxt').val();
|
|
if (!reghost.test(dbhost)) {
|
|
layer.msg('数据库地址格式错误', { icon: 2 });
|
|
return
|
|
}
|
|
if (dbname == '') {
|
|
layer.msg('数据库名称必填', { icon: 2 });
|
|
return
|
|
}
|
|
if (dbuser == '') {
|
|
layer.msg('数据库用户必填', { icon: 2 });
|
|
return
|
|
}
|
|
csjs.datasource.driver = 'mysql';
|
|
csjs.datasource.host = dbhost;
|
|
csjs.datasource.name = dbname;
|
|
csjs.datasource.user = dbuser;
|
|
csjs.datasource.pass = dbpass;
|
|
break
|
|
default:
|
|
csjs.datasource.driver = 'sqlite';
|
|
break
|
|
}
|
|
|
|
console.log('start install', csjs);
|
|
subBtn.attr('disabled', 'disabled');
|
|
subBtn.addClass('layui-btn-disabled');
|
|
service.post('/install', csjs).then(function (res) {
|
|
// subBtn.removeAttr('disabled');
|
|
subBtn.value = '跳转中';
|
|
msgDiv.text('安装成功:' + res.data);
|
|
layer.msg('安装成功:' + res.data, { icon: 1 });
|
|
setTimeout(function () {
|
|
window.location = 'gokins';
|
|
}, 1000)
|
|
}).catch(function (err) {
|
|
subBtn.removeAttr('disabled');
|
|
subBtn.removeClass('layui-btn-disabled');
|
|
console.log('install err:', err);
|
|
msgDiv.text('安装失败:' + (err.response ? err.response.data || '服务器错误' : '网络错误'));
|
|
|
|
const stat = err.response ? err.response.status : 0;
|
|
switch (stat) {
|
|
case 404:
|
|
window.location = 'gokins';
|
|
break
|
|
case 511:
|
|
layer.msg('无法连接访问地址,请重试!', { icon: 2 });
|
|
break
|
|
case 512:
|
|
layer.msg('无法初始化数据库,请重试!', { icon: 2 });
|
|
break
|
|
}
|
|
});
|
|
} catch (e) {
|
|
msgDiv.text('安装失败,json错误:' + e);
|
|
}
|
|
}
|
|
|
|
var hrefs = window.location.href;
|
|
if (regul.test(hrefs)) {
|
|
var mts = hrefs.match(regul);
|
|
console.log('match', mts);
|
|
$('#hostTxt').val(mts[0]);
|
|
}
|
|
</script>
|
|
</body>
|
|
|
|
</html>
|
|
`)
|
|
if core.Debug {
|
|
bs, err := ioutil.ReadFile("install.html")
|
|
if err == nil {
|
|
bts = bs
|
|
}
|
|
}
|
|
c.Data(200, "text/html", bts)
|
|
}
|