mirror of
https://github.com/TeaOSLab/EdgeAdmin.git
synced 2025-11-03 04:10:27 +08:00
增加恢复模式
This commit is contained in:
@@ -8,20 +8,22 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/nodes"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := apps.NewAppCmd().
|
||||
Version(teaconst.Version).
|
||||
Product(teaconst.ProductName).
|
||||
Usage(teaconst.ProcessName+" [-v|start|stop|restart|service|daemon|reset]").
|
||||
Usage(teaconst.ProcessName+" [-v|start|stop|restart|service|daemon|reset|recover]").
|
||||
Option("-h", "show this help").
|
||||
Option("-v", "show version").
|
||||
Option("start", "start the service").
|
||||
Option("stop", "stop the service").
|
||||
Option("service", "register service into systemd").
|
||||
Option("daemon", "start the service with daemon").
|
||||
Option("reset", "reset configs")
|
||||
Option("reset", "reset configs").
|
||||
Option("recover", "enter recovery mode")
|
||||
|
||||
app.On("daemon", func() {
|
||||
nodes.NewAdminNode().Daemon()
|
||||
@@ -42,6 +44,19 @@ func main() {
|
||||
}
|
||||
fmt.Println("done")
|
||||
})
|
||||
app.On("recover", func() {
|
||||
sock := gosock.NewTmpSock(teaconst.ProcessName)
|
||||
if !sock.IsListening() {
|
||||
fmt.Println("[ERROR]the service not started yet, you should start the service first")
|
||||
return
|
||||
}
|
||||
_, err := sock.Send(&gosock.Command{Code: "recover"})
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]enter recovery mode failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
fmt.Println("enter recovery mode successfully")
|
||||
})
|
||||
app.Run(func() {
|
||||
adminNode := nodes.NewAdminNode()
|
||||
adminNode.Run()
|
||||
|
||||
5
go.mod
5
go.mod
@@ -9,11 +9,11 @@ require (
|
||||
github.com/cespare/xxhash v1.1.0
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/protobuf v1.5.2
|
||||
github.com/google/go-cmp v0.5.6 // indirect
|
||||
github.com/iwind/TeaGo v0.0.0-20210720011303-fc255c995afa
|
||||
github.com/iwind/gosock v0.0.0-20210720054405-4030f271aa3f
|
||||
github.com/miekg/dns v1.1.35
|
||||
github.com/shirou/gopsutil v3.21.5+incompatible // indirect
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/tealeg/xlsx/v3 v3.2.3
|
||||
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119
|
||||
@@ -21,6 +21,5 @@ require (
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22
|
||||
google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced // indirect
|
||||
google.golang.org/grpc v1.38.0
|
||||
google.golang.org/protobuf v1.26.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
|
||||
)
|
||||
|
||||
25
go.sum
25
go.sum
@@ -44,7 +44,6 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
@@ -55,7 +54,6 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
@@ -63,13 +61,13 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/iwind/TeaGo v0.0.0-20210411134150-ddf57e240c2f h1:r2O8PONj/KiuZjJHVHn7KlCePUIjNtgAmvLfgRafQ8o=
|
||||
github.com/iwind/TeaGo v0.0.0-20210411134150-ddf57e240c2f/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
|
||||
github.com/iwind/TeaGo v0.0.0-20210628135026-38575a4ab060 h1:qdLtK4PDXxk2vMKkTWl5Fl9xqYuRCukzWAgJbLHdfOo=
|
||||
github.com/iwind/TeaGo v0.0.0-20210628135026-38575a4ab060/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
|
||||
github.com/iwind/TeaGo v0.0.0-20210720011303-fc255c995afa h1:woN88uEmRRUNFD7pRZEtX9heDcjFn0ClMxjF5ButKow=
|
||||
github.com/iwind/TeaGo v0.0.0-20210720011303-fc255c995afa/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
|
||||
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
||||
github.com/iwind/gosock v0.0.0-20210706152215-08fe64b7a8a3 h1:BVHIjJU5RUX0grmvDtr3ZZ2z0hX+ideJRpg4NtBXZfs=
|
||||
github.com/iwind/gosock v0.0.0-20210706152215-08fe64b7a8a3/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
|
||||
github.com/iwind/gosock v0.0.0-20210720054405-4030f271aa3f h1:+mKLTd5tCCLXK+iY5Xjz58hau6qFv9chGqcZ4FWUiIs=
|
||||
github.com/iwind/gosock v0.0.0-20210720054405-4030f271aa3f/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
@@ -80,11 +78,9 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs=
|
||||
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
@@ -110,8 +106,6 @@ github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa h1:2cO3RojjYl3hVTbEvJVqrMaFmORhL6O06qdW42toftk=
|
||||
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa/go.mod h1:Yjr3bdWaVWyME1kha7X0jsz3k2DgXNa1Pj3XGyUAbx8=
|
||||
github.com/shirou/gopsutil v3.21.5+incompatible h1:OloQyEerMi7JUrXiNzy8wQ5XN+baemxSl12QgIzt0jc=
|
||||
github.com/shirou/gopsutil v3.21.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
|
||||
@@ -151,7 +145,6 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
|
||||
@@ -159,8 +152,8 @@ golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -175,8 +168,6 @@ golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -185,7 +176,6 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7s
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -203,15 +193,14 @@ golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapK
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced h1:c5geK1iMU3cDKtFrCVQIcjR3W+JOZMuhIyICMCTbtus=
|
||||
google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
|
||||
@@ -220,7 +209,6 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=
|
||||
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
@@ -232,7 +220,6 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||
|
||||
@@ -2,17 +2,19 @@ package apps
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// App命令帮助
|
||||
// AppCmd App命令帮助
|
||||
type AppCmd struct {
|
||||
product string
|
||||
version string
|
||||
@@ -21,10 +23,14 @@ type AppCmd struct {
|
||||
appendStrings []string
|
||||
|
||||
directives []*Directive
|
||||
|
||||
sock *gosock.Sock
|
||||
}
|
||||
|
||||
func NewAppCmd() *AppCmd {
|
||||
return &AppCmd{}
|
||||
return &AppCmd{
|
||||
sock: gosock.NewTmpSock(teaconst.ProcessName),
|
||||
}
|
||||
}
|
||||
|
||||
type CommandHelpOption struct {
|
||||
@@ -32,25 +38,25 @@ type CommandHelpOption struct {
|
||||
Description string
|
||||
}
|
||||
|
||||
// 产品
|
||||
// Product 产品
|
||||
func (this *AppCmd) Product(product string) *AppCmd {
|
||||
this.product = product
|
||||
return this
|
||||
}
|
||||
|
||||
// 版本
|
||||
// Version 版本
|
||||
func (this *AppCmd) Version(version string) *AppCmd {
|
||||
this.version = version
|
||||
return this
|
||||
}
|
||||
|
||||
// 使用方法
|
||||
// Usage 使用方法
|
||||
func (this *AppCmd) Usage(usage string) *AppCmd {
|
||||
this.usage = usage
|
||||
return this
|
||||
}
|
||||
|
||||
// 选项
|
||||
// Option 选项
|
||||
func (this *AppCmd) Option(code string, description string) *AppCmd {
|
||||
this.options = append(this.options, &CommandHelpOption{
|
||||
Code: code,
|
||||
@@ -59,13 +65,13 @@ func (this *AppCmd) Option(code string, description string) *AppCmd {
|
||||
return this
|
||||
}
|
||||
|
||||
// 附加内容
|
||||
// Append 附加内容
|
||||
func (this *AppCmd) Append(appendString string) *AppCmd {
|
||||
this.appendStrings = append(this.appendStrings, appendString)
|
||||
return this
|
||||
}
|
||||
|
||||
// 打印
|
||||
// Print 打印
|
||||
func (this *AppCmd) Print() {
|
||||
fmt.Println(this.product + " v" + this.version)
|
||||
|
||||
@@ -104,7 +110,7 @@ func (this *AppCmd) Print() {
|
||||
}
|
||||
}
|
||||
|
||||
// 添加指令
|
||||
// On 添加指令
|
||||
func (this *AppCmd) On(arg string, callback func()) {
|
||||
this.directives = append(this.directives, &Directive{
|
||||
Arg: arg,
|
||||
@@ -112,7 +118,7 @@ func (this *AppCmd) On(arg string, callback func()) {
|
||||
})
|
||||
}
|
||||
|
||||
// 运行
|
||||
// Run 运行
|
||||
func (this *AppCmd) Run(main func()) {
|
||||
// 获取参数
|
||||
args := os.Args[1:]
|
||||
@@ -151,9 +157,6 @@ func (this *AppCmd) Run(main func()) {
|
||||
return
|
||||
}
|
||||
|
||||
// 记录PID
|
||||
_ = this.writePid()
|
||||
|
||||
// 日志
|
||||
writer := new(LogWriter)
|
||||
writer.Init()
|
||||
@@ -175,9 +178,9 @@ func (this *AppCmd) runHelp() {
|
||||
|
||||
// 启动
|
||||
func (this *AppCmd) runStart() {
|
||||
proc := this.checkPid()
|
||||
if proc != nil {
|
||||
fmt.Println(this.product+" already started, pid:", proc.Pid)
|
||||
var pid = this.getPID()
|
||||
if pid > 0 {
|
||||
fmt.Println(this.product+" already started, pid:", pid)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -193,18 +196,15 @@ func (this *AppCmd) runStart() {
|
||||
|
||||
// 停止
|
||||
func (this *AppCmd) runStop() {
|
||||
proc := this.checkPid()
|
||||
if proc == nil {
|
||||
var pid = this.getPID()
|
||||
if pid == 0 {
|
||||
fmt.Println(this.product + " not started yet")
|
||||
return
|
||||
}
|
||||
|
||||
// 停止进程
|
||||
_ = proc.Signal(syscall.SIGQUIT)
|
||||
_, _ = this.sock.Send(&gosock.Command{Code: "stop"})
|
||||
|
||||
// 在Windows上经常不能及时释放资源
|
||||
_ = DeletePid(Tea.Root + "/bin/pid")
|
||||
fmt.Println(this.product+" stopped ok, pid:", proc.Pid)
|
||||
fmt.Println(this.product+" stopped ok, pid:", types.String(pid))
|
||||
}
|
||||
|
||||
// 重启
|
||||
@@ -216,20 +216,24 @@ func (this *AppCmd) runRestart() {
|
||||
|
||||
// 状态
|
||||
func (this *AppCmd) runStatus() {
|
||||
proc := this.checkPid()
|
||||
if proc == nil {
|
||||
var pid = this.getPID()
|
||||
if pid == 0 {
|
||||
fmt.Println(this.product + " not started yet")
|
||||
} else {
|
||||
fmt.Println(this.product + " is running, pid: " + fmt.Sprintf("%d", proc.Pid))
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(this.product + " is running, pid: " + types.String(pid))
|
||||
}
|
||||
|
||||
// 检查PID
|
||||
func (this *AppCmd) checkPid() *os.Process {
|
||||
return CheckPid(Tea.Root + "/bin/pid")
|
||||
}
|
||||
// 获取当前的PID
|
||||
func (this *AppCmd) getPID() int {
|
||||
if !this.sock.IsListening() {
|
||||
return 0
|
||||
}
|
||||
|
||||
// 写入PID
|
||||
func (this *AppCmd) writePid() error {
|
||||
return WritePid(Tea.Root + "/bin/pid")
|
||||
reply, err := this.sock.Send(&gosock.Command{Code: "pid"})
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return maps.NewMap(reply.Params).GetInt("pid")
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package apps
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// lock file
|
||||
func LockFile(fp *os.File) error {
|
||||
return syscall.Flock(int(fp.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
|
||||
}
|
||||
|
||||
func UnlockFile(fp *os.File) error {
|
||||
return syscall.Flock(int(fp.Fd()), syscall.LOCK_UN)
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
// +build windows
|
||||
|
||||
package apps
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
)
|
||||
|
||||
// lock file
|
||||
func LockFile(fp *os.File) error {
|
||||
return errors.New("not implemented on windows")
|
||||
}
|
||||
|
||||
func UnlockFile(fp *os.File) error {
|
||||
return errors.New("not implemented on windows")
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var pidFileList = []*os.File{}
|
||||
|
||||
// 检查Pid
|
||||
func CheckPid(path string) *os.Process {
|
||||
// windows上打开的文件是不能删除的
|
||||
if runtime.GOOS == "windows" {
|
||||
if os.Remove(path) == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
}()
|
||||
|
||||
// 是否能取得Lock
|
||||
err = LockFile(file)
|
||||
if err == nil {
|
||||
_ = UnlockFile(file)
|
||||
return nil
|
||||
}
|
||||
|
||||
pidBytes, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
pid := types.Int(string(pidBytes))
|
||||
|
||||
if pid <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
proc, _ := os.FindProcess(pid)
|
||||
return proc
|
||||
}
|
||||
|
||||
// 写入Pid
|
||||
func WritePid(path string) error {
|
||||
fp, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_RDONLY, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
err = LockFile(fp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
pidFileList = append(pidFileList, fp) // hold the file pointers
|
||||
|
||||
_, err = fp.WriteString(fmt.Sprintf("%d", os.Getpid()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 写入Ppid
|
||||
func WritePpid(path string) error {
|
||||
fp, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_RDONLY, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
err = LockFile(fp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
pidFileList = append(pidFileList, fp) // hold the file pointers
|
||||
|
||||
_, err = fp.WriteString(fmt.Sprintf("%d", os.Getppid()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 删除Pid
|
||||
func DeletePid(path string) error {
|
||||
_, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
for _, fp := range pidFileList {
|
||||
_ = UnlockFile(fp)
|
||||
_ = fp.Close()
|
||||
}
|
||||
return os.Remove(path)
|
||||
}
|
||||
@@ -108,7 +108,6 @@ func (this *APIConfig) WriteFile(path string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(path, data, 0666)
|
||||
|
||||
// 写入 ~/ 和 /etc/ 目录,因为是备份需要,所以不需要提示错误信息
|
||||
// 写入 ~/.edge-admin/
|
||||
@@ -141,5 +140,10 @@ func (this *APIConfig) WriteFile(path string) error {
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
err = ioutil.WriteFile(path, data, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
7
internal/const/vars.go
Normal file
7
internal/const/vars.go
Normal file
@@ -0,0 +1,7 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package teaconst
|
||||
|
||||
var (
|
||||
IsRecoverMode = false
|
||||
)
|
||||
@@ -10,11 +10,12 @@ import (
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/sessions"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
@@ -25,6 +26,7 @@ import (
|
||||
var SharedAdminNode *AdminNode = nil
|
||||
|
||||
type AdminNode struct {
|
||||
sock *gosock.Sock
|
||||
subPIDs []int
|
||||
}
|
||||
|
||||
@@ -42,7 +44,7 @@ func (this *AdminNode) Run() {
|
||||
// 本地Sock
|
||||
err := this.listenSock()
|
||||
if err != nil {
|
||||
logs.Println("NODE" + err.Error())
|
||||
logs.Println("[NODE]", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -89,11 +91,11 @@ func (this *AdminNode) Run() {
|
||||
|
||||
// Daemon 实现守护进程
|
||||
func (this *AdminNode) Daemon() {
|
||||
path := os.TempDir() + "/edge-admin.sock"
|
||||
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
isDebug := lists.ContainsString(os.Args, "debug")
|
||||
isDebug = true
|
||||
for {
|
||||
conn, err := net.DialTimeout("unix", path, 1*time.Second)
|
||||
conn, err := sock.Dial()
|
||||
if err != nil {
|
||||
if isDebug {
|
||||
log.Println("[DAEMON]starting ...")
|
||||
@@ -281,37 +283,59 @@ func (this *AdminNode) genSecret() string {
|
||||
|
||||
// 监听本地sock
|
||||
func (this *AdminNode) listenSock() error {
|
||||
path := os.TempDir() + "/edge-admin.sock"
|
||||
this.sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
|
||||
// 检查是否已经存在
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
conn, err := net.Dial("unix", path)
|
||||
if err != nil {
|
||||
_ = os.Remove(path)
|
||||
// 检查是否在运行
|
||||
if this.sock.IsListening() {
|
||||
reply, err := this.sock.Send(&gosock.Command{Code: "pid"})
|
||||
if err == nil {
|
||||
return errors.New("error: the process is already running, pid: " + maps.NewMap(reply.Params).GetString("pid"))
|
||||
} else {
|
||||
_ = conn.Close()
|
||||
return errors.New("error: the process is already running")
|
||||
}
|
||||
}
|
||||
|
||||
// 新的监听任务
|
||||
listener, err := net.Listen("unix", path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
events.On(events.EventQuit, func() {
|
||||
logs.Println("NODE", "quit unix sock")
|
||||
_ = listener.Close()
|
||||
})
|
||||
|
||||
// 启动监听
|
||||
go func() {
|
||||
for {
|
||||
_, err := listener.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
this.sock.OnCommand(func(cmd *gosock.Command) {
|
||||
switch cmd.Code {
|
||||
case "pid":
|
||||
_ = cmd.Reply(&gosock.Command{
|
||||
Code: "pid",
|
||||
Params: map[string]interface{}{
|
||||
"pid": os.Getpid(),
|
||||
},
|
||||
})
|
||||
case "stop":
|
||||
_ = cmd.ReplyOk()
|
||||
|
||||
// 关闭子进程
|
||||
for _, pid := range this.subPIDs {
|
||||
p, err := os.FindProcess(pid)
|
||||
if err == nil && p != nil {
|
||||
_ = p.Kill()
|
||||
}
|
||||
}
|
||||
|
||||
// 退出主进程
|
||||
events.Notify(events.EventQuit)
|
||||
os.Exit(0)
|
||||
case "recover":
|
||||
teaconst.IsRecoverMode = true
|
||||
_ = cmd.ReplyOk()
|
||||
}
|
||||
})
|
||||
|
||||
err := this.sock.Listen()
|
||||
if err != nil {
|
||||
logs.Println("NODE", err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
events.On(events.EventQuit, func() {
|
||||
logs.Println("NODE", "quit unix sock")
|
||||
_ = this.sock.Close()
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -51,6 +51,10 @@ func NewRPCClient(apiConfig *configs.APIConfig) (*RPCClient, error) {
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (this *RPCClient) APITokenRPC() pb.APITokenServiceClient {
|
||||
return pb.NewAPITokenServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) AdminRPC() pb.AdminServiceClient {
|
||||
return pb.NewAdminServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package tasks
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/setup"
|
||||
@@ -44,7 +45,7 @@ func (this *SyncAPINodesTask) Start() {
|
||||
|
||||
func (this *SyncAPINodesTask) Loop() error {
|
||||
// 如果还没有安装直接返回
|
||||
if !setup.IsConfigured() {
|
||||
if !setup.IsConfigured() || teaconst.IsRecoverMode {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/setup"
|
||||
@@ -40,7 +41,7 @@ func (this *SyncClusterTask) Start() {
|
||||
|
||||
func (this *SyncClusterTask) loop() error {
|
||||
// 如果还没有安装直接返回
|
||||
if !setup.IsConfigured() {
|
||||
if !setup.IsConfigured() || teaconst.IsRecoverMode {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
17
internal/web/actions/default/recover/helper.go
Normal file
17
internal/web/actions/default/recover/helper.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package recover
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
)
|
||||
|
||||
type Helper struct {
|
||||
}
|
||||
|
||||
func (this *Helper) BeforeAction(actionPtr actions.ActionWrapper) (goNext bool) {
|
||||
if !teaconst.IsRecoverMode {
|
||||
actionPtr.Object().RedirectURL("/")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
17
internal/web/actions/default/recover/index.go
Normal file
17
internal/web/actions/default/recover/index.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package recover
|
||||
|
||||
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct{}) {
|
||||
this.Show()
|
||||
}
|
||||
15
internal/web/actions/default/recover/init.go
Normal file
15
internal/web/actions/default/recover/init.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package recover
|
||||
|
||||
import "github.com/iwind/TeaGo"
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(new(Helper)).
|
||||
Prefix("/recover").
|
||||
Get("", new(IndexAction)).
|
||||
Post("/validateApi", new(ValidateApiAction)).
|
||||
Post("/updateHosts", new(UpdateHostsAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
192
internal/web/actions/default/recover/updateHosts.go
Normal file
192
internal/web/actions/default/recover/updateHosts.go
Normal file
@@ -0,0 +1,192 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package recover
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
)
|
||||
|
||||
type UpdateHostsAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *UpdateHostsAction) RunPost(params struct {
|
||||
Protocol string
|
||||
Host string
|
||||
Port string
|
||||
NodeId string
|
||||
NodeSecret string
|
||||
|
||||
OldHosts []string
|
||||
NewHosts []string
|
||||
}) {
|
||||
if len(params.OldHosts) != len(params.NewHosts) {
|
||||
this.Fail("参数配置错误,请刷新页面后重试")
|
||||
}
|
||||
|
||||
client, err := rpc.NewRPCClient(&configs.APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
}{
|
||||
Endpoints: []string{params.Protocol + "://" + configutils.QuoteIP(params.Host) + ":" + params.Port},
|
||||
},
|
||||
NodeId: params.NodeId,
|
||||
Secret: params.NodeSecret,
|
||||
})
|
||||
if err != nil {
|
||||
this.FailField("host", "测试API节点时出错,请检查配置,错误信息:"+err.Error())
|
||||
}
|
||||
_, err = client.APINodeRPC().FindCurrentAPINodeVersion(client.APIContext(0), &pb.FindCurrentAPINodeVersionRequest{})
|
||||
if err != nil {
|
||||
this.FailField("host", "无法连接此API节点,错误信息:"+err.Error())
|
||||
}
|
||||
|
||||
// 获取管理员节点信息
|
||||
apiTokensResp, err := client.APITokenRPC().FindAllEnabledAPITokens(client.APIContext(0), &pb.FindAllEnabledAPITokensRequest{Role: "admin"})
|
||||
if err != nil {
|
||||
this.Fail("读取管理员令牌失败:" + err.Error())
|
||||
}
|
||||
|
||||
var apiTokens = apiTokensResp.ApiTokens
|
||||
if len(apiTokens) == 0 {
|
||||
this.Fail("数据库中没有管理员令牌信息,请确认数据是否完整")
|
||||
}
|
||||
var adminAPIToken = apiTokens[0]
|
||||
|
||||
// API节点列表
|
||||
nodesResp, err := client.APINodeRPC().FindAllEnabledAPINodes(client.Context(0), &pb.FindAllEnabledAPINodesRequest{})
|
||||
if err != nil {
|
||||
this.Fail("获取API节点列表失败,错误信息:" + err.Error())
|
||||
}
|
||||
var endpoints = []string{}
|
||||
for _, node := range nodesResp.Nodes {
|
||||
if !node.IsOn {
|
||||
continue
|
||||
}
|
||||
|
||||
// http
|
||||
if len(node.HttpJSON) > 0 {
|
||||
for index, oldHost := range params.OldHosts {
|
||||
if len(params.NewHosts[index]) == 0 {
|
||||
continue
|
||||
}
|
||||
node.HttpJSON = bytes.ReplaceAll(node.HttpJSON, []byte("\""+oldHost+"\""), []byte("\""+params.NewHosts[index]+"\""))
|
||||
}
|
||||
}
|
||||
|
||||
// https
|
||||
if len(node.HttpsJSON) > 0 {
|
||||
for index, oldHost := range params.OldHosts {
|
||||
if len(params.NewHosts[index]) == 0 {
|
||||
continue
|
||||
}
|
||||
node.HttpsJSON = bytes.ReplaceAll(node.HttpsJSON, []byte("\""+oldHost+"\""), []byte("\""+params.NewHosts[index]+"\""))
|
||||
}
|
||||
}
|
||||
|
||||
// restHTTP
|
||||
if len(node.RestHTTPJSON) > 0 {
|
||||
for index, oldHost := range params.OldHosts {
|
||||
if len(params.NewHosts[index]) == 0 {
|
||||
continue
|
||||
}
|
||||
node.RestHTTPJSON = bytes.ReplaceAll(node.RestHTTPJSON, []byte("\""+oldHost+"\""), []byte("\""+params.NewHosts[index]+"\""))
|
||||
}
|
||||
}
|
||||
|
||||
// restHTTPS
|
||||
if len(node.RestHTTPSJSON) > 0 {
|
||||
for index, oldHost := range params.OldHosts {
|
||||
if len(params.NewHosts[index]) == 0 {
|
||||
continue
|
||||
}
|
||||
node.RestHTTPSJSON = bytes.ReplaceAll(node.RestHTTPSJSON, []byte("\""+oldHost+"\""), []byte("\""+params.NewHosts[index]+"\""))
|
||||
}
|
||||
}
|
||||
|
||||
// access addrs
|
||||
if len(node.AccessAddrsJSON) > 0 {
|
||||
for index, oldHost := range params.OldHosts {
|
||||
if len(params.NewHosts[index]) == 0 {
|
||||
continue
|
||||
}
|
||||
node.AccessAddrsJSON = bytes.ReplaceAll(node.AccessAddrsJSON, []byte("\""+oldHost+"\""), []byte("\""+params.NewHosts[index]+"\""))
|
||||
}
|
||||
|
||||
var addrs []*serverconfigs.NetworkAddressConfig
|
||||
err = json.Unmarshal(node.AccessAddrsJSON, &addrs)
|
||||
if err != nil {
|
||||
this.Fail("读取节点访问地址失败:" + err.Error())
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
err = addr.Init()
|
||||
if err != nil {
|
||||
// 暂时不提示错误
|
||||
continue
|
||||
}
|
||||
for _, a := range addr.FullAddresses() {
|
||||
if !lists.ContainsString(endpoints, a) {
|
||||
endpoints = append(endpoints, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 保存
|
||||
_, err = client.APINodeRPC().UpdateAPINode(client.Context(0), &pb.UpdateAPINodeRequest{
|
||||
NodeId: node.Id,
|
||||
Name: node.Name,
|
||||
Description: node.Description,
|
||||
HttpJSON: node.HttpJSON,
|
||||
HttpsJSON: node.HttpsJSON,
|
||||
AccessAddrsJSON: node.AccessAddrsJSON,
|
||||
IsOn: node.IsOn,
|
||||
RestIsOn: node.RestIsOn,
|
||||
RestHTTPJSON: node.RestHTTPJSON,
|
||||
RestHTTPSJSON: node.RestHTTPSJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.Fail("保存API节点信息失败:" + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// 修改api.yaml
|
||||
var apiConfig = &configs.APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
}{
|
||||
Endpoints: endpoints,
|
||||
},
|
||||
NodeId: adminAPIToken.NodeId,
|
||||
Secret: adminAPIToken.Secret,
|
||||
}
|
||||
err = apiConfig.WriteFile(Tea.Root + "/configs/api.yaml")
|
||||
if err != nil {
|
||||
this.Fail("保存configs/api.yaml失败:" + err.Error())
|
||||
}
|
||||
|
||||
// 加载api.yaml
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
this.Fail("初始化RPC失败:" + err.Error())
|
||||
}
|
||||
err = rpcClient.UpdateConfig(apiConfig)
|
||||
if err != nil {
|
||||
this.Fail("修改API配置失败:" + err.Error())
|
||||
}
|
||||
|
||||
// 退出恢复模式
|
||||
teaconst.IsRecoverMode = false
|
||||
|
||||
this.Success()
|
||||
}
|
||||
153
internal/web/actions/default/recover/validateApi.go
Normal file
153
internal/web/actions/default/recover/validateApi.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package recover
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ValidateApiAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *ValidateApiAction) RunPost(params struct {
|
||||
Protocol string
|
||||
Host string
|
||||
Port string
|
||||
NodeId string
|
||||
NodeSecret string
|
||||
|
||||
Must *actions.Must
|
||||
}) {
|
||||
params.NodeId = strings.Trim(params.NodeId, "\"' ")
|
||||
params.NodeSecret = strings.Trim(params.NodeSecret, "\"' ")
|
||||
|
||||
// 使用已有的API节点
|
||||
params.Must.
|
||||
Field("host", params.Host).
|
||||
Require("请输入主机地址").
|
||||
Field("port", params.Port).
|
||||
Require("请输入服务端口").
|
||||
Match(`^\d+$`, "服务端口只能是数字").
|
||||
Field("nodeId", params.NodeId).
|
||||
Require("请输入节点nodeId").
|
||||
Field("nodeSecret", params.NodeSecret).
|
||||
Require("请输入节点secret")
|
||||
client, err := rpc.NewRPCClient(&configs.APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
}{
|
||||
Endpoints: []string{params.Protocol + "://" + configutils.QuoteIP(params.Host) + ":" + params.Port},
|
||||
},
|
||||
NodeId: params.NodeId,
|
||||
Secret: params.NodeSecret,
|
||||
})
|
||||
if err != nil {
|
||||
this.FailField("host", "测试API节点时出错,请检查配置,错误信息:"+err.Error())
|
||||
}
|
||||
_, err = client.APINodeRPC().FindCurrentAPINodeVersion(client.APIContext(0), &pb.FindCurrentAPINodeVersionRequest{})
|
||||
if err != nil {
|
||||
this.FailField("host", "无法连接此API节点,错误信息:"+err.Error())
|
||||
}
|
||||
|
||||
// API节点列表
|
||||
nodesResp, err := client.APINodeRPC().FindAllEnabledAPINodes(client.Context(0), &pb.FindAllEnabledAPINodesRequest{})
|
||||
if err != nil {
|
||||
this.Fail("获取API节点列表失败,错误信息:" + err.Error())
|
||||
}
|
||||
var hosts = []string{}
|
||||
for _, node := range nodesResp.Nodes {
|
||||
if !node.IsOn {
|
||||
continue
|
||||
}
|
||||
|
||||
// http
|
||||
if len(node.HttpJSON) > 0 {
|
||||
var config = &serverconfigs.HTTPProtocolConfig{}
|
||||
err = json.Unmarshal(node.HttpJSON, config)
|
||||
if err != nil {
|
||||
this.Fail("读取节点HTTP信息失败:" + err.Error())
|
||||
}
|
||||
for _, listen := range config.Listen {
|
||||
if len(listen.Host) > 0 && !lists.ContainsString(hosts, listen.Host) {
|
||||
hosts = append(hosts, listen.Host)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https
|
||||
if len(node.HttpsJSON) > 0 {
|
||||
var config = &serverconfigs.HTTPSProtocolConfig{}
|
||||
err = json.Unmarshal(node.HttpsJSON, config)
|
||||
if err != nil {
|
||||
this.Fail("读取节点HTTPS信息失败:" + err.Error())
|
||||
}
|
||||
for _, listen := range config.Listen {
|
||||
if len(listen.Host) > 0 && !lists.ContainsString(hosts, listen.Host) {
|
||||
hosts = append(hosts, listen.Host)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// restHTTP
|
||||
if len(node.RestHTTPJSON) > 0 {
|
||||
var config = &serverconfigs.HTTPProtocolConfig{}
|
||||
err = json.Unmarshal(node.RestHTTPJSON, config)
|
||||
if err != nil {
|
||||
this.Fail("读取节点REST HTTP信息失败:" + err.Error())
|
||||
}
|
||||
for _, listen := range config.Listen {
|
||||
if len(listen.Host) > 0 && !lists.ContainsString(hosts, listen.Host) {
|
||||
hosts = append(hosts, listen.Host)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// restHTTPS
|
||||
if len(node.RestHTTPSJSON) > 0 {
|
||||
var config = &serverconfigs.HTTPSProtocolConfig{}
|
||||
err = json.Unmarshal(node.RestHTTPSJSON, config)
|
||||
if err != nil {
|
||||
this.Fail("读取节点REST HTTPS信息失败:" + err.Error())
|
||||
}
|
||||
for _, listen := range config.Listen {
|
||||
if len(listen.Host) > 0 && !lists.ContainsString(hosts, listen.Host) {
|
||||
hosts = append(hosts, listen.Host)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// access addrs
|
||||
if len(node.AccessAddrsJSON) > 0 {
|
||||
var addrs []*serverconfigs.NetworkAddressConfig
|
||||
err = json.Unmarshal(node.AccessAddrsJSON, &addrs)
|
||||
if err != nil {
|
||||
this.Fail("读取节点访问地址失败:" + err.Error())
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
if len(addr.Host) > 0 && !lists.ContainsString(hosts, addr.Host) {
|
||||
hosts = append(hosts, addr.Host)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.Data["apiNode"] = maps.Map{
|
||||
"protocol": params.Protocol,
|
||||
"host": params.Host,
|
||||
"port": params.Port,
|
||||
"nodeId": params.NodeId,
|
||||
"nodeSecret": params.NodeSecret,
|
||||
"hosts": hosts,
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -3,8 +3,7 @@ package setup
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"net"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
@@ -16,31 +15,18 @@ func (this *IndexAction) Init() {
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct{}) {
|
||||
// 当前服务器的IP
|
||||
serverIPs := []string{}
|
||||
addrs, _ := net.InterfaceAddrs()
|
||||
for _, addr := range addrs {
|
||||
netAddr, ok := addr.(*net.IPNet)
|
||||
if !ok {
|
||||
continue
|
||||
var currentHost = this.Request.Host
|
||||
if strings.Contains(this.Request.Host, ":") {
|
||||
host, _, err := net.SplitHostPort(this.Request.Host)
|
||||
if err == nil {
|
||||
currentHost = host
|
||||
}
|
||||
|
||||
serverIPs = append(serverIPs, netAddr.IP.String())
|
||||
}
|
||||
|
||||
// 对IP进行排序,我们希望IPv4排在前面,而且希望127.0.0.1排在IPv4里的最后
|
||||
sort.Slice(serverIPs, func(i, j int) bool {
|
||||
ip1 := serverIPs[i]
|
||||
|
||||
if ip1 == "127.0.0.1" {
|
||||
return false
|
||||
}
|
||||
if regexp.MustCompile(`^\d+\.\d+\.\d+.\d+$`).MatchString(ip1) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
this.Data["serverIPs"] = serverIPs
|
||||
if net.ParseIP(currentHost) != nil {
|
||||
this.Data["currentHost"] = currentHost
|
||||
} else {
|
||||
this.Data["currentHost"] = ""
|
||||
}
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/nodes"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/go-yaml/yaml"
|
||||
@@ -68,14 +69,14 @@ func (this *InstallAction) RunPost(params struct {
|
||||
_, err = os.Stat(apiNodeDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
this.Fail("在当前目录下找不到" + dir + "目录,请将" + dir + "目录上传或者重新下载解压")
|
||||
this.Fail("在当前目录(" + Tea.Root + ")下找不到" + dir + "目录,请将" + dir + "目录上传或者重新下载解压")
|
||||
}
|
||||
this.Fail("无法检查" + dir + "目录,发生错误:" + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// 保存数据库配置
|
||||
dsn := dbMap.GetString("username") + ":" + dbMap.GetString("password") + "@tcp(" + dbMap.GetString("host") + ":" + dbMap.GetString("port") + ")/" + dbMap.GetString("database") + "?charset=utf8mb4&timeout=30s"
|
||||
dsn := dbMap.GetString("username") + ":" + dbMap.GetString("password") + "@tcp(" + configutils.QuoteIP(dbMap.GetString("host")) + ":" + dbMap.GetString("port") + ")/" + dbMap.GetString("database") + "?charset=utf8mb4&timeout=30s"
|
||||
dbConfig := &dbs.Config{
|
||||
DBs: map[string]*dbs.DBConfig{
|
||||
"prod": {
|
||||
@@ -175,7 +176,7 @@ func (this *InstallAction) RunPost(params struct {
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
}{
|
||||
Endpoints: []string{"http://" + apiNodeMap.GetString("newHost") + ":" + apiNodeMap.GetString("newPort")},
|
||||
Endpoints: []string{"http://" + configutils.QuoteIP(apiNodeMap.GetString("newHost")) + ":" + apiNodeMap.GetString("newPort")},
|
||||
},
|
||||
NodeId: resultMap.GetString("adminNodeId"),
|
||||
Secret: resultMap.GetString("adminNodeSecret"),
|
||||
@@ -233,7 +234,7 @@ func (this *InstallAction) RunPost(params struct {
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
}{
|
||||
Endpoints: []string{apiNodeMap.GetString("oldProtocol") + "://" + apiNodeMap.GetString("oldHost") + ":" + apiNodeMap.GetString("oldPort")},
|
||||
Endpoints: []string{apiNodeMap.GetString("oldProtocol") + "://" + configutils.QuoteIP(apiNodeMap.GetString("oldHost")) + ":" + apiNodeMap.GetString("oldPort")},
|
||||
},
|
||||
NodeId: apiNodeMap.GetString("oldNodeId"),
|
||||
Secret: apiNodeMap.GetString("oldNodeSecret"),
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
@@ -62,7 +63,7 @@ func (this *ValidateApiAction) RunPost(params struct {
|
||||
if net.ParseIP(params.NewHost) == nil {
|
||||
this.FailField("newHost", "请输入正确的节点主机地址")
|
||||
}
|
||||
|
||||
|
||||
params.Must.
|
||||
Field("newHost", params.NewHost).
|
||||
Require("请输入节点主机地址")
|
||||
@@ -86,7 +87,7 @@ func (this *ValidateApiAction) RunPost(params struct {
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
}{
|
||||
Endpoints: []string{params.OldProtocol + "://" + params.OldHost + ":" + params.OldPort},
|
||||
Endpoints: []string{params.OldProtocol + "://" + configutils.QuoteIP(params.OldHost) + ":" + params.OldPort},
|
||||
},
|
||||
NodeId: params.OldNodeId,
|
||||
Secret: params.OldNodeSecret,
|
||||
|
||||
@@ -2,11 +2,14 @@ package setup
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/dbs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -27,7 +30,20 @@ func (this *ValidateDbAction) RunPost(params struct {
|
||||
params.Must.
|
||||
Field("host", params.Host).
|
||||
Require("请输入主机地址").
|
||||
Match(`^[\w\.-]+$`, "主机地址中不能包含特殊字符").
|
||||
Expect(func() (message string, success bool) {
|
||||
// 是否为IP
|
||||
if net.ParseIP(params.Host) != nil {
|
||||
success = true
|
||||
return
|
||||
}
|
||||
if !regexp.MustCompile(`^[\w.-]+$`).MatchString(params.Host) {
|
||||
message = "主机地址中不能包含特殊字符"
|
||||
success = false
|
||||
return
|
||||
}
|
||||
success = true
|
||||
return
|
||||
}).
|
||||
Field("port", params.Port).
|
||||
Require("请输入端口").
|
||||
Match(`^\d+$`, "端口中只能包含数字").
|
||||
@@ -41,7 +57,7 @@ func (this *ValidateDbAction) RunPost(params struct {
|
||||
// 测试连接
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: params.Username + ":" + params.Password + "@tcp(" + params.Host + ":" + params.Port + ")/" + params.Database,
|
||||
Dsn: params.Username + ":" + params.Password + "@tcp(" + configutils.QuoteIP(params.Host) + ":" + params.Port + ")/" + params.Database,
|
||||
Prefix: "",
|
||||
})
|
||||
if err != nil {
|
||||
@@ -58,7 +74,7 @@ func (this *ValidateDbAction) RunPost(params struct {
|
||||
if strings.Contains(err.Error(), "Error 1049") {
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: params.Username + ":" + params.Password + "@tcp(" + params.Host + ":" + params.Port + ")/",
|
||||
Dsn: params.Username + ":" + params.Password + "@tcp(" + configutils.QuoteIP(params.Host) + ":" + params.Port + ")/",
|
||||
Prefix: "",
|
||||
})
|
||||
|
||||
|
||||
@@ -21,6 +21,11 @@ func NewUserMustAuth(module string) *userMustAuth {
|
||||
}
|
||||
|
||||
func (this *userMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramName string) (goNext bool) {
|
||||
if teaconst.IsRecoverMode {
|
||||
actionPtr.Object().RedirectURL("/recover")
|
||||
return false
|
||||
}
|
||||
|
||||
var action = actionPtr.Object()
|
||||
|
||||
// 安全相关
|
||||
|
||||
@@ -13,6 +13,11 @@ type UserShouldAuth struct {
|
||||
}
|
||||
|
||||
func (this *UserShouldAuth) BeforeAction(actionPtr actions.ActionWrapper, paramName string) (goNext bool) {
|
||||
if teaconst.IsRecoverMode {
|
||||
actionPtr.Object().RedirectURL("/recover")
|
||||
return false
|
||||
}
|
||||
|
||||
this.action = actionPtr.Object()
|
||||
|
||||
// 安全相关
|
||||
@@ -34,7 +39,7 @@ func (this *UserShouldAuth) BeforeAction(actionPtr actions.ActionWrapper, paramN
|
||||
return true
|
||||
}
|
||||
|
||||
// 存储用户名到SESSION
|
||||
// StoreAdmin 存储用户名到SESSION
|
||||
func (this *UserShouldAuth) StoreAdmin(adminId int64, remember bool) {
|
||||
// 修改sid的时间
|
||||
if remember {
|
||||
|
||||
@@ -131,6 +131,9 @@ import (
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/user-nodes"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/user-ui"
|
||||
|
||||
// 恢复
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/recover"
|
||||
|
||||
// 安装
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/setup"
|
||||
|
||||
|
||||
54
web/views/@default/recover/@install.css
Normal file
54
web/views/@default/recover/@install.css
Normal file
@@ -0,0 +1,54 @@
|
||||
.install-box {
|
||||
width: 50em;
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
margin-left: -25em;
|
||||
top: 1em;
|
||||
bottom: 1em;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.install-box .button.margin {
|
||||
margin-top: 1em;
|
||||
}
|
||||
.install-box .button.primary {
|
||||
float: right;
|
||||
}
|
||||
.install-box table td.title {
|
||||
width: 10em;
|
||||
}
|
||||
.install-box .radio {
|
||||
margin-right: 1em;
|
||||
}
|
||||
.install-box .radio label {
|
||||
cursor: pointer !important;
|
||||
font-size: 0.9em !important;
|
||||
}
|
||||
.install-box h3 {
|
||||
font-weight: normal;
|
||||
}
|
||||
.install-box .content-box {
|
||||
overflow-y: auto;
|
||||
position: fixed;
|
||||
top: 5em;
|
||||
bottom: 5em;
|
||||
left: 50%;
|
||||
width: 50em;
|
||||
padding-right: 1em;
|
||||
margin-left: -25em;
|
||||
z-index: 1;
|
||||
}
|
||||
.install-box .content-box::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
.install-box .button-group {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
margin-left: -25em;
|
||||
z-index: 1;
|
||||
width: 50em;
|
||||
bottom: 1em;
|
||||
}
|
||||
.install-box::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
/*# sourceMappingURL=@install.css.map */
|
||||
1
web/views/@default/recover/@install.css.map
Normal file
1
web/views/@default/recover/@install.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["@install.less"],"names":[],"mappings":"AAAA;EAGC,WAAA;EACA,eAAA;EACA,SAAA;EACA,kBAAA;EACA,QAAA;EACA,WAAA;EACA,gBAAA;;AATD,YAWC,QAAO;EACN,eAAA;;AAZF,YAeC,QAAO;EACN,YAAA;;AAhBF,YAmBC,MACC,GAAE;EACD,WAAA;;AArBH,YAyBC;EACC,iBAAA;;AA1BF,YAyBC,OAGC;EACC,0BAAA;EACA,2BAAA;;AA9BH,YAkCC;EACC,mBAAA;;AAnCF,YAsCC;EACC,gBAAA;EACA,eAAA;EACA,QAAA;EACA,WAAA;EACA,SAAA;EACA,WAAA;EACA,kBAAA;EACA,kBAAA;EACA,UAAA;;AA/CF,YAkDC,aAAY;EACX,UAAA;;AAnDF,YAsDC;EACC,eAAA;EACA,SAAA;EACA,kBAAA;EACA,UAAA;EACA,WAAA;EACA,WAAA;;AAIF,YAAY;EACX,UAAA","file":"@install.css"}
|
||||
67
web/views/@default/recover/@install.less
Normal file
67
web/views/@default/recover/@install.less
Normal file
@@ -0,0 +1,67 @@
|
||||
.install-box {
|
||||
@width: 50em;
|
||||
|
||||
width: @width;
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
margin-left: -@width/2;
|
||||
top: 1em;
|
||||
bottom: 1em;
|
||||
overflow-y: auto;
|
||||
|
||||
.button.margin {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.button.primary {
|
||||
float: right;
|
||||
}
|
||||
|
||||
table {
|
||||
td.title {
|
||||
width: 10em;
|
||||
}
|
||||
}
|
||||
|
||||
.radio {
|
||||
margin-right: 1em;
|
||||
|
||||
label {
|
||||
cursor: pointer !important;
|
||||
font-size: 0.9em !important;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.content-box {
|
||||
overflow-y: auto;
|
||||
position: fixed;
|
||||
top: 5em;
|
||||
bottom: 5em;
|
||||
left: 50%;
|
||||
width: @width;
|
||||
padding-right: 1em;
|
||||
margin-left: -@width/2;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.content-box::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
margin-left: -@width/2;
|
||||
z-index: 1;
|
||||
width: @width;
|
||||
bottom: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.install-box::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
54
web/views/@default/recover/index.css
Normal file
54
web/views/@default/recover/index.css
Normal file
@@ -0,0 +1,54 @@
|
||||
.install-box {
|
||||
width: 50em;
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
margin-left: -25em;
|
||||
top: 1em;
|
||||
bottom: 1em;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.install-box .button.margin {
|
||||
margin-top: 1em;
|
||||
}
|
||||
.install-box .button.primary {
|
||||
float: right;
|
||||
}
|
||||
.install-box table td.title {
|
||||
width: 10em;
|
||||
}
|
||||
.install-box .radio {
|
||||
margin-right: 1em;
|
||||
}
|
||||
.install-box .radio label {
|
||||
cursor: pointer !important;
|
||||
font-size: 0.9em !important;
|
||||
}
|
||||
.install-box h3 {
|
||||
font-weight: normal;
|
||||
}
|
||||
.install-box .content-box {
|
||||
overflow-y: auto;
|
||||
position: fixed;
|
||||
top: 5em;
|
||||
bottom: 5em;
|
||||
left: 50%;
|
||||
width: 50em;
|
||||
padding-right: 1em;
|
||||
margin-left: -25em;
|
||||
z-index: 1;
|
||||
}
|
||||
.install-box .content-box::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
.install-box .button-group {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
margin-left: -25em;
|
||||
z-index: 1;
|
||||
width: 50em;
|
||||
bottom: 1em;
|
||||
}
|
||||
.install-box::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
/*# sourceMappingURL=index.css.map */
|
||||
1
web/views/@default/recover/index.css.map
Normal file
1
web/views/@default/recover/index.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["@install.less"],"names":[],"mappings":"AAAA;EAGC,WAAA;EACA,eAAA;EACA,SAAA;EACA,kBAAA;EACA,QAAA;EACA,WAAA;EACA,gBAAA;;AATD,YAWC,QAAO;EACN,eAAA;;AAZF,YAeC,QAAO;EACN,YAAA;;AAhBF,YAmBC,MACC,GAAE;EACD,WAAA;;AArBH,YAyBC;EACC,iBAAA;;AA1BF,YAyBC,OAGC;EACC,0BAAA;EACA,2BAAA;;AA9BH,YAkCC;EACC,mBAAA;;AAnCF,YAsCC;EACC,gBAAA;EACA,eAAA;EACA,QAAA;EACA,WAAA;EACA,SAAA;EACA,WAAA;EACA,kBAAA;EACA,kBAAA;EACA,UAAA;;AA/CF,YAkDC,aAAY;EACX,UAAA;;AAnDF,YAsDC;EACC,eAAA;EACA,SAAA;EACA,kBAAA;EACA,UAAA;EACA,WAAA;EACA,WAAA;;AAIF,YAAY;EACX,UAAA","file":"index.css"}
|
||||
138
web/views/@default/recover/index.html
Normal file
138
web/views/@default/recover/index.html
Normal file
@@ -0,0 +1,138 @@
|
||||
<!doctype html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
<link rel="shortcut icon" href="/images/favicon.png"/>
|
||||
<title>GoEdge管理系统恢复模式</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0">
|
||||
{$TEA.VUE}
|
||||
{$TEA.SEMANTIC}
|
||||
<script type="text/javascript" src="/js/md5.min.js"></script>
|
||||
<script type="text/javascript" src="/js/utils.js"></script>
|
||||
<script type="text/javascript" src="/js/sweetalert2/dist/sweetalert2.all.min.js"></script>
|
||||
<script type="text/javascript" src="/ui/components.js"></script>
|
||||
<link rel="stylesheet" href="/_/@default/@layout.css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="install-box">
|
||||
<!-- 步骤列表 -->
|
||||
<div class="ui steps fluid small">
|
||||
<div class="ui step" :class="{active: step == STEP_INTRO}">
|
||||
<div class="content">
|
||||
<div :class="{title: step == STEP_INTRO}">1. 介绍</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui step" :class="{active: step == STEP_NEW_API}">
|
||||
<div class="content">
|
||||
<div :class="{title: step == STEP_NEW_API}">2. 新API地址</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui step" :class="{active: step == STEP_API_LIST}">
|
||||
<div class="content">
|
||||
<div :class="{title: step == STEP_API_LIST}">3. 修改API节点地址</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui step" :class="{active: step == STEP_FINISH}">
|
||||
<div class="content">
|
||||
<div :class="{title: step == STEP_FINISH}">4. 完成</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 介绍 -->
|
||||
<div v-if="step == STEP_INTRO">
|
||||
可以通过恢复模式重新设置系统的API节点。
|
||||
|
||||
<button class="ui button primary" style="margin-top: 10em" type="button" @click.prevent="goIntroNext()">开始<i class="icon long arrow right"></i></button>
|
||||
</div>
|
||||
|
||||
<!-- 新API地址 -->
|
||||
<div v-show="step == STEP_NEW_API">
|
||||
<p class="comment">输入一个可用的API节点信息:</p>
|
||||
<form method="post" class="ui form" data-tea-action=".validateApi" data-tea-success="apiSuccess" data-tea-before="apiSubmit" data-tea-done="apiDone">
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">节点协议 *</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" name="protocol">
|
||||
<option value="http">HTTP</option>
|
||||
<option value="https">HTTPS</option>
|
||||
</select>
|
||||
<p class="comment">API节点使用的协议。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>主机地址 *</td>
|
||||
<td>
|
||||
<input type="text" name="host" maxlength="100" style="width:20em"/>
|
||||
<p class="comment">API节点所在主机地址。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>服务端口 *</td>
|
||||
<td>
|
||||
<input type="text" name="port" style="width:6em" maxlength="5"/>
|
||||
<p class="comment">API节点启动的端口。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>节点nodeId *</td>
|
||||
<td>
|
||||
<input type="text" name="nodeId" maxlength="100"/>
|
||||
<p class="comment">在节点的配置文件中<code-label>configs/api.yaml</code-label>中获取,不需要带双引号。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>节点secret *</td>
|
||||
<td>
|
||||
<input type="password" name="nodeSecret" maxlength="100"/>
|
||||
<p class="comment">在节点的配置文件中<code-label>configs/api.yaml</code-label>中获取,不需要带双引号。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<button class="ui button" type="button" @click.prevent="goBackIntro"><i class="icon long arrow left"></i>上一步</button>
|
||||
<button class="ui button primary" type="submit" v-if="!apiRequesting">下一步<i class="icon long arrow right"></i></button>
|
||||
<button class="ui button primary" type="button" v-if="apiRequesting">提交中</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- API节点列表 -->
|
||||
<div v-show="step == STEP_API_LIST">
|
||||
<form method="post" class="ui form" data-tea-action=".updateHosts" data-tea-success="updateHostsSuccess">
|
||||
<input type="hidden" name="protocol" :value="apiNodeInfo.protocol"/>
|
||||
<input type="hidden" name="host" :value="apiNodeInfo.host"/>
|
||||
<input type="hidden" name="port" :value="apiNodeInfo.port"/>
|
||||
<input type="hidden" name="nodeId" :value="apiNodeInfo.nodeId"/>
|
||||
<input type="hidden" name="nodeSecret" :value="apiNodeInfo.nodeSecret"/>
|
||||
|
||||
<table class="ui table selectable celled">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 50%">原地址</th>
|
||||
<th>新地址(留空表示不修改)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr v-for="host in apiNodeInfo.hosts">
|
||||
<td>{{host}}</td>
|
||||
<td>
|
||||
<input type="text" maxlength="100" name="newHosts"/>
|
||||
<input type="hidden" name="oldHosts" :value="host"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<button class="ui button" type="button" @click.prevent="goBackAPI"><i class="icon long arrow left"></i>上一步</button>
|
||||
<button class="ui button primary" type="submit">保存<i class="icon long arrow right"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 修改成功 -->
|
||||
<div v-show="step == STEP_FINISH">
|
||||
修改成功,请<a href="/">刷新</a>此页面后,进入系统。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
42
web/views/@default/recover/index.js
Normal file
42
web/views/@default/recover/index.js
Normal file
@@ -0,0 +1,42 @@
|
||||
Tea.context(function () {
|
||||
this.STEP_INTRO = "intro"
|
||||
this.STEP_NEW_API = "newAPI"
|
||||
this.STEP_API_LIST = "apiList"
|
||||
this.STEP_FINISH = "finish"
|
||||
|
||||
this.step = this.STEP_INTRO
|
||||
|
||||
// 介绍
|
||||
this.goIntroNext = function () {
|
||||
this.step = this.STEP_NEW_API
|
||||
}
|
||||
|
||||
// 新API地址
|
||||
this.apiNodeInfo = {}
|
||||
this.apiRequesting = false
|
||||
this.goBackIntro = function () {
|
||||
this.step = this.STEP_INTRO
|
||||
}
|
||||
|
||||
this.apiSubmit = function () {
|
||||
this.apiRequesting = true
|
||||
}
|
||||
|
||||
this.apiDone = function () {
|
||||
this.apiRequesting = false
|
||||
}
|
||||
|
||||
this.apiSuccess = function (resp) {
|
||||
this.step = this.STEP_API_LIST
|
||||
this.apiNodeInfo = resp.data.apiNode
|
||||
}
|
||||
|
||||
// 修改API主机地址
|
||||
this.goBackAPI = function () {
|
||||
this.step = this.STEP_NEW_API
|
||||
}
|
||||
|
||||
this.updateHostsSuccess = function () {
|
||||
this.step = this.STEP_FINISH
|
||||
}
|
||||
})
|
||||
2
web/views/@default/recover/index.less
Normal file
2
web/views/@default/recover/index.less
Normal file
@@ -0,0 +1,2 @@
|
||||
@import "@install";
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
<div>感谢你选择使用<strong>GoEdge</strong>集群服务系统,下面让我们一起开始配置系统。</div>
|
||||
<div class="margin">在这之前如果你还没有可用的MySQL数据库,请先安装数据库再进行。</div>
|
||||
<div class="margin" style="color: grey">免责声明:GoEdge软件开发者并不对您的软件使用方法、服务对象、服务内容负道德或法律上的约束责任,在使用本软件时产生的一切法律风险自负。</div>
|
||||
<div class="margin" style="color: grey">用户协议:请在遵守中华人民共和国政策、法律、法规的前提下使用本软件;自愿承担因不当使用本软件产生的一切法律后果;承认GoEdge开发者拥有本软件的著作权;点击"开始"安装表示你同意此用户协议。</div>
|
||||
<button class="ui button primary" style="margin-top: 10em" type="button" @click.prevent="goIntroNext()">开始<i class="icon long arrow right"></i></button>
|
||||
</div>
|
||||
|
||||
@@ -85,20 +86,10 @@
|
||||
<td>
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<div v-if="serverIPs.length > 0 && !apiHostInput">
|
||||
<select name="newHost" class="ui dropdown auto-width">
|
||||
<option v-for="ip in serverIPs" :value="ip">{{ip}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div v-else="">
|
||||
<input type="text" name="newHost" value="" ref="newHostRef" placeholder="x.x.x.x"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<a href="" v-if="!apiHostInput" @click.prevent="inputAPIHost()">[手工输入]</a>
|
||||
<input type="text" name="newHost" value="" ref="newHostRef" placeholder="x.x.x.x" v-model="currentHost"/>
|
||||
</div>
|
||||
</div>
|
||||
<p class="comment">其他节点访问此API节点的主机地址,可以是IP或者域名,第一次安装时通常是<strong>当前服务器</strong>的IP地址。后期可以修改这个地址。</p>
|
||||
<p class="comment">其他节点访问此API节点的主机地址,可以是IP或者域名,第一次安装时通常是<strong>当前服务器</strong>的公网IP地址。后期可以修改这个地址。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -191,7 +182,10 @@
|
||||
<tr>
|
||||
<td>访问日志保留天数</td>
|
||||
<td>
|
||||
<input type="number" name="accessLogKeepDays" style="width: 5em" maxlength="4" value="30"/>
|
||||
<div class="ui input right labeled">
|
||||
<input type="number" name="accessLogKeepDays" style="width: 5em" maxlength="4" value="30"/>
|
||||
<span class="ui label">天</span>
|
||||
</div>
|
||||
<p class="comment">网站等服务记录的访问日志保留天数,防止无限制地占用数据库空间。</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user