2014-05-01 21:21:46 -04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2016-12-21 10:13:17 -02:00
// Copyright 2016 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2014-05-01 21:21:46 -04:00
package cmd
import (
"os"
"path"
2017-01-12 05:47:20 +01:00
"path/filepath"
2020-06-05 22:47:39 +02:00
"strings"
2014-05-01 21:21:46 -04:00
2021-09-19 19:49:59 +08:00
"code.gitea.io/gitea/models/db"
2024-04-03 10:16:46 +08:00
"code.gitea.io/gitea/modules/dump"
2021-07-25 00:03:58 +08:00
"code.gitea.io/gitea/modules/json"
2019-12-17 17:12:10 +01:00
"code.gitea.io/gitea/modules/log"
2016-11-10 17:24:48 +01:00
"code.gitea.io/gitea/modules/setting"
2020-09-29 17:05:13 +08:00
"code.gitea.io/gitea/modules/storage"
2020-08-11 21:05:34 +01:00
"code.gitea.io/gitea/modules/util"
2017-04-12 15:44:54 +08:00
2021-01-26 23:36:53 +08:00
"gitea.com/go-chi/session"
2022-06-18 22:06:32 +08:00
"github.com/mholt/archiver/v3"
2023-07-21 17:28:19 +08:00
"github.com/urfave/cli/v2"
2014-05-01 21:21:46 -04:00
)
2016-11-04 12:42:18 +01:00
// CmdDump represents the available dump sub-command.
2023-07-21 17:28:19 +08:00
var CmdDump = & cli . Command {
2024-04-03 10:16:46 +08:00
Name : "dump" ,
Usage : "Dump Gitea files and database" ,
Description : ` Dump compresses all related files and database into zip file. It can be used for backup and capture Gitea server image to send to maintainer ` ,
Action : runDump ,
2014-09-07 19:39:26 -04:00
Flags : [ ] cli . Flag {
2023-07-21 17:28:19 +08:00
& cli . StringFlag {
Name : "file" ,
Aliases : [ ] string { "f" } ,
2024-04-03 10:16:46 +08:00
Usage : ` Name of the dump file which will be created, default to "gitea-dump- { time}.zip". Supply '-' for stdout. See type for available types. ` ,
2019-04-01 01:31:37 -03:00
} ,
2023-07-21 17:28:19 +08:00
& cli . BoolFlag {
Name : "verbose" ,
Aliases : [ ] string { "V" } ,
Usage : "Show process details" ,
2016-11-09 23:18:22 +01:00
} ,
2023-07-21 17:28:19 +08:00
& cli . BoolFlag {
Name : "quiet" ,
Aliases : [ ] string { "q" } ,
Usage : "Only display warnings and errors" ,
2023-04-10 15:46:23 +02:00
} ,
2023-07-21 17:28:19 +08:00
& cli . StringFlag {
Name : "tempdir" ,
Aliases : [ ] string { "t" } ,
Value : os . TempDir ( ) ,
Usage : "Temporary dir path" ,
2016-11-09 23:18:22 +01:00
} ,
2023-07-21 17:28:19 +08:00
& cli . StringFlag {
Name : "database" ,
Aliases : [ ] string { "d" } ,
2023-09-03 19:44:01 +01:00
Usage : "Specify the database SQL syntax: sqlite3, mysql, mssql, postgres" ,
2017-01-03 16:20:28 +08:00
} ,
2023-07-21 17:28:19 +08:00
& cli . BoolFlag {
Name : "skip-repository" ,
Aliases : [ ] string { "R" } ,
Usage : "Skip the repository dumping" ,
2019-01-13 22:52:26 +01:00
} ,
2023-07-21 17:28:19 +08:00
& cli . BoolFlag {
Name : "skip-log" ,
Aliases : [ ] string { "L" } ,
Usage : "Skip the log dumping" ,
2020-04-30 20:30:31 -05:00
} ,
2023-07-21 17:28:19 +08:00
& cli . BoolFlag {
2021-02-08 01:00:12 +00:00
Name : "skip-custom-dir" ,
Usage : "Skip custom directory" ,
} ,
2023-07-21 17:28:19 +08:00
& cli . BoolFlag {
2021-04-12 11:33:32 +02:00
Name : "skip-lfs-data" ,
Usage : "Skip LFS data" ,
} ,
2023-07-21 17:28:19 +08:00
& cli . BoolFlag {
2021-04-12 11:33:32 +02:00
Name : "skip-attachment-data" ,
Usage : "Skip attachment data" ,
} ,
2023-07-21 17:28:19 +08:00
& cli . BoolFlag {
2022-04-26 22:30:51 +02:00
Name : "skip-package-data" ,
Usage : "Skip package data" ,
} ,
2023-07-21 17:28:19 +08:00
& cli . BoolFlag {
2022-10-24 05:19:21 +02:00
Name : "skip-index" ,
Usage : "Skip bleve index data" ,
} ,
2024-04-21 14:32:12 -07:00
& cli . BoolFlag {
Name : "skip-db" ,
Usage : "Skip database" ,
} ,
2024-04-03 10:16:46 +08:00
& cli . StringFlag {
2020-06-05 22:47:39 +02:00
Name : "type" ,
2025-04-01 12:14:01 +02:00
Usage : ` Dump output format, default to "zip", supported types: ` + strings . Join ( dump . SupportedOutputTypes , ", " ) ,
2020-06-05 22:47:39 +02:00
} ,
2014-09-07 19:39:26 -04:00
} ,
2014-05-01 21:21:46 -04:00
}
2023-07-04 20:36:08 +02:00
func fatal ( format string , args ... any ) {
2019-12-17 17:12:10 +01:00
log . Fatal ( format , args ... )
}
2016-05-12 14:32:28 -04:00
func runDump ( ctx * cli . Context ) error {
2023-06-21 13:50:26 +08:00
setting . MustInstalled ( )
2021-12-01 15:50:01 +08:00
2024-04-03 10:16:46 +08:00
quite := ctx . Bool ( "quiet" )
verbose := ctx . Bool ( "verbose" )
if verbose && quite {
fatal ( "Option --quiet and --verbose cannot both be set" )
2020-06-05 22:47:39 +02:00
}
2024-04-03 10:16:46 +08:00
// outFileName is either "-" or a file name (will be made absolute)
outFileName , outType := dump . PrepareFileNameAndType ( ctx . String ( "file" ) , ctx . String ( "type" ) )
if outType == "" {
fatal ( "Invalid output type" )
2020-06-05 22:47:39 +02:00
}
2023-04-10 15:46:23 +02:00
2024-04-03 10:16:46 +08:00
outFile := os . Stdout
if outFileName != "-" {
var err error
if outFileName , err = filepath . Abs ( outFileName ) ; err != nil {
fatal ( "Unable to get absolute path of dump file: %v" , err )
}
if exist , _ := util . IsExist ( outFileName ) ; exist {
fatal ( "Dump file %q exists" , outFileName )
2023-04-10 15:46:23 +02:00
}
2024-04-03 10:16:46 +08:00
if outFile , err = os . Create ( outFileName ) ; err != nil {
fatal ( "Unable to create dump file %q: %v" , outFileName , err )
}
defer outFile . Close ( )
2023-04-10 15:46:23 +02:00
}
2024-04-03 10:16:46 +08:00
setupConsoleLogger ( util . Iif ( quite , log . WARN , log . INFO ) , log . CanColorStderr , os . Stderr )
2017-01-23 17:11:18 +08:00
2024-04-03 10:16:46 +08:00
setting . DisableLoggerInit ( )
setting . LoadSettings ( ) // cannot access session settings otherwise
2023-04-10 15:46:23 +02:00
2021-11-07 11:11:27 +08:00
stdCtx , cancel := installSignals ( )
defer cancel ( )
err := db . InitEngine ( stdCtx )
2017-01-23 17:11:18 +08:00
if err != nil {
return err
}
2014-05-01 21:21:46 -04:00
2024-04-03 10:16:46 +08:00
if err = storage . Init ( ) ; err != nil {
2020-09-29 17:05:13 +08:00
return err
}
2024-04-03 10:16:46 +08:00
archiverGeneric , err := archiver . ByExtension ( "." + outType )
2019-01-13 22:52:26 +01:00
if err != nil {
2020-06-05 22:47:39 +02:00
fatal ( "Unable to get archiver for extension: %v" , err )
2019-01-13 22:52:26 +01:00
}
2019-12-17 17:12:10 +01:00
2024-04-03 10:16:46 +08:00
archiverWriter := archiverGeneric . ( archiver . Writer )
if err := archiverWriter . Create ( outFile ) ; err != nil {
2020-06-05 22:47:39 +02:00
fatal ( "Creating archiver.Writer failed: %v" , err )
}
2024-04-03 10:16:46 +08:00
defer archiverWriter . Close ( )
dumper := & dump . Dumper {
Writer : archiverWriter ,
Verbose : verbose ,
}
dumper . GlobalExcludeAbsPath ( outFileName )
2019-01-13 22:52:26 +01:00
2020-05-02 22:57:45 -05:00
if ctx . IsSet ( "skip-repository" ) && ctx . Bool ( "skip-repository" ) {
2019-12-17 17:12:10 +01:00
log . Info ( "Skip dumping local repositories" )
2019-01-13 22:52:26 +01:00
} else {
2020-06-05 22:47:39 +02:00
log . Info ( "Dumping local repositories... %s" , setting . RepoRootPath )
2024-04-03 10:16:46 +08:00
if err := dumper . AddRecursiveExclude ( "repos" , setting . RepoRootPath , nil ) ; err != nil {
2020-06-05 22:47:39 +02:00
fatal ( "Failed to include repositories: %v" , err )
2019-01-13 22:52:26 +01:00
}
2020-06-05 22:47:39 +02:00
2021-04-12 11:33:32 +02:00
if ctx . IsSet ( "skip-lfs-data" ) && ctx . Bool ( "skip-lfs-data" ) {
log . Info ( "Skip dumping LFS data" )
2023-03-23 20:30:28 +08:00
} else if ! setting . LFS . StartServer {
log . Info ( "LFS isn't enabled. Skip dumping LFS data" )
2023-03-13 18:23:51 +08:00
} else if err := storage . LFS . IterateObjects ( "" , func ( objPath string , object storage . Object ) error {
2020-09-29 17:05:13 +08:00
info , err := object . Stat ( )
if err != nil {
return err
2020-06-05 22:47:39 +02:00
}
2024-04-03 10:16:46 +08:00
return dumper . AddReader ( object , info , path . Join ( "data" , "lfs" , objPath ) )
2020-09-29 17:05:13 +08:00
} ) ; err != nil {
fatal ( "Failed to dump LFS objects: %v" , err )
2019-01-13 22:52:26 +01:00
}
2014-05-01 21:21:46 -04:00
}
2024-04-21 14:32:12 -07:00
if ctx . Bool ( "skip-db" ) {
// Ensure that we don't dump the database file that may reside in setting.AppDataPath or elsewhere.
dumper . GlobalExcludeAbsPath ( setting . Database . Path )
log . Info ( "Skipping database" )
} else {
tmpDir := ctx . String ( "tempdir" )
if _ , err := os . Stat ( tmpDir ) ; os . IsNotExist ( err ) {
fatal ( "Path does not exist: %s" , tmpDir )
}
2020-06-05 22:47:39 +02:00
2024-04-21 14:32:12 -07:00
dbDump , err := os . CreateTemp ( tmpDir , "gitea-db.sql" )
if err != nil {
fatal ( "Failed to create tmp file: %v" , err )
2020-08-11 21:05:34 +01:00
}
2024-04-21 14:32:12 -07:00
defer func ( ) {
_ = dbDump . Close ( )
if err := util . Remove ( dbDump . Name ( ) ) ; err != nil {
log . Warn ( "Unable to remove temporary file: %s: Error: %v" , dbDump . Name ( ) , err )
}
} ( )
2020-06-05 22:47:39 +02:00
2024-04-21 14:32:12 -07:00
targetDBType := ctx . String ( "database" )
if len ( targetDBType ) > 0 && targetDBType != setting . Database . Type . String ( ) {
log . Info ( "Dumping database %s => %s..." , setting . Database . Type , targetDBType )
} else {
log . Info ( "Dumping database..." )
}
2017-01-03 16:20:28 +08:00
2024-04-21 14:32:12 -07:00
if err := db . DumpDatabase ( dbDump . Name ( ) , targetDBType ) ; err != nil {
fatal ( "Failed to dump database: %v" , err )
}
2014-05-05 00:55:17 -04:00
2024-04-21 14:32:12 -07:00
if err = dumper . AddFile ( "gitea-db.sql" , dbDump . Name ( ) ) ; err != nil {
fatal ( "Failed to include gitea-db.sql: %v" , err )
}
2015-11-28 12:11:38 +01:00
}
2019-04-05 09:24:28 -04:00
2024-04-03 10:16:46 +08:00
log . Info ( "Adding custom configuration file from %s" , setting . CustomConf )
if err = dumper . AddFile ( "app.ini" , setting . CustomConf ) ; err != nil {
fatal ( "Failed to include specified app.ini: %v" , err )
2019-04-05 09:24:28 -04:00
}
2021-02-08 01:00:12 +00:00
if ctx . IsSet ( "skip-custom-dir" ) && ctx . Bool ( "skip-custom-dir" ) {
2021-07-08 07:38:13 -04:00
log . Info ( "Skipping custom directory" )
2021-02-08 01:00:12 +00:00
} else {
customDir , err := os . Stat ( setting . CustomPath )
if err == nil && customDir . IsDir ( ) {
2024-04-03 10:16:46 +08:00
if is , _ := dump . IsSubdir ( setting . AppDataPath , setting . CustomPath ) ; ! is {
if err := dumper . AddRecursiveExclude ( "custom" , setting . CustomPath , nil ) ; err != nil {
2021-02-08 01:00:12 +00:00
fatal ( "Failed to include custom: %v" , err )
}
} else {
log . Info ( "Custom dir %s is inside data dir %s, skipped" , setting . CustomPath , setting . AppDataPath )
2020-06-05 22:47:39 +02:00
}
} else {
2021-02-08 01:00:12 +00:00
log . Info ( "Custom dir %s doesn't exist, skipped" , setting . CustomPath )
2016-05-12 14:32:28 -04:00
}
2015-11-28 12:11:38 +01:00
}
2017-01-12 05:47:20 +01:00
2020-11-28 02:42:08 +00:00
isExist , err := util . IsExist ( setting . AppDataPath )
if err != nil {
log . Error ( "Unable to check if %s exists. Error: %v" , setting . AppDataPath , err )
}
if isExist {
2019-12-17 17:12:10 +01:00
log . Info ( "Packing data directory...%s" , setting . AppDataPath )
2017-01-12 05:47:20 +01:00
2020-06-05 22:47:39 +02:00
var excludes [ ] string
2023-02-20 00:12:01 +08:00
if setting . SessionConfig . OriginalProvider == "file" {
2020-06-05 22:47:39 +02:00
var opts session . Options
if err = json . Unmarshal ( [ ] byte ( setting . SessionConfig . ProviderConfig ) , & opts ) ; err != nil {
return err
}
excludes = append ( excludes , opts . ProviderConfig )
2017-03-02 17:41:33 +08:00
}
2020-06-05 22:47:39 +02:00
2022-10-24 05:19:21 +02:00
if ctx . IsSet ( "skip-index" ) && ctx . Bool ( "skip-index" ) {
excludes = append ( excludes , setting . Indexer . RepoPath )
excludes = append ( excludes , setting . Indexer . IssuePath )
}
2020-06-05 22:47:39 +02:00
excludes = append ( excludes , setting . RepoRootPath )
2023-06-14 11:42:38 +08:00
excludes = append ( excludes , setting . LFS . Storage . Path )
excludes = append ( excludes , setting . Attachment . Storage . Path )
excludes = append ( excludes , setting . Packages . Storage . Path )
2023-02-20 00:12:01 +08:00
excludes = append ( excludes , setting . Log . RootPath )
2024-04-03 10:16:46 +08:00
if err := dumper . AddRecursiveExclude ( "data" , setting . AppDataPath , excludes ) ; err != nil {
2019-12-17 17:12:10 +01:00
fatal ( "Failed to include data directory: %v" , err )
2017-03-02 17:41:33 +08:00
}
2017-01-12 05:47:20 +01:00
}
2021-04-12 11:33:32 +02:00
if ctx . IsSet ( "skip-attachment-data" ) && ctx . Bool ( "skip-attachment-data" ) {
log . Info ( "Skip dumping attachment data" )
2023-03-13 18:23:51 +08:00
} else if err := storage . Attachments . IterateObjects ( "" , func ( objPath string , object storage . Object ) error {
2020-09-29 17:05:13 +08:00
info , err := object . Stat ( )
if err != nil {
return err
}
2024-04-03 10:16:46 +08:00
return dumper . AddReader ( object , info , path . Join ( "data" , "attachments" , objPath ) )
2020-09-29 17:05:13 +08:00
} ) ; err != nil {
fatal ( "Failed to dump attachments: %v" , err )
}
2022-04-26 22:30:51 +02:00
if ctx . IsSet ( "skip-package-data" ) && ctx . Bool ( "skip-package-data" ) {
log . Info ( "Skip dumping package data" )
2023-03-23 20:30:28 +08:00
} else if ! setting . Packages . Enabled {
log . Info ( "Packages isn't enabled. Skip dumping package data" )
2023-03-13 18:23:51 +08:00
} else if err := storage . Packages . IterateObjects ( "" , func ( objPath string , object storage . Object ) error {
2022-04-26 22:30:51 +02:00
info , err := object . Stat ( )
if err != nil {
return err
}
2024-04-03 10:16:46 +08:00
return dumper . AddReader ( object , info , path . Join ( "data" , "packages" , objPath ) )
2022-04-26 22:30:51 +02:00
} ) ; err != nil {
fatal ( "Failed to dump packages: %v" , err )
}
2020-04-30 20:30:31 -05:00
// Doesn't check if LogRootPath exists before processing --skip-log intentionally,
// ensuring that it's clear the dump is skipped whether the directory's initialized
// yet or not.
if ctx . IsSet ( "skip-log" ) && ctx . Bool ( "skip-log" ) {
log . Info ( "Skip dumping log files" )
2020-11-28 02:42:08 +00:00
} else {
2023-02-20 00:12:01 +08:00
isExist , err := util . IsExist ( setting . Log . RootPath )
2020-11-28 02:42:08 +00:00
if err != nil {
2023-02-20 00:12:01 +08:00
log . Error ( "Unable to check if %s exists. Error: %v" , setting . Log . RootPath , err )
2020-11-28 02:42:08 +00:00
}
if isExist {
2024-04-03 10:16:46 +08:00
if err := dumper . AddRecursiveExclude ( "log" , setting . Log . RootPath , nil ) ; err != nil {
2020-11-28 02:42:08 +00:00
fatal ( "Failed to include log: %v" , err )
}
2020-01-17 10:56:51 +08:00
}
2015-11-28 12:11:38 +01:00
}
2017-02-26 16:01:49 +08:00
2024-04-03 10:16:46 +08:00
if outFileName == "-" {
log . Info ( "Finish dumping to stdout" )
} else {
if err = archiverWriter . Close ( ) ; err != nil {
_ = os . Remove ( outFileName )
fatal ( "Failed to save %q: %v" , outFileName , err )
2020-06-05 22:47:39 +02:00
}
2024-04-03 10:16:46 +08:00
if err = os . Chmod ( outFileName , 0 o600 ) ; err != nil {
2020-06-05 22:47:39 +02:00
log . Info ( "Can't change file access permissions mask to 0600: %v" , err )
}
2024-04-03 10:16:46 +08:00
log . Info ( "Finish dumping in file %s" , outFileName )
2017-01-12 05:47:20 +01:00
}
return nil
}