chore: 初始化 Cloudreve 源码仓库
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/cloudreve/Cloudreve/v4/application/constants"
|
||||
"github.com/cloudreve/Cloudreve/v4/application/dependency"
|
||||
"github.com/cloudreve/Cloudreve/v4/application/statics"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(ejectCmd)
|
||||
}
|
||||
|
||||
var ejectCmd = &cobra.Command{
|
||||
Use: "eject",
|
||||
Short: "Eject all embedded static files",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
dep := dependency.NewDependency(
|
||||
dependency.WithConfigPath(confPath),
|
||||
dependency.WithProFlag(constants.IsPro == "true"),
|
||||
)
|
||||
logger := dep.Logger()
|
||||
|
||||
if err := statics.Eject(dep.Logger(), dep.Statics()); err != nil {
|
||||
logger.Error("Failed to eject static files: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/cloudreve/Cloudreve/v4/application/dependency"
|
||||
"github.com/cloudreve/Cloudreve/v4/ent/entity"
|
||||
"github.com/cloudreve/Cloudreve/v4/inventory/types"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/encrypt"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/setting"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
outputToFile string
|
||||
newMasterKeyFile string
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(masterKeyCmd)
|
||||
masterKeyCmd.AddCommand(masterKeyGenerateCmd)
|
||||
masterKeyCmd.AddCommand(masterKeyGetCmd)
|
||||
masterKeyCmd.AddCommand(masterKeyRotateCmd)
|
||||
|
||||
masterKeyGenerateCmd.Flags().StringVarP(&outputToFile, "output", "o", "", "Output master key to file instead of stdout")
|
||||
masterKeyRotateCmd.Flags().StringVarP(&newMasterKeyFile, "new-key", "n", "", "Path to file containing the new master key (base64 encoded).")
|
||||
}
|
||||
|
||||
var masterKeyCmd = &cobra.Command{
|
||||
Use: "master-key",
|
||||
Short: "Master encryption key management",
|
||||
Long: "Manage master encryption keys for file encryption. Use subcommands to generate, get, or rotate keys.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
_ = cmd.Help()
|
||||
},
|
||||
}
|
||||
|
||||
var masterKeyGenerateCmd = &cobra.Command{
|
||||
Use: "generate",
|
||||
Short: "Generate a new master encryption key",
|
||||
Long: "Generate a new random 32-byte (256-bit) master encryption key and output it in base64 format.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Generate 32-byte random key
|
||||
key := make([]byte, 32)
|
||||
if _, err := io.ReadFull(rand.Reader, key); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: Failed to generate random key: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Encode to base64
|
||||
encodedKey := base64.StdEncoding.EncodeToString(key)
|
||||
|
||||
if outputToFile != "" {
|
||||
// Write to file
|
||||
if err := os.WriteFile(outputToFile, []byte(encodedKey), 0600); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: Failed to write key to file: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Master key generated and saved to: %s\n", outputToFile)
|
||||
} else {
|
||||
// Output to stdout
|
||||
fmt.Println(encodedKey)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var masterKeyGetCmd = &cobra.Command{
|
||||
Use: "get",
|
||||
Short: "Get the current master encryption key",
|
||||
Long: "Retrieve and display the current master encryption key from the configured vault (setting, env, or file).",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := context.Background()
|
||||
dep := dependency.NewDependency(
|
||||
dependency.WithConfigPath(confPath),
|
||||
)
|
||||
logger := dep.Logger()
|
||||
|
||||
// Get the master key vault
|
||||
vault := encrypt.NewMasterEncryptKeyVault(ctx, dep.SettingProvider())
|
||||
|
||||
// Retrieve the master key
|
||||
key, err := vault.GetMasterKey(ctx)
|
||||
if err != nil {
|
||||
logger.Error("Failed to get master key: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Encode to base64 and display
|
||||
encodedKey := base64.StdEncoding.EncodeToString(key)
|
||||
fmt.Println("")
|
||||
fmt.Println(encodedKey)
|
||||
},
|
||||
}
|
||||
|
||||
var masterKeyRotateCmd = &cobra.Command{
|
||||
Use: "rotate",
|
||||
Short: "Rotate the master encryption key",
|
||||
Long: `Rotate the master encryption key by re-encrypting all encrypted file keys with a new master key.
|
||||
This operation:
|
||||
1. Retrieves the current master key
|
||||
2. Loads a new master key from file
|
||||
3. Re-encrypts all file encryption keys with the new master key
|
||||
4. Updates the master key in the settings database
|
||||
|
||||
Warning: This is a critical operation. Make sure to backup your database before proceeding.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := context.Background()
|
||||
dep := dependency.NewDependency(
|
||||
dependency.WithConfigPath(confPath),
|
||||
)
|
||||
logger := dep.Logger()
|
||||
|
||||
logger.Info("Starting master key rotation...")
|
||||
|
||||
// Get the old master key
|
||||
vault := encrypt.NewMasterEncryptKeyVault(ctx, dep.SettingProvider())
|
||||
oldMasterKey, err := vault.GetMasterKey(ctx)
|
||||
if err != nil {
|
||||
logger.Error("Failed to get current master key: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
logger.Info("Retrieved current master key")
|
||||
|
||||
// Get or generate the new master key
|
||||
var newMasterKey []byte
|
||||
// Load from file
|
||||
keyData, err := os.ReadFile(newMasterKeyFile)
|
||||
if err != nil {
|
||||
logger.Error("Failed to read new master key file: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
newMasterKey, err = base64.StdEncoding.DecodeString(string(keyData))
|
||||
if err != nil {
|
||||
logger.Error("Failed to decode new master key: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if len(newMasterKey) != 32 {
|
||||
logger.Error("Invalid new master key: must be 32 bytes (256 bits), got %d bytes", len(newMasterKey))
|
||||
os.Exit(1)
|
||||
}
|
||||
logger.Info("Loaded new master key from file: %s", newMasterKeyFile)
|
||||
|
||||
// Query all entities with encryption metadata
|
||||
db := dep.DBClient()
|
||||
entities, err := db.Entity.Query().
|
||||
Where(entity.Not(entity.PropsIsNil())).
|
||||
All(ctx)
|
||||
if err != nil {
|
||||
logger.Error("Failed to query entities: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logger.Info("Found %d entities to check for encryption", len(entities))
|
||||
|
||||
// Re-encrypt each entity's encryption key
|
||||
encryptedCount := 0
|
||||
for _, ent := range entities {
|
||||
if ent.Props == nil || ent.Props.EncryptMetadata == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
encMeta := ent.Props.EncryptMetadata
|
||||
|
||||
// Decrypt the file key with old master key
|
||||
decryptedFileKey, err := encrypt.DecryptWithMasterKey(oldMasterKey, encMeta.Key)
|
||||
if err != nil {
|
||||
logger.Error("Failed to decrypt key for entity %d: %s", ent.ID, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Re-encrypt the file key with new master key
|
||||
newEncryptedKey, err := encrypt.EncryptWithMasterKey(newMasterKey, decryptedFileKey)
|
||||
if err != nil {
|
||||
logger.Error("Failed to re-encrypt key for entity %d: %s", ent.ID, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Update the entity
|
||||
newProps := *ent.Props
|
||||
newProps.EncryptMetadata = &types.EncryptMetadata{
|
||||
Algorithm: encMeta.Algorithm,
|
||||
Key: newEncryptedKey,
|
||||
KeyPlainText: nil, // Don't store plaintext
|
||||
IV: encMeta.IV,
|
||||
}
|
||||
|
||||
err = db.Entity.UpdateOne(ent).
|
||||
SetProps(&newProps).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
logger.Error("Failed to update entity %d: %s", ent.ID, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
encryptedCount++
|
||||
}
|
||||
|
||||
logger.Info("Re-encrypted %d file keys", encryptedCount)
|
||||
|
||||
// Update the master key in settings
|
||||
keyStore := dep.SettingProvider().MasterEncryptKeyVault(ctx)
|
||||
if keyStore == setting.MasterEncryptKeyVaultTypeSetting {
|
||||
encodedNewKey := base64.StdEncoding.EncodeToString(newMasterKey)
|
||||
err = dep.SettingClient().Set(ctx, map[string]string{
|
||||
"encrypt_master_key": encodedNewKey,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error("Failed to update master key in settings: %s", err)
|
||||
logger.Error("WARNING: File keys have been re-encrypted but master key update failed!")
|
||||
logger.Error("Please manually update the encrypt_master_key setting.")
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
logger.Info("Current master key is stored in %q", keyStore)
|
||||
if keyStore == setting.MasterEncryptKeyVaultTypeEnv {
|
||||
logger.Info("Please update the new master encryption key in your \"CR_ENCRYPT_MASTER_KEY\" environment variable.")
|
||||
} else if keyStore == setting.MasterEncryptKeyVaultTypeFile {
|
||||
logger.Info("Please update the new master encryption key in your key file: %q", dep.SettingProvider().MasterEncryptKeyFile(ctx))
|
||||
}
|
||||
logger.Info("Last step: Please manually update the new master encryption key in your ENV or key file.")
|
||||
}
|
||||
|
||||
logger.Info("Master key rotation completed successfully")
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/cloudreve/Cloudreve/v4/application/constants"
|
||||
"github.com/cloudreve/Cloudreve/v4/application/dependency"
|
||||
"github.com/cloudreve/Cloudreve/v4/application/migrator"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/util"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
v3ConfPath string
|
||||
forceReset bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(migrateCmd)
|
||||
migrateCmd.PersistentFlags().StringVar(&v3ConfPath, "v3-conf", "", "Path to the v3 config file")
|
||||
migrateCmd.PersistentFlags().BoolVar(&forceReset, "force-reset", false, "Force reset migration state and start from beginning")
|
||||
}
|
||||
|
||||
var migrateCmd = &cobra.Command{
|
||||
Use: "migrate",
|
||||
Short: "Migrate from v3 to v4",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
dep := dependency.NewDependency(
|
||||
dependency.WithConfigPath(confPath),
|
||||
dependency.WithRequiredDbVersion(constants.BackendVersion),
|
||||
dependency.WithProFlag(constants.IsPro == "true"),
|
||||
)
|
||||
logger := dep.Logger()
|
||||
logger.Info("Migrating from v3 to v4...")
|
||||
|
||||
if v3ConfPath == "" {
|
||||
logger.Error("v3 config file is required, please use -v3-conf to specify the path.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Check if state file exists and warn about resuming
|
||||
stateFilePath := filepath.Join(filepath.Dir(v3ConfPath), "migration_state.json")
|
||||
if util.Exists(stateFilePath) && !forceReset {
|
||||
logger.Info("Found existing migration state file at %s. Migration will resume from the last successful step.", stateFilePath)
|
||||
logger.Info("If you want to start migration from the beginning, please use --force-reset flag.")
|
||||
} else if forceReset && util.Exists(stateFilePath) {
|
||||
logger.Info("Force resetting migration state. Will start from the beginning.")
|
||||
if err := os.Remove(stateFilePath); err != nil {
|
||||
logger.Error("Failed to remove migration state file: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
migrator, err := migrator.NewMigrator(dep, v3ConfPath)
|
||||
if err != nil {
|
||||
logger.Error("Failed to create migrator: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := migrator.Migrate(); err != nil {
|
||||
logger.Error("Failed to migrate: %s", err)
|
||||
logger.Info("Migration failed but state has been saved. You can retry with the same command to resume from the last successful step.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logger.Info("Migration from v3 to v4 completed successfully.")
|
||||
},
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/util"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var (
|
||||
confPath string
|
||||
licenseKey string
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&confPath, "conf", "c", util.DataPath("conf.ini"), "Path to the config file")
|
||||
rootCmd.PersistentFlags().BoolVarP(&util.UseWorkingDir, "use-working-dir", "w", false, "Use working directory, instead of executable directory")
|
||||
}
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "cloudreve",
|
||||
Short: "Cloudreve is a server-side self-hosted cloud storage platform",
|
||||
Long: `Self-hosted file management and sharing system, supports multiple storage providers.
|
||||
Complete documentation is available at https://docs.cloudreve.org/`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Do Stuff Here
|
||||
},
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
cmd, _, err := rootCmd.Find(os.Args[1:])
|
||||
// redirect to default server cmd if no cmd is given
|
||||
if err == nil && cmd.Use == rootCmd.Use && cmd.Flags().Parse(os.Args[1:]) != pflag.ErrHelp {
|
||||
args := append([]string{"server"}, os.Args[1:]...)
|
||||
rootCmd.SetArgs(args)
|
||||
}
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/cloudreve/Cloudreve/v4/application"
|
||||
"github.com/cloudreve/Cloudreve/v4/application/constants"
|
||||
"github.com/cloudreve/Cloudreve/v4/application/dependency"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/logging"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(serverCmd)
|
||||
serverCmd.PersistentFlags().StringVarP(&licenseKey, "license-key", "l", "", "License key of your Cloudreve Pro")
|
||||
}
|
||||
|
||||
var serverCmd = &cobra.Command{
|
||||
Use: "server",
|
||||
Short: "Start a Cloudreve server with the given config file",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
dep := dependency.NewDependency(
|
||||
dependency.WithConfigPath(confPath),
|
||||
dependency.WithProFlag(constants.IsProBool),
|
||||
dependency.WithRequiredDbVersion(constants.BackendVersion),
|
||||
)
|
||||
server := application.NewServer(dep)
|
||||
logger := dep.Logger()
|
||||
|
||||
server.PrintBanner()
|
||||
|
||||
// Graceful shutdown after received signal.
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
|
||||
go shutdown(sigChan, logger, server)
|
||||
|
||||
if err := server.Start(); err != nil {
|
||||
logger.Error("Failed to start server: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
<-sigChan
|
||||
}()
|
||||
},
|
||||
}
|
||||
|
||||
func shutdown(sigChan chan os.Signal, logger logging.Logger, server application.Server) {
|
||||
sig := <-sigChan
|
||||
logger.Info("Signal %s received, shutting down server...", sig)
|
||||
server.Close()
|
||||
close(sigChan)
|
||||
}
|
||||
Reference in New Issue
Block a user