初始化蓝鸟文件快递柜源码与部署文档
This commit is contained in:
commit
5643d22242
70 files changed
+18669
No files matched your search
@@ -0,0 +1,171 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"bluebird/internal/app"
|
||||
"bluebird/internal/web"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
type accessURLFlags []string
|
||||
|
||||
func (v *accessURLFlags) String() string { return strings.Join(*v, ",") }
|
||||
func (v *accessURLFlags) Set(raw string) error { *v = append(*v, raw); return nil }
|
||||
|
||||
func main() {
|
||||
if e := run(); e != nil {
|
||||
log.Print(e)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
func run() error {
|
||||
if len(os.Args) < 2 {
|
||||
return errors.New("用法: bluebird init|serve|validate-config|configure-access|check|cleanup|backup|restore|reset-password --config config.toml")
|
||||
}
|
||||
command := os.Args[1]
|
||||
flags := flag.NewFlagSet(command, flag.ContinueOnError)
|
||||
config := flags.String("config", "config.toml", "配置文件")
|
||||
username := flags.String("username", "admin", "管理员名称")
|
||||
output := flags.String("output", "", "备份输出路径")
|
||||
input := flags.String("input", "", "备份输入路径")
|
||||
var accessURLs accessURLFlags
|
||||
flags.Var(&accessURLs, "access-url", "增加访问入口,可重复")
|
||||
listen := flags.String("listen", "", "访问监听地址")
|
||||
lanURL := flags.String("lan-url", "", "内网直连入口")
|
||||
allowLAN := flags.Bool("allow-lan-http", false, "允许明确配置的内网HTTP入口")
|
||||
if e := flags.Parse(os.Args[2:]); e != nil {
|
||||
return e
|
||||
}
|
||||
if command == "configure-access" {
|
||||
if *lanURL != "" {
|
||||
address, base, err := app.LANListen(*lanURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if *listen == "" {
|
||||
*listen = address
|
||||
}
|
||||
accessURLs = append(accessURLs, base)
|
||||
*allowLAN = true
|
||||
}
|
||||
if e := app.UpdateAccessConfig(*config, *listen, accessURLs, *allowLAN); e != nil {
|
||||
return e
|
||||
}
|
||||
fmt.Println("访问入口配置已更新")
|
||||
return nil
|
||||
}
|
||||
c, e := app.LoadConfig(*config)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
if command == "validate-config" {
|
||||
fmt.Println("配置检查通过")
|
||||
return nil
|
||||
}
|
||||
if command == "restore" {
|
||||
if *input == "" {
|
||||
return errors.New("需指定 --input")
|
||||
}
|
||||
return app.Restore(c, *input)
|
||||
}
|
||||
a, e := app.Open(c)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
defer a.Close()
|
||||
switch command {
|
||||
case "init", "reset-password":
|
||||
if command == "init" && a.Initialized() {
|
||||
return errors.New("已初始化;重置请使用 reset-password")
|
||||
}
|
||||
password := os.Getenv("BLUEBIRD_ADMIN_PASSWORD")
|
||||
if password == "" {
|
||||
if !term.IsTerminal(int(os.Stdin.Fd())) {
|
||||
return errors.New("需使用交互终端或 BLUEBIRD_ADMIN_PASSWORD 环境变量")
|
||||
}
|
||||
fmt.Print("管理员密码(至少8字节): ")
|
||||
b, e := term.ReadPassword(int(os.Stdin.Fd()))
|
||||
fmt.Println()
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
password = string(b)
|
||||
fmt.Print("再次输入: ")
|
||||
again, e := term.ReadPassword(int(os.Stdin.Fd()))
|
||||
fmt.Println()
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
if password != string(again) {
|
||||
return errors.New("两次密码不同")
|
||||
}
|
||||
}
|
||||
if e = a.SetAdmin(*username, password); e != nil {
|
||||
return e
|
||||
}
|
||||
fmt.Println("管理员配置完成")
|
||||
return nil
|
||||
case "check":
|
||||
if e = a.Check(); e != nil {
|
||||
return e
|
||||
}
|
||||
fmt.Println("数据库与实体内容完整性检查通过")
|
||||
return nil
|
||||
case "cleanup":
|
||||
return a.Cleanup()
|
||||
case "backup":
|
||||
if *output == "" {
|
||||
return errors.New("需指定 --output")
|
||||
}
|
||||
return a.Backup(*config, *output)
|
||||
case "serve":
|
||||
if !a.Initialized() {
|
||||
return errors.New("请先执行 init")
|
||||
}
|
||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT)
|
||||
defer stop()
|
||||
maintenanceStop := make(chan struct{})
|
||||
maintenanceDone := make(chan struct{})
|
||||
go func() { defer close(maintenanceDone); a.RunMaintenance(maintenanceStop) }()
|
||||
server := &http.Server{Addr: c.Listen, Handler: a.Handler(web.Handler()), ReadHeaderTimeout: 10 * time.Second, IdleTimeout: 60 * time.Second, MaxHeaderBytes: 32 << 10}
|
||||
result := make(chan error, 1)
|
||||
go func() {
|
||||
if c.TLSCert != "" {
|
||||
result <- server.ListenAndServeTLS(c.TLSCert, c.TLSKey)
|
||||
} else {
|
||||
result <- server.ListenAndServe()
|
||||
}
|
||||
}()
|
||||
log.Printf("蓝鸟正在监听 %s (%s)", c.Listen, strings.TrimRight(c.PublicURL, "/"))
|
||||
select {
|
||||
case e = <-result:
|
||||
case <-ctx.Done():
|
||||
shutdown, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
e = server.Shutdown(shutdown)
|
||||
if e != nil {
|
||||
_ = server.Close()
|
||||
}
|
||||
}
|
||||
close(maintenanceStop)
|
||||
<-maintenanceDone
|
||||
a.WaitRequests()
|
||||
if errors.Is(e, http.ErrServerClosed) {
|
||||
return nil
|
||||
}
|
||||
return e
|
||||
default:
|
||||
return errors.New("未知命令")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"bluebird/internal/app"
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
)
|
||||
|
||||
func TestValidateConfigDoesNotCreateRuntimeData(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "config.toml")
|
||||
args := os.Args
|
||||
t.Cleanup(func() { os.Args = args })
|
||||
os.Args = []string{"bluebird", "validate-config", "--config", path}
|
||||
cfg := app.DefaultConfig()
|
||||
for _, invalid := range []bool{false, true} {
|
||||
if invalid {
|
||||
cfg.PublicURL = "https://files.example.com"
|
||||
}
|
||||
encoded, err := toml.Marshal(cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = os.WriteFile(path, encoded, 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = run()
|
||||
if (err != nil) != invalid {
|
||||
t.Fatalf("validation error=%v invalid=%v", err, invalid)
|
||||
}
|
||||
if _, err = os.Stat(filepath.Join(dir, "var")); !os.IsNotExist(err) {
|
||||
t.Fatal("configuration validation initialized runtime data")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user